Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
091aa96c4d | ||
|
|
499a41ea35 | ||
|
|
025e77eaf1 | ||
|
|
0c0c373844 | ||
|
|
2a7e17fe4e | ||
|
|
1caa653f2d | ||
|
|
d9e13f42b8 | ||
|
|
8e9644761a | ||
|
|
1d2fcb9227 | ||
|
|
029bb593e7 | ||
|
|
b6d7d246fe | ||
|
|
b9084dfd47 | ||
|
|
f868052872 | ||
|
|
2636bbbf33 | ||
|
|
67650446b5 | ||
|
|
380f01567d | ||
|
|
e7add205d8 | ||
|
|
9f2fda7d85 | ||
|
|
5a74bf5f95 | ||
|
|
7ed70f925b | ||
|
|
091ee9d770 | ||
|
|
d951a7f021 | ||
|
|
86d24b069b | ||
|
|
490b3ba007 | ||
|
|
0728726251 | ||
|
|
96932fe77c | ||
|
|
346c651de3 | ||
|
|
36da3325a3 | ||
|
|
af6652004d | ||
|
|
ba45b67370 | ||
|
|
cd09ea92be | ||
|
|
6d93e4cb22 | ||
|
|
0eabc6614a | ||
|
|
b670838b3d | ||
|
|
3267bcd210 | ||
|
|
b899504862 | ||
|
|
6bf1a83057 | ||
|
|
12843dcea1 | ||
|
|
00af718a9e | ||
|
|
9e15e3ad8f | ||
|
|
2a70140ba8 | ||
|
|
93a6308bb7 | ||
|
|
e6bdd94cb2 | ||
|
|
71ddb22b39 | ||
|
|
05fb6f906d | ||
|
|
b439588172 | ||
|
|
478a321dcc | ||
|
|
db1e28f175 | ||
|
|
70c5401952 | ||
|
|
7338e3d8fe | ||
|
|
05cd4c9e08 | ||
|
|
ac474631c1 | ||
|
|
8e620c8e76 | ||
|
|
8448fe0b62 | ||
|
|
0e98b2ad8e | ||
|
|
1cb9e7efb8 | ||
|
|
572c32abbe | ||
|
|
505cab9c15 | ||
|
|
99cc13e3d1 | ||
|
|
69fb2ddbc7 | ||
|
|
3460a7b696 | ||
|
|
4fc5cd32d0 | ||
|
|
3d2e30d764 | ||
|
|
c4ae7d8203 | ||
|
|
3ed96cca88 | ||
|
|
48f906249e | ||
|
|
ce9d2c9f8e | ||
|
|
b054abe7fc | ||
|
|
2c5cb2c53f | ||
|
|
f719b63840 | ||
|
|
7142214243 | ||
|
|
09ea3a424f | ||
|
|
4787eb594a | ||
|
|
7e4819e705 | ||
|
|
6ab013cbdd | ||
|
|
11d35b72a6 | ||
|
|
5f74ed5828 | ||
|
|
9b9307ea9f | ||
|
|
0a1588d39b | ||
|
|
8bdedf9062 | ||
|
|
d3e3bd4307 | ||
|
|
bd96b97170 | ||
|
|
06c9c38199 | ||
|
|
0b9bc6f180 | ||
|
|
2a4d7c4aa8 | ||
|
|
13dff3073d | ||
|
|
e75d28effd | ||
|
|
df7d5a1689 | ||
|
|
6554db47ab | ||
|
|
ee75908134 | ||
|
|
8200304768 | ||
|
|
0bf0efd6d3 | ||
|
|
65e9139764 | ||
|
|
5d9aa72f25 | ||
|
|
d07b7f068d | ||
|
|
9800855895 | ||
|
|
1d482e42cb | ||
|
|
de0f9b9f2d | ||
|
|
aa5f234b43 | ||
|
|
76a92fd30b | ||
|
|
31eb2f9f33 | ||
|
|
e1586585b4 | ||
|
|
4ae43a1c40 | ||
|
|
2c6d42e704 | ||
|
|
8e08a2ec97 | ||
|
|
a32db637b5 | ||
|
|
8a6ee7fe94 | ||
|
|
1eb6cbd093 | ||
|
|
f21a980e37 | ||
|
|
c8cc203735 | ||
|
|
e5676a67ee | ||
|
|
0de881fa72 | ||
|
|
a6cb92c74a | ||
|
|
0198bc13a1 | ||
|
|
a921c0628e | ||
|
|
698a6a2747 | ||
|
|
2dbe33ce83 | ||
|
|
aab0116e7d | ||
|
|
1f1d2d5f49 | ||
|
|
57fbc9cd76 | ||
|
|
23c5965d47 | ||
|
|
ad2cda343b | ||
|
|
2a0a595e37 | ||
|
|
19bd69e5e3 | ||
|
|
22e4aac9fa | ||
|
|
aee413d246 | ||
|
|
cc746c9508 | ||
|
|
db3754d8d6 | ||
|
|
59cdfe50b2 | ||
|
|
b6fa63b01e | ||
|
|
c84460f61f | ||
|
|
c68251a3a7 | ||
|
|
59b0fec508 | ||
|
|
b3b2cc44fa | ||
|
|
b2b15d4e6e | ||
|
|
d5cabf7348 | ||
|
|
83b742df77 | ||
|
|
376e57ee7b | ||
|
|
823f52f91f | ||
|
|
b36e8569a1 | ||
|
|
855a4779ae | ||
|
|
d90d5ee1a0 | ||
|
|
16b9c4c952 | ||
|
|
3cabd11f9b | ||
|
|
23289a452f | ||
|
|
b0c9f582ad | ||
|
|
13fe87f66b | ||
|
|
02d8d4e3ef | ||
|
|
122bbb9ff4 | ||
|
|
000c045aa8 | ||
|
|
c23b3ac53a | ||
|
|
353e4efdef | ||
|
|
f8689eff6d | ||
|
|
7f300a00e9 | ||
|
|
65a317436d | ||
|
|
fd28be400c | ||
|
|
e5b016b7bf | ||
|
|
d60bbd86e3 | ||
|
|
370ed5777c | ||
|
|
b6f605285c | ||
|
|
193b6fd8dd | ||
|
|
7da15ca9bd | ||
|
|
61b8e1d436 | ||
|
|
78f1629a53 | ||
|
|
6a88e7463e | ||
|
|
cd9fe3a843 | ||
|
|
fa9a1ff0a0 | ||
|
|
8614cb38d9 | ||
|
|
b67d124dc0 | ||
|
|
abf6b22abd | ||
|
|
2750ca597c | ||
|
|
971dbcc21c | ||
|
|
cb7048700f | ||
|
|
0e376150a6 | ||
|
|
464a045ed8 | ||
|
|
56b06907a6 | ||
|
|
90df6c79c9 | ||
|
|
053297b1ca | ||
|
|
1ccb049d3b | ||
|
|
3f7556baef | ||
|
|
2b4c867505 | ||
|
|
d520258ffb | ||
|
|
40c2f2eeb0 | ||
|
|
d37e461eab | ||
|
|
893f76045f | ||
|
|
4e4252f916 | ||
|
|
b16cea750f | ||
|
|
5e2658c981 | ||
|
|
1cb085a82e | ||
|
|
5083db91eb | ||
|
|
f73881fa10 | ||
|
|
591494b271 | ||
|
|
1e6c57d4e4 | ||
|
|
5b5d163a6a | ||
|
|
0cd612530c | ||
|
|
f95cdb2e97 | ||
|
|
6d82c04dca | ||
|
|
63270ff898 | ||
|
|
82735d1287 | ||
|
|
c13d6da4d4 | ||
|
|
a220f08648 | ||
|
|
901b3dddb0 | ||
|
|
c2bd3b1ecc | ||
|
|
f3c2f1f6aa | ||
|
|
5e3eb03ecf | ||
|
|
4c179a27c2 | ||
|
|
f09570664f | ||
|
|
db3b5c9309 | ||
|
|
7c6570402b | ||
|
|
78bb1586d3 | ||
|
|
fba7c69b8a | ||
|
|
37ac43847e | ||
|
|
13afc2150b | ||
|
|
f5daae12ff | ||
|
|
5af9fefff6 | ||
|
|
d123af34b5 | ||
|
|
3a2dc8b22e | ||
|
|
c26e80d0fd | ||
|
|
9249887641 | ||
|
|
e2e6bb496e | ||
|
|
c1acb1afe1 | ||
|
|
311cc5b611 | ||
|
|
66a46cba89 | ||
|
|
b31f2f6cb9 | ||
|
|
2dc6b27ee7 | ||
|
|
f379cf0bf8 | ||
|
|
251069d9ea | ||
|
|
6e0e39b79f | ||
|
|
dbb5dd7c33 | ||
|
|
033af5390f | ||
|
|
4a90da4e1d | ||
|
|
65a531b8e4 | ||
|
|
53477ffc4b | ||
|
|
21f2468d67 | ||
|
|
eab5457e47 | ||
|
|
a394d9e3ae | ||
|
|
0a6c09b96f | ||
|
|
a098e98198 | ||
|
|
67fef2b152 | ||
|
|
14971424b9 | ||
|
|
da13a3b231 | ||
|
|
e143966a76 | ||
|
|
1649ba15ab | ||
|
|
b08c8f50b5 | ||
|
|
13815bf251 | ||
|
|
03fbb57bd9 | ||
|
|
1a006d6f06 | ||
|
|
f93faf26d8 | ||
|
|
eb033bc29f | ||
|
|
521535cebe | ||
|
|
78963d1e66 | ||
|
|
235fabcfae | ||
|
|
bea582f383 | ||
|
|
8c2d73f112 | ||
|
|
8e5642ff68 | ||
|
|
a714ab0601 | ||
|
|
78cf974375 | ||
|
|
d92395d205 | ||
|
|
4e74320320 | ||
|
|
42c5867c4e | ||
|
|
23414d1d71 | ||
|
|
c7487cebd1 | ||
|
|
f942071524 | ||
|
|
6bcda94cbc | ||
|
|
ba6ba32825 | ||
|
|
ba370e9599 | ||
|
|
0d8a6c6984 | ||
|
|
aacc1942fb | ||
|
|
73c3dc0a7b | ||
|
|
b2006eeb74 | ||
|
|
b0d95f6f3c | ||
|
|
f57f13ceb0 | ||
|
|
6bbf44d842 | ||
|
|
b6413f82b2 | ||
|
|
8d4e012bfa | ||
|
|
9efdb0368e | ||
|
|
5a99015acf | ||
|
|
ac7f67b3f2 | ||
|
|
dcc3c2eb38 | ||
|
|
b5894e760d | ||
|
|
85aa9635e0 | ||
|
|
07fd511e14 | ||
|
|
4cae192377 | ||
|
|
20c9538e32 | ||
|
|
4667b3a182 | ||
|
|
35de8ea0a8 | ||
|
|
05f018b81a | ||
|
|
f762303feb | ||
|
|
cb2d7c1534 | ||
|
|
94d853d3a3 | ||
|
|
27d88e4a9b | ||
|
|
cd8fd4b788 | ||
|
|
bcfabf40a4 | ||
|
|
1edb3d061b | ||
|
|
a6c8b4efa5 | ||
|
|
98b38b04c9 | ||
|
|
3e401bf652 | ||
|
|
3ed9c1c970 | ||
|
|
53b7a10f54 | ||
|
|
640b8532aa | ||
|
|
fb56896319 | ||
|
|
17dde8eeb6 | ||
|
|
1c8d2246bf | ||
|
|
17a89e5778 | ||
|
|
1ab5d829ce | ||
|
|
573673e6aa | ||
|
|
afbddbdf8f | ||
|
|
19becc8ea9 | ||
|
|
6487abde79 | ||
|
|
e5a9b792ec | ||
|
|
ca75fedaf4 | ||
|
|
6fc0e7e0a0 | ||
|
|
d3479018db | ||
|
|
f850d82e65 | ||
|
|
53f17a71f4 | ||
|
|
12d1761064 | ||
|
|
8a0929973d | ||
|
|
e8aae688c3 | ||
|
|
b4466cf0d4 | ||
|
|
f37f4de849 | ||
|
|
abb089431d | ||
|
|
48fc642e0c | ||
|
|
517489814d | ||
|
|
872df1e06f | ||
|
|
f043d73405 | ||
|
|
2ff14f92be | ||
|
|
a4f92cef1a | ||
|
|
bcb18b8847 | ||
|
|
98352362ea | ||
|
|
cfd6595386 | ||
|
|
a19571679f | ||
|
|
67e17f1104 | ||
|
|
0e1cfe0c42 | ||
|
|
7bf31c51f2 | ||
|
|
1bb726dedc | ||
|
|
dab588a4a2 | ||
|
|
203fc6fe44 | ||
|
|
3ae9f671d4 | ||
|
|
6eb0dc5447 | ||
|
|
d26c1c6814 | ||
|
|
20c1bd8a5e | ||
|
|
e58e650a97 | ||
|
|
3e660f6164 | ||
|
|
dfc060f099 | ||
|
|
985a1cde5a | ||
|
|
00838fe0c3 | ||
|
|
c9d685f5d6 | ||
|
|
3b56f82965 | ||
|
|
c3cee4b9ec | ||
|
|
bdd1e1e7ec | ||
|
|
337ccc50dc | ||
|
|
4b3666329e | ||
|
|
871900f562 | ||
|
|
2ce4568018 | ||
|
|
6cd0978f8a | ||
|
|
5189ea4696 | ||
|
|
fdce778605 | ||
|
|
02cb44ac8b | ||
|
|
67aa7b0439 | ||
|
|
f7073a5ec9 | ||
|
|
89ae370d41 | ||
|
|
04b4155582 | ||
|
|
821320252e | ||
|
|
706e708d2f | ||
|
|
e96ab5becb | ||
|
|
9e0b0be8ee | ||
|
|
a7230dfed0 | ||
|
|
2c43d2b75a | ||
|
|
835586513d | ||
|
|
c10ba5eb8e | ||
|
|
7510ed6f68 | ||
|
|
6d92e55502 | ||
|
|
6fdfa7c991 | ||
|
|
a5461b29c4 | ||
|
|
1c984f992f | ||
|
|
a267452b00 | ||
|
|
c6b08d222f | ||
|
|
be4996e312 | ||
|
|
17ad806993 | ||
|
|
fc29620ec8 | ||
|
|
93dd77b38e | ||
|
|
4e712e627c | ||
|
|
b27836011e | ||
|
|
afe3a5a010 | ||
|
|
00c9a6c237 | ||
|
|
69f7251f32 | ||
|
|
c2ab4d22b4 | ||
|
|
619b466cef | ||
|
|
f4c04f5a3c | ||
|
|
d51e637150 | ||
|
|
6f9399327e | ||
|
|
8e50d7a746 | ||
|
|
888f4e63a4 | ||
|
|
a908101258 | ||
|
|
d3f8d82279 | ||
|
|
40b557db6a | ||
|
|
c184e290b0 | ||
|
|
e63091b3cc | ||
|
|
9471077efd | ||
|
|
4a82349900 | ||
|
|
52536aaa7b | ||
|
|
edc6075fa3 | ||
|
|
7384b05433 | ||
|
|
ca0177481b | ||
|
|
51ec2e86e8 | ||
|
|
8b0ff53b69 | ||
|
|
49cde32be0 | ||
|
|
50f263990a | ||
|
|
5e2779b2da | ||
|
|
e67386e50b | ||
|
|
d66eb93e5d | ||
|
|
767c35c883 | ||
|
|
223876d92b | ||
|
|
7ac622bc5f | ||
|
|
77f309c94f | ||
|
|
b0827b4dc4 | ||
|
|
5d5fdb77e9 | ||
|
|
065c307e8e |
No files matched your search
@@ -11,13 +11,13 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to
|
||||
https://0x0.st/ or a similar site, and replace this text with a link to it.
|
||||
Make a log file made with -v -v or --log-file=output.txt, paste it to
|
||||
https://0x0.st/ or attach it to the github issue, and replace this text with a
|
||||
link to it. If the issue is a build failure, upload build/config.log instead.
|
||||
|
||||
Providing a log file is strongly encouraged. It is very helpful for reproducing
|
||||
bugs or getting important technical context. Without a log it might not be
|
||||
possible to analyze and fix certain bugs, and your bug report will be closed
|
||||
or ignored.
|
||||
If this is a normal runtime bug, and no log is provided, the issue will be
|
||||
closed for ignoring the issue template. This is because analyzing a bug without
|
||||
a log file is harder than necessary. Low quality bug reports are noise.
|
||||
|
||||
### Sample files
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.31.0 ---
|
||||
1.107 - Deprecate MPV_EVENT_TICK
|
||||
|
||||
--- mpv 0.30.0 ---
|
||||
1.106 - Add cancel_fn to mpv_stream_cb_info
|
||||
1.105 - Fix deadlock problems with MPV_RENDER_PARAM_ADVANCED_CONTROL and if
|
||||
|
||||
@@ -168,8 +168,12 @@ Or, compile and install both libmpv and mpv:
|
||||
Linking libmpv with MSVC programs
|
||||
---------------------------------
|
||||
|
||||
You can build C++ programs in Visual Studio and link them with libmpv. To do
|
||||
this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
mpv/libmpv cannot be built with Visual Studio (Microsoft is too incompetent to
|
||||
support C99/C11 properly and/or hates open source and Linux too much to
|
||||
seriously do it). But you can build C++ programs in Visual Studio and link them
|
||||
with a libmpv built with MinGW.
|
||||
|
||||
To do this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
and you need to create a import library for the mpv DLL:
|
||||
|
||||
```bash
|
||||
|
||||
+26
-11
@@ -41,7 +41,7 @@ Copyright of contributions
|
||||
behalf of your employer, and the employer owns the copyright, you must mention
|
||||
this. If the license of the code is not LGPLv2.1+, you must mention this.
|
||||
- These license statements are legally binding.
|
||||
- Don't use fake names (something that looks like an actual names, and may be
|
||||
- Don't use fake names (something that looks like an actual name, and may be
|
||||
someone else's name, but is not your legal name). Using a pseudonyms is
|
||||
allowed if it can be used to identify or contact you, even if whatever
|
||||
account you used to submit the patch dies.
|
||||
@@ -55,10 +55,10 @@ Write good commit messages
|
||||
- Write informative commit messages. Use present tense to describe the
|
||||
situation with the patch applied, and past tense for the situation before
|
||||
the change.
|
||||
- The subject line (the first line in a commit message) should contain a
|
||||
- The subject line (the first line in a commit message) must contain a
|
||||
prefix identifying the sub system, followed by a short description what
|
||||
impact this commit has. This subject line and the commit message body
|
||||
shouldn't be longer than 72 characters per line, because it messes up the
|
||||
must not be longer than 72 characters per line, because it messes up the
|
||||
output of many git tools otherwise.
|
||||
|
||||
For example, you fixed a crash in af_volume.c:
|
||||
@@ -68,7 +68,11 @@ Write good commit messages
|
||||
|
||||
Having a prefix gives context, and is especially useful when trying to find
|
||||
a specific change by looking at the history, or when running ``git blame``.
|
||||
- The body of the commit message (everything else after the subject line) should
|
||||
- The first word after the ``:`` is lower case.
|
||||
- Don't end the subject line with a ``.``.
|
||||
- Put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- The body of the commit message (everything else after the subject line) must
|
||||
be as informative as possible and contain everything that isn't obvious. Don't
|
||||
hesitate to dump as much information as you can - it doesn't cost you
|
||||
anything. Put some effort into it. If someone finds a bug months or years
|
||||
@@ -77,13 +81,10 @@ Write good commit messages
|
||||
information to test the original bug. The old bug might be reintroduced while
|
||||
fixing the new bug.
|
||||
|
||||
The commit message should be wrapped on 72 characters per line, because git
|
||||
The commit message must be wrapped on 72 characters per line, because git
|
||||
tools usually do not break text automatically. On the other hand, you do not
|
||||
need to break text that would be unnatural to break (like data for test cases,
|
||||
or long URLs).
|
||||
|
||||
Important: put an empty line between the subject line and the commit message.
|
||||
If this is missing, it will break display in common git tools.
|
||||
- Another summary of good conventions: https://chris.beams.io/posts/git-commit/
|
||||
|
||||
Split changes into multiple commits
|
||||
@@ -95,6 +96,9 @@ Split changes into multiple commits
|
||||
additional cosmetic changes in the same file you're working on. But don't do
|
||||
something like reformatting a whole file, and hiding an actual functional
|
||||
change in the same commit.
|
||||
- Splitting changes does _not_ mean that you should make them as fine-grained
|
||||
as possible. Commits should form logical steps in development. The way you
|
||||
split changes is important for code review and analyzing bugs.
|
||||
|
||||
Touching user-visible parts may require updating the mpv docs
|
||||
-------------------------------------------------------------
|
||||
@@ -120,7 +124,7 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
- Use the K&R indent style.
|
||||
- Use 4 spaces of indentation, never use tabs (except in Makefiles).
|
||||
- Add a single space between keywords and binary operators. There are some other
|
||||
cases where spaces should be added. Example:
|
||||
cases where spaces must be added. Example:
|
||||
|
||||
```C
|
||||
if ((a * b) > c) {
|
||||
@@ -153,7 +157,7 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
do_something();
|
||||
}
|
||||
```
|
||||
- If the if has an else branch, both branches should use braces, even if they're
|
||||
- If the if has an else branch, both branches must use braces, even if they're
|
||||
technically redundant.
|
||||
|
||||
Example:
|
||||
@@ -185,6 +189,17 @@ mpv uses C99 with K&R formatting, with some exceptions.
|
||||
- Remove any trailing whitespace.
|
||||
- Do not make stray whitespaces changes.
|
||||
|
||||
Header #include statement order
|
||||
-------------------------------
|
||||
|
||||
The order of ``#include`` statements in the source code is not very consistent.
|
||||
New code must follow the following conventions:
|
||||
|
||||
- Put standard includes (``#include <stdlib.h>`` etc.) on the top,
|
||||
- then after a blank line, add library includes (``#include <zlib.h>`` etc.)
|
||||
- then after a blank line, add internal includes (``#include "player/core.h"``)
|
||||
- sort them alphabetically within these sections
|
||||
|
||||
General coding
|
||||
--------------
|
||||
|
||||
@@ -193,7 +208,7 @@ General coding
|
||||
- Don't use non-standard language (such as GNU C-only features). In some cases
|
||||
they may be warranted, if they are optional (such as attributes enabling
|
||||
printf-like format string checks). "#pragma once" is allowed as an exception.
|
||||
But in general, standard C99 should be used.
|
||||
But in general, standard C99 must be used.
|
||||
- The same applies to libc functions. We have to be Windows-compatible too. Use
|
||||
functions guaranteed by C99 or POSIX only, unless your use is guarded by a
|
||||
configure check. There is some restricted use of C11 (ask on IRC for details).
|
||||
|
||||
@@ -254,6 +254,3 @@ header, the syntax is exactly the same. It's far more convenient to use ``;``
|
||||
instead of line breaks, but that is orthogonal.
|
||||
|
||||
Example: ``edl://f1.mkv,length=5,start=10;f2.mkv,30,20;f3.mkv``
|
||||
|
||||
As a quirks, mpv will accept arbitrary paths in EDLs originating from
|
||||
``edl://``, while ``.edl`` does not. This makes no sense.
|
||||
@@ -24,6 +24,64 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.31.0 ---
|
||||
- add `--resume-playback-check-mtime` to check consistent mtime when
|
||||
restoring playback state.
|
||||
- add `--d3d11-output-csp` to enable explicit selection of a D3D11
|
||||
swap chain color space.
|
||||
- the --sws- options and similar now affect vo_image and screenshot
|
||||
conversion (does not matter as much for vo_gpu, which does most of this
|
||||
with shaders)
|
||||
- add a builtin "sw-fast" profile, which restores performance settings
|
||||
for software video conversion. These were switched to higher quality since
|
||||
mpv 0.30.0 (related to the previous changelog entry). This affects video
|
||||
outputs like vo_x11 and vo_drm, and screenshots, but not much else.
|
||||
- deprecate --input-file (there are no plans to remove this short-term,
|
||||
but it will probably eventually go away)
|
||||
- deprecate --video-sync=display-adrop (might be removed if it's in the way;
|
||||
undeprecated or readded if it's not too much of a problem)
|
||||
- deprecate all input section commands (these will be changed/removed, as
|
||||
soon as mpv internals do not require them anymore)
|
||||
- remove deprecated --playlist-pos alias (use --playlist-start)
|
||||
- deprecate --display-fps, introduce --override-display-fps. The display-fps
|
||||
property now is unavailable if no VO exists (or the VO did not return a
|
||||
display FPS), instead of returning the option value in this case. The
|
||||
property will keep existing, but writing to it is deprecated.
|
||||
- the vf/af properties now do not reject the set value anymore, even if
|
||||
filter chain initialization fails. Instead, the vf/af options are always
|
||||
set to the user's value, even if it does not reflect the "runtime" vf/af
|
||||
chain.
|
||||
- the vid/aid/sid/secondary-sid properties (and their aliases: "audio",
|
||||
"video", "sub") will now allow setting any track ID; before this change,
|
||||
only IDs of actually existing tracks could be set (the restriction was
|
||||
active the MPV_EVENT_FILE_LOADED/"file-loaded" event was sent). Setting
|
||||
an ID for which no track exists is equivalent to disabling it. Note that
|
||||
setting the properties to non-existing tracks may report it as selected
|
||||
track for a small time window, until it's forced back to "no". The exact
|
||||
details how this is handled may change in the future.
|
||||
- remove old Apple Remote support, including --input-appleremote
|
||||
- add MediaPlayer support and remove the old Media Key event tap on macOS.
|
||||
this possibly also re-adds the Apple Remote support
|
||||
- the "edition" property now strictly returns the value of the option,
|
||||
instead of the runtime value. The new "current-edition" property needs to
|
||||
be queried to read the runtime-chosen edition. This is a breaking change
|
||||
for any users which expected "edition" to return the runtime-chosen
|
||||
edition at default settings (--edition=auto).
|
||||
- the "window-scale" property now strictly returns the value of the option,
|
||||
instead of the actual size of the window. The new "current-window-scale"
|
||||
property needs to be queried to read the value as indicated by the current
|
||||
window size. This is a breaking change.
|
||||
- explicitly deprecate passing more than 1 item to "-add" suffix in key/value
|
||||
options (for example --script-opts-add). This was actually always
|
||||
deprecated, like with other list options, but the option parser did not
|
||||
print a warning in this particular case.
|
||||
- deprecate -del for list options (use -remove instead, which is by content
|
||||
instead of by integer index)
|
||||
- if `--fs` is used but `--fs-screen` is not set, mpv will now use `--screen`
|
||||
instead.
|
||||
- change the default of --hwdec to "no" on RPI. The default used to be "mmal"
|
||||
specifically if 'Raspberry Pi support' was enabled at configure time
|
||||
(equivalent to --enable-rpi). Use --hwdec=mmal to get the old behavior.
|
||||
--- mpv 0.30.0 ---
|
||||
- add `--d3d11-output-format` to enable explicit selection of a D3D11
|
||||
swap chain format.
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
CONSOLE
|
||||
=======
|
||||
|
||||
The console is a REPL for mpv input commands. It is displayed on the video
|
||||
window. It also shows log messages. It can be disabled entirely using the
|
||||
``--load-osd-console=no`` option.
|
||||
|
||||
Keybindings
|
||||
-----------
|
||||
|
||||
\`
|
||||
Show the console.
|
||||
|
||||
ESC
|
||||
Hide the console.
|
||||
|
||||
ENTER
|
||||
Run the typed command.
|
||||
|
||||
Shift+ENTER
|
||||
Type a literal newline character.
|
||||
|
||||
Ctrl+LEFT and Ctrl+RIGHT
|
||||
Move cursor to previous/next word.
|
||||
|
||||
UP and DOWN
|
||||
Navigate command history.
|
||||
|
||||
PGUP
|
||||
Go to the first command in the history.
|
||||
|
||||
PGDN
|
||||
Stop navigating command history.
|
||||
|
||||
INSERT
|
||||
Toggle insert mode.
|
||||
|
||||
Shift+INSERT
|
||||
Paste text (uses the primary selection on X11.)
|
||||
|
||||
TAB
|
||||
Complete the command or property name at the cursor.
|
||||
|
||||
Ctrl+C
|
||||
Clear current line.
|
||||
|
||||
Ctrl+K.
|
||||
Delete text from the cursor to the end of the line.
|
||||
|
||||
Ctrl+L
|
||||
Clear all log messages from the console.
|
||||
|
||||
Ctrl+U
|
||||
Delete text from the cursor to the beginning of the line.
|
||||
|
||||
Ctrl+V
|
||||
Paste text (uses the clipboard on X11.)
|
||||
|
||||
Ctrl+W
|
||||
Delete text from the cursor to the beginning of the current word.
|
||||
|
||||
Commands
|
||||
--------
|
||||
|
||||
``script-message-to console type <text>``
|
||||
Show the console and pre-fill it with the provided text.
|
||||
|
||||
Known issues
|
||||
------------
|
||||
|
||||
- Pasting text is slow on Windows
|
||||
- Non-ASCII keyboard input has restrictions
|
||||
- The cursor keys move between Unicode code-points, not grapheme clusters
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``script-opts/console.conf``
|
||||
placed in mpv's user directory and through the ``--script-opts`` command-line
|
||||
option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
|
||||
|
||||
Key bindings can be changed in a standard way, see for example stats.lua
|
||||
documentation.
|
||||
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``scale``
|
||||
Default: 1
|
||||
|
||||
All drawing is scaled by this value, including the text borders and the
|
||||
cursor.
|
||||
|
||||
If the VO backend in use has HiDPI scale reporting implemented, the option
|
||||
value is scaled with the reported HiDPI scale.
|
||||
|
||||
``font``
|
||||
Default: unset (picks a hardcoded font depending on detected platform)
|
||||
|
||||
Set the font used for the REPL and the console. This probably doesn't
|
||||
have to be a monospaced font.
|
||||
|
||||
``font_size``
|
||||
Default: 16
|
||||
|
||||
Set the font size used for the REPL and the console. This will be
|
||||
multiplied by "scale."
|
||||
+7
-6
@@ -15,8 +15,7 @@ You can encode files from one format/codec to another using this facility.
|
||||
Specifies the output format options for libavformat.
|
||||
See ``--ofopts=help`` for a full list of supported options.
|
||||
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
``--ofopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
@@ -41,8 +40,7 @@ You can encode files from one format/codec to another using this facility.
|
||||
"``--oac=libmp3lame --oacopts=b=128000``"
|
||||
selects 128 kbps MP3 encoding.
|
||||
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
``--oacopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
@@ -74,8 +72,7 @@ You can encode files from one format/codec to another using this facility.
|
||||
``"--ovc=libx264 --ovcopts=crf=23"``
|
||||
selects VBR quality factor 23 for H.264 encoding.
|
||||
|
||||
Options are managed in lists. There are a few commands to manage the
|
||||
options list.
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
``--ovcopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
@@ -103,6 +100,8 @@ You can encode files from one format/codec to another using this facility.
|
||||
FLAC allow almost arbitrary keys, while support in MP4 and MP3 is more
|
||||
limited.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oset-metadata=title="Output title",comment="Another tag"``"
|
||||
@@ -112,6 +111,8 @@ You can encode files from one format/codec to another using this facility.
|
||||
Specifies metadata to exclude from the output file when copying from the
|
||||
input file.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oremove-metadata=comment,genre``"
|
||||
|
||||
+303
-42
@@ -41,6 +41,8 @@ commands they're bound to on the OSD, instead of executing the commands::
|
||||
(Only closing the window will make **mpv** exit, pressing normal keys will
|
||||
merely display the binding, even if mapped to quit.)
|
||||
|
||||
Also see `Key names`_.
|
||||
|
||||
input.conf syntax
|
||||
-----------------
|
||||
|
||||
@@ -78,6 +80,80 @@ that matches, and the multi-key command will never be called. Intermediate keys
|
||||
can be remapped to ``ignore`` in order to avoid this issue. The maximum number
|
||||
of (non-modifier) keys for combinations is currently 4.
|
||||
|
||||
Key names
|
||||
---------
|
||||
|
||||
All mouse and keyboard input is to converted to mpv-specific key names. Key
|
||||
names are either special symbolic identifiers representing a physical key, or a
|
||||
text key names, which are unicode code points encoded as UTF-8. These are what
|
||||
keyboard input would normally produce, for example ``a`` for the A key. As a
|
||||
consequence, mpv uses input translated by the current OS keyboard layout, rather
|
||||
than physical scan codes.
|
||||
|
||||
Currently there is the hardcoded assumption that every text key can be
|
||||
represented as a single unicode code point (in NFKC form).
|
||||
|
||||
All key names can be combined with the modifiers ``Shift``, ``Ctrl``, ``Alt``,
|
||||
``Meta``. They must be prefixed to the actual key name, where each modifier
|
||||
is followed by a ``+`` (for example ``ctrl+q``).
|
||||
|
||||
Symbolic key names and modifier names are case-insensitive. Unicode key names
|
||||
are case-sensitive because input bindings typically respect the shift key.
|
||||
|
||||
Another type of key names are hexadecimal key names, that serve as fallback
|
||||
for special keys that are neither unicode, nor have a special mpv defined name.
|
||||
They will break as soon as mpv adds proper names for them, but can enable you
|
||||
to use a key at all if that does not happen.
|
||||
|
||||
All symbolic names are listed by ``--input-keylist``. ``--input-test`` is a
|
||||
special mode that prints all input on the OSD.
|
||||
|
||||
Comments on some symbolic names:
|
||||
|
||||
``KP*``
|
||||
Keypad names. Behavior varies by backend (whether they implement this, and
|
||||
on how they treat numlock), but typically, mpv tries to map keys on the
|
||||
keypad to separate names, even if they produce the same text as normal keys.
|
||||
|
||||
``MOUSE_BTN*``, ``MBTN*``
|
||||
Various mouse buttons.
|
||||
|
||||
Depending on backend, the mouse wheel might also be represented as a button.
|
||||
In addition, ``MOUSE_BTN3`` to ``MOUSE_BTN6`` are deprecated aliases for
|
||||
``WHEEL_UP``, ``WHEEL_DOWN``, ``WHEEL_LEFT``, ``WHEEL_RIGHT``.
|
||||
|
||||
``MBTN*`` are aliases for ``MOUSE_BTN*``.
|
||||
|
||||
``WHEEL_*``
|
||||
Mouse wheels (typically).
|
||||
|
||||
``AXIS_*``
|
||||
Deprecated aliases for ``WHEEL_*``.
|
||||
|
||||
``*_DBL``
|
||||
Mouse button double clicks.
|
||||
|
||||
``MOUSE_MOVE``, ``MOUSE_ENTER``, ``MOUSE_LEAVE``
|
||||
Emitted by mouse move events. Enter/leave happens when the mouse enters or
|
||||
leave the mpv window (or the current mouse region, using the deprecated
|
||||
mouse region input section mechanism).
|
||||
|
||||
``CLOSE_WIN``
|
||||
Pseudo key emitted when closing the mpv window using the OS window manager
|
||||
(for example, by clicking the close button in the window title bar).
|
||||
|
||||
``GAMEPAD_*``
|
||||
Keys emitted by the SDL gamepad backend.
|
||||
|
||||
``UNMAPPED``
|
||||
Pseudo-key that matches any unmapped key. (You should probably avoid this
|
||||
if possible, because it might change behavior or get removed in the future.)
|
||||
|
||||
``ANY_UNICODE``
|
||||
Pseudo-key that matches any key that produces text. (You should probably
|
||||
avoid this if possible, because it might change behavior or get removed in
|
||||
the future.)
|
||||
|
||||
Flat command syntax
|
||||
-------------------
|
||||
|
||||
@@ -649,6 +725,10 @@ Input Commands that are Possibly Subject to Change
|
||||
``vf <operation> <value>``
|
||||
Change video filter chain.
|
||||
|
||||
The semantics are exactly the same as with option parsing (see
|
||||
`VIDEO FILTERS`_). As such the text below is a redundant and incomplete
|
||||
summary.
|
||||
|
||||
The first argument decides what happens:
|
||||
|
||||
<set>
|
||||
@@ -672,7 +752,7 @@ Input Commands that are Possibly Subject to Change
|
||||
cases, the second parameter is a comma separated list of filter names
|
||||
or integer indexes. ``0`` would denote the first filter. Negative
|
||||
indexes start from the last filter, and ``-1`` denotes the last
|
||||
filter.
|
||||
filter. Deprecated.
|
||||
|
||||
<clr>
|
||||
Remove all filters. Note that like the other sub-commands, this does
|
||||
@@ -728,6 +808,8 @@ Input Commands that are Possibly Subject to Change
|
||||
list yourself when adding a second key binding for cycling backwards.
|
||||
|
||||
``enable-section <name> [<flags>]``
|
||||
This command is deprecated, except for mpv-internal uses.
|
||||
|
||||
Enable all key bindings in the named input section.
|
||||
|
||||
The enabled input sections form a stack. Bindings in sections on the top of
|
||||
@@ -750,9 +832,13 @@ Input Commands that are Possibly Subject to Change
|
||||
Same.
|
||||
|
||||
``disable-section <name>``
|
||||
This command is deprecated, except for mpv-internal uses.
|
||||
|
||||
Disable the named input section. Undoes ``enable-section``.
|
||||
|
||||
``define-section <name> <contents> [<flags>]``
|
||||
This command is deprecated, except for mpv-internal uses.
|
||||
|
||||
Create a named input section, or replace the contents of an already existing
|
||||
input section. The ``contents`` parameter uses the same syntax as the
|
||||
``input.conf`` file (except that using the section syntax in it is not
|
||||
@@ -851,6 +937,86 @@ Input Commands that are Possibly Subject to Change
|
||||
Remove an overlay added with ``overlay-add`` and the same ID. Does nothing
|
||||
if no overlay with this ID exists.
|
||||
|
||||
``osd-overlay``
|
||||
Add/update/remove an OSD overlay.
|
||||
|
||||
(Although this sounds similar to ``overlay-add``, ``osd-overlay`` is for
|
||||
text overlays, while ``overlay-add`` is for bitmaps. Maybe ``overlay-add``
|
||||
will be merged into ``osd-overlay`` to remove this oddity.)
|
||||
|
||||
You can use this to add text overlays in ASS format. ASS has advanced
|
||||
positioning and rendering tags, which can be used to render almost any kind
|
||||
of vector graphics.
|
||||
|
||||
This command accepts the following parameters:
|
||||
|
||||
``id``
|
||||
Arbitrary integer that identifies the overlay. Multiple overlays can be
|
||||
added by calling this command with different ``id`` parameters. Calling
|
||||
this command with the same ``id`` replaces the previously set overlay.
|
||||
|
||||
There is a separate namespace for each libmpv client (i.e. IPC
|
||||
connection, script), so IDs can be made up and assigned by the API user
|
||||
without conflicting with other API users.
|
||||
|
||||
If the libmpv client is destroyed, all overlays associated with it are
|
||||
also deleted. In particular, connecting via ``--input-ipc-server``,
|
||||
adding an overlay, and disconnecting will remove the overlay immediately
|
||||
again.
|
||||
|
||||
``format``
|
||||
String that gives the type of the overlay. Accepts the following values:
|
||||
|
||||
``ass-events``
|
||||
The ``data`` parameter is a string. The string is split on the
|
||||
newline character. Every line is turned into the ``Text`` part of
|
||||
a ``Dialogue`` ASS event. Timing is unused (but behavior of timing
|
||||
dependent ASS tags may change in future mpv versions).
|
||||
|
||||
Note that it's better to put multiple lines into ``data``, instead
|
||||
of adding multiple OSD overlays.
|
||||
|
||||
This provides 2 ASS ``Styles``. ``OSD`` contains the text style as
|
||||
defined by the current ``--osd-...`` options. ``Default`` is
|
||||
similar, and contains style that ``OSD`` would have if all options
|
||||
were set to the default.
|
||||
|
||||
In addition, the ``res_x`` and ``res_y`` options specify the value
|
||||
of the ASS ``PlayResX`` and ``PlayResY`` header fields. If ``res_y``
|
||||
is set to 0, ``PlayResY`` is initialized to an arbitrary default
|
||||
value (but note that the default for this command is 720, not 0).
|
||||
If ``res_x`` is set to 0, ``PlayResX`` is set based on ``res_y``
|
||||
such that a virtual ASS pixel has a square pixel aspect ratio.
|
||||
|
||||
``none``
|
||||
Special value that causes the overlay to be removed. Most parameters
|
||||
other than ``id`` and ``format`` are mostly ignored.
|
||||
|
||||
``data``
|
||||
String defining the overlay contents according to the ``format``
|
||||
parameter.
|
||||
|
||||
``res_x``, ``res_y``
|
||||
Used if ``format`` is set to ``ass-events`` (see description there).
|
||||
Optional, defaults to 0/720.
|
||||
|
||||
``z``
|
||||
The Z order of the overlay. Optional, defaults to 0.
|
||||
|
||||
Note that Z order between different overlays of different formats is
|
||||
static, and cannot be changed (currently, this means that bitmap
|
||||
overlays added by ``overlay-add`` are always on top of the ASS overlays
|
||||
added by ``osd-overlay``). In addition, the builtin OSD components are
|
||||
always below any of the custom OSD. (This includes subtitles of any kind
|
||||
as well as text rendered by ``show-text``.)
|
||||
|
||||
It's possible that future mpv versions will randomly change how Z order
|
||||
between different OSD formats and builtin OSD is handled.
|
||||
|
||||
Note: always use named arguments (``mpv_command_node()``). Scripts should
|
||||
use the ``mp.create_osd_overlay()`` helper instead of invoking this command
|
||||
directly.
|
||||
|
||||
``script-message [<arg1> [<arg2> [...]]]``
|
||||
Send a message to all clients, and pass it the following list of arguments.
|
||||
What this message means, how many arguments it takes, and what the arguments
|
||||
@@ -886,8 +1052,13 @@ Input Commands that are Possibly Subject to Change
|
||||
2. The name of the binding (as established above).
|
||||
3. The key state as string (see below).
|
||||
4. The key name (since mpv 0.15.0).
|
||||
5. The text the key would produce, or empty string if not applicable.
|
||||
|
||||
The key state consists of 2 letters:
|
||||
The 5th argument is only set if no modifiers are present (using the shift
|
||||
key with a letter is normally not emitted as having a modifier, and results
|
||||
in upper case text instead, but some backends may mess up).
|
||||
|
||||
The key state consists of 2 characters:
|
||||
|
||||
1. One of ``d`` (key was pressed down), ``u`` (was released), ``r`` (key
|
||||
is still down, and was repeated; only if key repeat is enabled for this
|
||||
@@ -895,6 +1066,9 @@ Input Commands that are Possibly Subject to Change
|
||||
2. Whether the event originates from the mouse, either ``m`` (mouse button)
|
||||
or ``-`` (something else).
|
||||
|
||||
Future versions can add more arguments and more key state characters to
|
||||
support more input peculiarities.
|
||||
|
||||
``ab-loop``
|
||||
Cycle through A-B loop states. The first command will set the ``A`` point
|
||||
(the ``ab-loop-a`` property); the second the ``B`` point, and the third
|
||||
@@ -1435,6 +1609,18 @@ Property list
|
||||
Current MKV edition number. Setting this property to a different value will
|
||||
restart playback. The number of the first edition is 0.
|
||||
|
||||
Before mpv 0.31.0, this showed the actual edition selected at runtime, if
|
||||
you didn't set the option or property manually. With mpv 0.31.0 and later,
|
||||
this strictly returns the user-set option or property value, and the
|
||||
``current-edition`` property was added to return the runtime selected
|
||||
edition (this matters with ``--edition=auto``, the default).
|
||||
|
||||
``current-edition``
|
||||
Currently selected edition. This property is unavailable if no file is
|
||||
loaded, or the file has no editions. (Matroska files make a difference
|
||||
between having no editions and a single edition, which will be reflected by
|
||||
the property, although in practice it does not matter.)
|
||||
|
||||
``chapters``
|
||||
Number of chapters.
|
||||
|
||||
@@ -1924,8 +2110,19 @@ Property list
|
||||
(or to be exact, the size the video filters output). ``2`` will set the
|
||||
double size, ``0.5`` halves the size.
|
||||
|
||||
``window-minimized``
|
||||
Return whether the video window is minimized or not.
|
||||
See ``current-window-scale`` for the value derived from the actual window
|
||||
size.
|
||||
|
||||
Since mpv 0.31.0, this always returns the previously set value (or the
|
||||
default value), instead of the value implied by the actual window size.
|
||||
Before mpv 0.31.0, this returned what ``current-window-scale`` returns now,
|
||||
after the window was created.
|
||||
|
||||
``current-window-scale``
|
||||
The ``window-scale`` value calculated from the current window size. This
|
||||
has the same value as ``window-scale`` if the window size was not changed
|
||||
since setting the option, and the window size was not restricted in other
|
||||
ways. The property is unavailable if no video is active.
|
||||
|
||||
``display-names``
|
||||
Names of the displays that the mpv window covers. On X11, these
|
||||
@@ -1936,13 +2133,18 @@ Property list
|
||||
Display Product Names as used in the System Information and only one display
|
||||
name is returned since a window can only be on one screen.
|
||||
|
||||
``display-fps`` (RW)
|
||||
``display-fps``
|
||||
The refresh rate of the current display. Currently, this is the lowest FPS
|
||||
of any display covered by the video, as retrieved by the underlying system
|
||||
APIs (e.g. xrandr on X11). It is not the measured FPS. It's not necessarily
|
||||
available on all platforms. Note that any of the listed facts may change
|
||||
any time without a warning.
|
||||
|
||||
Writing to this property is deprecated. It has the same effect as writing to
|
||||
``override-display-fps``. Since mpv 0.31.0, this property is unavailable
|
||||
if no display FPS was reported (e.g. if no video is active), while in older
|
||||
versions, it returned the ``--display-fps`` option value.
|
||||
|
||||
``estimated-display-fps``
|
||||
Only available if display-sync mode (as selected by ``--video-sync``) is
|
||||
active. Returns the actual rate at which display refreshes seem to occur,
|
||||
@@ -1951,6 +2153,12 @@ Property list
|
||||
``vsync-jitter``
|
||||
Estimated deviation factor of the vsync duration.
|
||||
|
||||
``display-hidpi-scale``
|
||||
The HiDPI scale factor as reported by the windowing backend. If no VO is
|
||||
active, or if the VO does not report a value, this property is unavailable.
|
||||
It may be saner to report an absolute DPI, however, this is the way HiDPI
|
||||
support is implemented on most OS APIs. See also ``--hidpi-window-scale``.
|
||||
|
||||
``video-aspect`` (RW)
|
||||
Deprecated. This is tied to ``--video-aspect-override``, but always
|
||||
reports the current video aspect if video is active.
|
||||
@@ -2050,7 +2258,11 @@ Property list
|
||||
String describing the media type. One of ``audio``, ``video``, ``sub``.
|
||||
|
||||
``track-list/N/src-id``
|
||||
Track ID as used in the source file. Not always available.
|
||||
Track ID as used in the source file. Not always available. (It is
|
||||
missing if the format has no native ID, if the track is a pseudo-track
|
||||
that does not exist in this way in the actual file, or if the format
|
||||
is handled by libavformat, and the format was not whitelisted as having
|
||||
track IDs.)
|
||||
|
||||
``track-list/N/title``
|
||||
Track title as it is stored in the file. Not always available.
|
||||
@@ -2407,6 +2619,28 @@ Property list
|
||||
``current-ao``
|
||||
Current audio output driver (name as used with ``--ao``).
|
||||
|
||||
``shared-script-properties`` (RW)
|
||||
This is a key/value map of arbitrary strings shared between scripts for
|
||||
general use. The player itself does not use any data in it (although some
|
||||
builtin scripts may). The property is not preserved across player restarts.
|
||||
|
||||
This is very primitive, inefficient, and annoying to use. It's a makeshift
|
||||
solution which could go away any time (for example, when a better solution
|
||||
becomes available). This is also why this property has an annoying name. You
|
||||
should avoid using it, unless you absolutely have to.
|
||||
|
||||
Lua scripting has helpers starting with ``utils.shared_script_property_``.
|
||||
They are undocumented because you should not use this property. If you still
|
||||
think you must, you should use the helpers instead of the property directly.
|
||||
|
||||
You are supposed to use the ``change-list`` command to modify the contents.
|
||||
Reading, modifying, and writing the property manually could data loss if two
|
||||
scripts update different keys at the same time due to lack of
|
||||
synchronization. The Lua helpers take care of this.
|
||||
|
||||
(There is no way to ensure synchronization if two scripts try to update the
|
||||
same key at the same time.)
|
||||
|
||||
``working-directory``
|
||||
Return the working directory of the mpv process. Can be useful for JSON IPC
|
||||
users, because the command line player usually works with relative paths.
|
||||
@@ -2540,6 +2774,56 @@ Property list
|
||||
is not a map, as order matters and duplicate entries are possible. Recursive
|
||||
profiles are not expanded, and show up as special ``profile`` options.
|
||||
|
||||
``command-list``
|
||||
Return the list of input commands. This returns an array of maps, where
|
||||
each map node represents a command. This map currently only has a single
|
||||
entry: ``name`` for the name of the command. (This property is supposed to
|
||||
be a replacement for ``--input-cmdlist``. The option dumps some more
|
||||
information, but it's a valid feature request to extend this property if
|
||||
needed.)
|
||||
|
||||
``input-bindings``
|
||||
Return list of current input key bindings. This returns an array of maps,
|
||||
where each map node represents a binding for a single key/command. This map
|
||||
has the following entries:
|
||||
|
||||
``key``
|
||||
The key name. This is normalized and may look slightly different from
|
||||
how it was specified in the source (e.g. in input.conf).
|
||||
|
||||
``cmd``
|
||||
The command mapped to the key. (Currently, this is exactly the same
|
||||
string as specified in the source, other than stripping whitespace and
|
||||
comments. It's possible that it will be normalized in the future.)
|
||||
|
||||
``is_weak``
|
||||
If set to true, any existing and active user bindings will take priority.
|
||||
|
||||
``owner``
|
||||
If this entry exists, the name of the script (or similar) which added
|
||||
this binding.
|
||||
|
||||
``section``
|
||||
Name of the section this binding is part of. This is a rarely used
|
||||
mechanism. This entry may be removed or change meaning in the future.
|
||||
|
||||
``priority``
|
||||
A number. Bindings with a higher value are preferred over bindings
|
||||
with a lower value. If the value is negative, this binding is inactive
|
||||
and will not be triggered by input. Note that mpv does not use this
|
||||
value internally, and matching of bindings may work slightly differently
|
||||
in some cases. In addition, this value is dynamic and can change around
|
||||
at runtime.
|
||||
|
||||
``comment``
|
||||
If available, the comment following the command on the same line. (For
|
||||
example, the input.conf entry ``f cycle bla # toggle bla`` would
|
||||
result in an entry with ``comment = "toggle bla", cmd = "cycle bla"``.)
|
||||
|
||||
This property is read-only, and change notification is not supported.
|
||||
Currently, there is no mechanism to change key bindings at runtime, other
|
||||
than scripts adding or removing their own bindings.
|
||||
|
||||
Inconsistencies between options and properties
|
||||
----------------------------------------------
|
||||
|
||||
@@ -2547,55 +2831,32 @@ You can access (almost) all options as properties, though there are some
|
||||
caveats with some properties (due to historical reasons):
|
||||
|
||||
``vid``, ``aid``, ``sid``
|
||||
While playback is active, you can set existing tracks only. (The option
|
||||
allows setting any track ID, and which tracks to enable is chosen at
|
||||
loading time.)
|
||||
While playback is active, these result the actually active tracks. For
|
||||
example, if you set ``aid=5``, and the currently played file contains no
|
||||
audio track with ID 5, the ``aid`` property will return ``no``.
|
||||
|
||||
Option changes at runtime are affected by this as well.
|
||||
Before mpv 0.31.0, you could set existing tracks at runtime only.
|
||||
|
||||
``display-fps``
|
||||
If a VO is created, this will return either the actual display FPS, or
|
||||
an invalid value, instead of the option value.
|
||||
This inconsistent behavior is deprecated. Post-deprecation, the reported
|
||||
value and the option value are cleanly separated (``override-display-fps``
|
||||
for the option value).
|
||||
|
||||
``vf``, ``af``
|
||||
If you set the properties during playback, and the filter chain fails to
|
||||
reinitialize, the new value will be rejected. Setting the option or
|
||||
setting the property outside of playback will always succeed/fail in the
|
||||
same way. Also, there are no ``vf-add`` etc. properties, but you can use
|
||||
the ``vf``/``af`` group of commands to achieve the same.
|
||||
reinitialize, the option will be set, but the runtime filter chain does not
|
||||
change. On the other hand, the next video to be played will fail, because
|
||||
the initial filter chain cannot be created.
|
||||
|
||||
Option changes at runtime are affected by this as well.
|
||||
|
||||
``edition``
|
||||
While a file is loaded, the property will always return the effective
|
||||
edition, and setting the ``auto`` value will show somewhat strange behavior
|
||||
(the property eventually switching to whatever is the default edition).
|
||||
This behavior changed in mpv 0.31.0. Before this, the new value was rejected
|
||||
*iff* video (for ``vf``) or audio (for ``af``) was active. If playback was
|
||||
not active, the behavior was the same as the current behavior.
|
||||
|
||||
``playlist``
|
||||
The property is read-only and returns the current internal playlist. The
|
||||
option is for loading playlist during command line parsing. For client API
|
||||
uses, you should use the ``loadlist`` command instead.
|
||||
|
||||
``window-scale``
|
||||
Might verify the set value when setting while a window is created.
|
||||
|
||||
``audio-file``, ``sub-file``, ``external-file``
|
||||
These options/properties are actually lists of filenames. To make the
|
||||
command-line interface easier, each ``--audio-file=...`` option appends
|
||||
the full string to the internal list. However, when used as properties,
|
||||
every time you set the property as a string the internal list will be
|
||||
replaced with a single entry containing the string you set. ``,`` or other
|
||||
separators are never used. You have to use ``MPV_FORMAT_NODE_ARRAY`` (or
|
||||
corresponding API, e.g. ``mp.set_property_native()`` with a table in Lua)
|
||||
to set multiple entries.
|
||||
|
||||
Strictly speaking, option access via API (e.g. ``mpv_set_option_string()``)
|
||||
has the same problem, and it's only a difference between CLI/API.
|
||||
|
||||
``playlist-pos``, ``chapter``
|
||||
These properties behave different from the deprecated options with the same
|
||||
names.
|
||||
|
||||
``profile``, ``include``
|
||||
These are write-only, and will perform actions as they are written to,
|
||||
exactly as if they were used on the mpv CLI commandline. Their only use is
|
||||
|
||||
+24
-8
@@ -153,6 +153,10 @@ success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.unregister_script_message(name)``
|
||||
|
||||
``mp.create_osd_overlay(format)``
|
||||
|
||||
``mp.get_osd_size()`` (returned object has properties: width, height, aspect)
|
||||
|
||||
``mp.msg.log(level, ...)``
|
||||
|
||||
``mp.msg.fatal(...)``
|
||||
@@ -187,7 +191,8 @@ success, ``fn`` is called always a-sync, ``error`` is empty string on success.
|
||||
|
||||
``mp.add_hook(type, priority, fn)``
|
||||
|
||||
``mp.options.read_options(obj [, identifier])`` (types: string/boolean/number)
|
||||
``mp.options.read_options(obj [, identifier [, on_update]])`` (types:
|
||||
string/boolean/number)
|
||||
|
||||
Additional utilities
|
||||
--------------------
|
||||
@@ -240,6 +245,9 @@ Note: ``read_file`` and ``write_file`` throw on errors, allow text content only.
|
||||
anything from the filesystem), and returns it as a function. Very similar
|
||||
to a ``Function`` constructor, but shows at stack traces as ``fname``.
|
||||
|
||||
``mp.module_paths``
|
||||
Global modules search paths array for the ``require`` function (see below).
|
||||
|
||||
Timers (global)
|
||||
---------------
|
||||
|
||||
@@ -278,9 +286,11 @@ CommonJS modules and ``require(id)``
|
||||
------------------------------------
|
||||
|
||||
CommonJS Modules are a standard system where scripts can export common functions
|
||||
for use by other scripts. A module is a script which adds properties (functions,
|
||||
etc) to its invisible ``exports`` object, which another script can access by
|
||||
loading it with ``require(module-id)`` - which returns that ``exports`` object.
|
||||
for use by other scripts. Specifically, a module is a script which adds
|
||||
properties (functions, etc) to its pre-existing ``exports`` object, which
|
||||
another script can access with ``require(module-id)``. This runs the module and
|
||||
returns its ``exports`` object. Further calls to ``require`` for the same module
|
||||
will return its cached ``exports`` object without running the module again.
|
||||
|
||||
Modules and ``require`` are supported, standard compliant, and generally similar
|
||||
to node.js. However, most node.js modules won't run due to missing modules such
|
||||
@@ -293,10 +303,16 @@ will load the file ``./foo.js`` and return its ``exports`` object.
|
||||
An id is relative (to the script which ``require``'d it) if it starts with
|
||||
``./`` or ``../``. Otherwise, it's considered a "top-level id" (CommonJS term).
|
||||
|
||||
Top level id is evaluated as absolute filesystem path if possible (e.g. ``/x/y``
|
||||
or ``~/x``). Otherwise, it's searched at ``scripts/modules.js/`` in mpv config
|
||||
dirs - in normal config search order. E.g. ``require("x")`` is searched as file
|
||||
``x.js`` at those dirs, and id ``foo/x`` is searched as file ``foo/x.js``.
|
||||
Top level id is evaluated as absolute filesystem path if possible, e.g. ``/x/y``
|
||||
or ``~/x``. Otherwise, it's considered a global module id and searched at
|
||||
``scripts/modules.js/`` in mpv config dirs - in normal config search order. E.g.
|
||||
``require("x")`` is searched as file ``x.js`` at those dirs, and id ``foo/x`` is
|
||||
searched as file ``x.js`` inside dir ``foo`` at those dirs.
|
||||
|
||||
Search paths for global module id's are at the array ``mp.module_paths``, which
|
||||
is searched in order. Initially it contains one item: ``~~/scripts/modules.js``
|
||||
such that it behaves as described above. Modifying it will affect future
|
||||
``require`` calls with global module id's which are not already loaded/cached.
|
||||
|
||||
No ``global`` variable, but a module's ``this`` at its top lexical scope is the
|
||||
global object - also in strict mode. If you have a module which needs ``global``
|
||||
|
||||
+75
-8
@@ -240,7 +240,9 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
an example). The name should be unique across other bindings in the same
|
||||
script - if not, the previous binding with the same name will be
|
||||
overwritten. You can omit the name, in which case a random name is generated
|
||||
internally.
|
||||
internally. (Omitting works as follows: either pass ``nil`` for ``name``,
|
||||
or pass the ``fn`` argument in place of the name. The latter is not
|
||||
recommended and is handled for compatibility only.)
|
||||
|
||||
The last argument is used for optional flags. This is a table, which can
|
||||
have the following entries:
|
||||
@@ -251,11 +253,24 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
``complex``
|
||||
If set to ``true``, then ``fn`` is called on both key up and down
|
||||
events (as well as key repeat, if enabled), with the first
|
||||
argument being a table. This table has an ``event`` entry, which
|
||||
is set to one of the strings ``down``, ``repeat``, ``up`` or
|
||||
``press`` (the latter if key up/down can't be tracked). It further
|
||||
has an ``is_mouse`` entry, which tells whether the event was caused
|
||||
by a mouse button.
|
||||
argument being a table. This table has the following entries (and
|
||||
may contain undocumented ones):
|
||||
|
||||
``event``
|
||||
Set to one of the strings ``down``, ``repeat``, ``up`` or
|
||||
``press`` (the latter if key up/down can't be tracked).
|
||||
|
||||
``is_mouse``
|
||||
Boolean Whether the event was caused by a mouse button.
|
||||
|
||||
``key_name``
|
||||
The name of they key that triggered this, or ``nil`` if invoked
|
||||
artificially. If the key name is unknown, it's an empty string.
|
||||
|
||||
``key_text``
|
||||
Text if triggered by a text key, otherwise ``nil``. See
|
||||
description of ``script-binding`` command for details (this
|
||||
field is equivalent to the 5th argument).
|
||||
|
||||
Internally, key bindings are dispatched via the ``script-message-to`` or
|
||||
``script-binding`` input commands and ``mp.register_script_message``.
|
||||
@@ -343,6 +358,10 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
of times in a row, only the last change triggers the change function. (The
|
||||
exact behavior depends on timing and other things.)
|
||||
|
||||
If a property is unavailable, or on error, the value argument to ``fn`` is
|
||||
``nil``. (The ``observe_property()`` call always succeeds, even if a
|
||||
property does not exist.)
|
||||
|
||||
In some cases the function is not called even if the property changes.
|
||||
This depends on the property, and it's a valid feature request to ask for
|
||||
better update handling of a specific property.
|
||||
@@ -517,6 +536,41 @@ are useful only in special situations.
|
||||
Undo a previous registration with ``mp.register_script_message``. Does
|
||||
nothing if the ``name`` wasn't registered.
|
||||
|
||||
``mp.create_osd_overlay(format)``
|
||||
Create an OSD overlay. This is a very thin wrapper around the ``osd-overlay``
|
||||
command. The function returns a table, which mostly contains fields that
|
||||
will be passed to ``osd-overlay``. The ``format`` parameter is used to
|
||||
initialize the ``format`` field. The ``data`` field contains the text to
|
||||
be used as overlay. For details, see the ``osd-overlay`` command.
|
||||
|
||||
In addition, it provides the following methods:
|
||||
|
||||
``update()``
|
||||
Commit the OSD overlay to the screen, or in other words, run the
|
||||
``osd-overlay`` command with the current fields of the overlay table.
|
||||
|
||||
``remove()``
|
||||
Remove the overlay from the screen. A ``update()`` call will add it
|
||||
again.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
ov = mp.create_osd_overlay("ass-events")
|
||||
ov.data = "{\\an5}{\\b1}hello world!"
|
||||
ov:update()
|
||||
|
||||
The advantage of using this wrapper (as opposed to running ``osd-overlay``
|
||||
directly) is that the ``id`` field is allocated automatically.
|
||||
|
||||
``mp.get_osd_size()``
|
||||
Returns a tuple of ``osd_width, osd_height, osd_par``. The first two give
|
||||
the size of the OSD in pixels (for video ouputs like ``--vo=xv``, this may
|
||||
be "scaled" pixels). The third is the display pixel aspect ratio.
|
||||
|
||||
May return invalid/nonsense values if OSD is not initialized yet.
|
||||
|
||||
mp.msg functions
|
||||
----------------
|
||||
|
||||
@@ -547,14 +601,27 @@ command-line. All you have to do is to supply a table with default options to
|
||||
the read_options function. The function will overwrite the default values
|
||||
with values found in the config-file and the command-line (in that order).
|
||||
|
||||
``options.read_options(table [, identifier])``
|
||||
``options.read_options(table [, identifier [, on_update]])``
|
||||
A ``table`` with key-value pairs. The type of the default values is
|
||||
important for converting the values read from the config file or
|
||||
command-line back. Do not use ``nil`` as a default value!
|
||||
|
||||
The ``identifier`` is used to identify the config-file and the command-line
|
||||
options. These needs to unique to avoid collisions with other scripts.
|
||||
Defaults to ``mp.get_script_name()``.
|
||||
Defaults to ``mp.get_script_name()`` if the parameter is ``nil`` or missing.
|
||||
|
||||
The ``on_update`` parameter enables run-time updates of all matching option
|
||||
values via the ``script-opts`` option/property. If any of the matching
|
||||
options changes, the values in the ``table`` (which was originally passed to
|
||||
the function) are changed, and ``on_update(list)`` is called. ``list`` is
|
||||
a table where each updated option has a ``list[option_name] = true`` entry.
|
||||
There is no initial ``on_update()`` call. This never re-reads the config file.
|
||||
``script-opts`` is always applied on the original config file, ignoring
|
||||
previous ``script-opts`` values (for example, if an option is removed from
|
||||
``script-opts`` at runtime, the option will have the value in the config
|
||||
file). ``table`` entries are only written for option values whose values
|
||||
effectively change (this is important if the script changes ``table``
|
||||
entries independently).
|
||||
|
||||
|
||||
Example implementation::
|
||||
|
||||
+168
-76
@@ -223,6 +223,12 @@ i and I
|
||||
file such as codec, framerate, number of dropped frames and so on. See
|
||||
`STATS`_ for more information.
|
||||
|
||||
del
|
||||
Cycles visibility between never / auto (mouse-move) / always
|
||||
|
||||
\`
|
||||
Show the console. (ESC closes it again. See `CONSOLE`_.)
|
||||
|
||||
(The following keys are valid only when using a video output that supports the
|
||||
corresponding adjustment.)
|
||||
|
||||
@@ -268,10 +274,19 @@ in the mpv git repository.
|
||||
Mouse Control
|
||||
-------------
|
||||
|
||||
button 3 and button 4
|
||||
Seek backward/forward 1 minute.
|
||||
Left double click
|
||||
Toggle fullscreen on/off.
|
||||
|
||||
button 5 and button 6
|
||||
Right click
|
||||
Toggle pause on/off.
|
||||
|
||||
Forward/Back button
|
||||
Skip to next/previous entry in playlist.
|
||||
|
||||
Wheel up/down
|
||||
Seek forward/backward 10 seconds.
|
||||
|
||||
Wheel left/right
|
||||
Decrease/increase volume.
|
||||
|
||||
|
||||
@@ -463,62 +478,98 @@ List Options
|
||||
------------
|
||||
|
||||
Some options which store lists of option values can have action suffixes. For
|
||||
example, you can set a ``,``-separated list of filters with ``--vf``, but the
|
||||
option also allows you to append filters with ``--vf-append``.
|
||||
example, the ``--display-tags`` option takes a ``,``-separated list of tags, but
|
||||
the option also allows you to append a single tag with ``--display-tags-append``,
|
||||
and the tag name can for example contain a literal ``,`` without the need for
|
||||
escaping.
|
||||
|
||||
Options for filenames do not use ``,`` as separator, but ``:`` (Unix) or ``;``
|
||||
(Windows).
|
||||
String list and path list options
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
String lists are separated by ``,``. The strings are not parsed or interpreted
|
||||
by the option system itself. However, most
|
||||
|
||||
Path or file list options use ``:`` (Unix) or ``;`` (Windows) as separator,
|
||||
instead of ``,``.
|
||||
|
||||
They support the following operations:
|
||||
|
||||
============= ===============================================
|
||||
Suffix Meaning
|
||||
============= ===============================================
|
||||
-add Append 1 or more items (may become alias for -append)
|
||||
-append Append single item (avoids need for escaping)
|
||||
-clr Clear the option
|
||||
-del Delete an existing item by integer index
|
||||
-pre Prepend 1 or more items
|
||||
-set Set a list of items
|
||||
-toggle Append an item, or remove if if it already exists
|
||||
-set Set a list of items (using the list separator, interprets escapes)
|
||||
-append Append single item (does not interpret escapes)
|
||||
-add Append 1 or more items (same syntax as -set)
|
||||
-pre Prepend 1 or more items (same syntax as -set)
|
||||
-clr Clear the option (remove all items)
|
||||
-remove Delete item if present (does not interpret escapes)
|
||||
-del Delete 1 or more items by integer index (deprecated)
|
||||
-toggle Append an item, or remove if if it already exists (no escapes)
|
||||
============= ===============================================
|
||||
|
||||
Although some operations allow specifying multiple ``,``-separated items, using
|
||||
this is strongly discouraged and deprecated, except for ``-set``.
|
||||
``-append`` is meant as a simple way to append a single item without having
|
||||
to escape the argument (you may still need to escape on the shell level).
|
||||
|
||||
Without suffix, the action taken is normally ``-set``.
|
||||
Key/value list options
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A key/value list is a list of key/value string pairs. In programming languages,
|
||||
this type of data structure is often called a map or a dictionary. The order
|
||||
normally does not matter, although in some cases the order might matter.
|
||||
|
||||
They support the following operations:
|
||||
|
||||
============= ===============================================
|
||||
Suffix Meaning
|
||||
============= ===============================================
|
||||
-set Set a list of items (using ``,`` as separator)
|
||||
-append Append a single item (escapes for the key, no escapes for the value)
|
||||
-add Append 1 or more items (same syntax as -set)
|
||||
-remove Delete item by key if present (does not interpret escapes)
|
||||
============= ===============================================
|
||||
|
||||
Keys are unique within the list. If an already present key is set, the existing
|
||||
key is removed before the new value is appended.
|
||||
|
||||
Filter options
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This is a very complex option type for the ``--af`` and ``--vf`` options only.
|
||||
They often require complicated escaping. See `VIDEO FILTERS`_ for details. They
|
||||
support the following operations:
|
||||
|
||||
============= ===============================================
|
||||
Suffix Meaning
|
||||
============= ===============================================
|
||||
-set Set a list of filters (using ``,`` as separator)
|
||||
-append Append single filter
|
||||
-add Append 1 or more filters (same syntax as -set)
|
||||
-pre Prepend 1 or more filters (same syntax as -set)
|
||||
-clr Clear the option (remove all filters)
|
||||
-remove Delete filter if present
|
||||
-del Delete 1 or more filters by integer index or filter label (deprecated)
|
||||
-toggle Append a filter, or remove if if it already exists
|
||||
-help Pseudo operation that prints a help text to the terminal
|
||||
============= ===============================================
|
||||
|
||||
General
|
||||
~~~~~~~
|
||||
|
||||
Without suffix, the operation used is normally ``-set``.
|
||||
|
||||
Although some operations allow specifying multiple items, using this is strongly
|
||||
discouraged and deprecated, except for ``-set``. There is a chance that
|
||||
operations like ``-add`` and ``-pre`` will work like ``-append`` and accept a
|
||||
single, unescaped item only (so the ``,`` separator will not be interpreted and
|
||||
is passed on as part of the value).
|
||||
|
||||
Some options (like ``--sub-file``, ``--audio-file``, ``--glsl-shader``) are
|
||||
aliases for the proper option with ``-append`` action. For example,
|
||||
``--sub-file`` is an alias for ``--sub-files-append``.
|
||||
|
||||
Some options only support a subset of the above.
|
||||
|
||||
Options of this type can be changed at runtime using the ``change-list``
|
||||
command, which takes the suffix as separate operation parameter.
|
||||
|
||||
Playing DVDs
|
||||
------------
|
||||
|
||||
DVDs can be played with the ``dvd://[title]`` syntax. The optional
|
||||
title specifier is a number which selects between separate video
|
||||
streams on the DVD. If no title is given (``dvd://``) then the longest
|
||||
title is selected automatically by the library. This is usually what
|
||||
you want. mpv does not support DVD menus.
|
||||
|
||||
DVDs which have been copied on to a hard drive or other mounted
|
||||
filesystem (by e.g. the ``dvdbackup`` tool) are accommodated by
|
||||
specifying the path to the local copy: ``--dvd-device=PATH``.
|
||||
Alternatively, running ``mpv PATH`` should auto-detect a DVD directory
|
||||
tree and play the longest title.
|
||||
|
||||
.. note:: DVD subtitles
|
||||
|
||||
DVDs use image-based subtitles. Image subtitles are implemented as
|
||||
a bitmap video stream which can be superimposed over the main
|
||||
movie. mpv's subtitle styling and positioning options and keyboard
|
||||
shortcuts generally do not work with image-based subtitles.
|
||||
Exceptions include options like ``--stretch-dvd-subs`` and
|
||||
``--stretch-image-subs-to-screen``.
|
||||
|
||||
command, which takes the suffix (without the ``-``) as separate operation
|
||||
parameter.
|
||||
|
||||
CONFIGURATION FILES
|
||||
===================
|
||||
@@ -641,12 +692,8 @@ Some profiles are loaded automatically. The following example demonstrates this:
|
||||
|
||||
::
|
||||
|
||||
[protocol.dvd]
|
||||
profile-desc="profile for dvd:// streams"
|
||||
alang=en
|
||||
|
||||
[extension.flv]
|
||||
profile-desc="profile for .flv files"
|
||||
[extension.mkv]
|
||||
profile-desc="profile for .mkv files"
|
||||
vf=flip
|
||||
|
||||
The profile name follows the schema ``type.name``, where type can be
|
||||
@@ -656,6 +703,51 @@ and ``extension`` for the extension of the path of the currently played file
|
||||
|
||||
This feature is very limited, and there are no other auto profiles.
|
||||
|
||||
Using mpv from other programs or scripts
|
||||
========================================
|
||||
|
||||
There are three choices for using mpv from other programs or scripts:
|
||||
|
||||
1. Calling it as UNIX process. If you do this, *do not parse terminal output*.
|
||||
The terminal output is intended for humans, and may change any time. In
|
||||
addition, terminal behavior itself may change any time. Compatibility
|
||||
cannot be guaranteed.
|
||||
|
||||
Your code should work even if you pass ``--no-terminal``. Do not attempt
|
||||
to simulate user input by sending terminal control codes to mpv's stdin.
|
||||
If you need interactive control, using ``--input-ipc-server`` is
|
||||
recommended. This gives you access to the `JSON IPC`_ over unix domain
|
||||
sockets (or named pipes on Windows).
|
||||
|
||||
Depending on what you do, passing ``--no-config`` or ``--config-dir`` may
|
||||
be a good idea to avoid conflicts with the normal mpv user configuration
|
||||
intended for CLI playback.
|
||||
|
||||
Using ``--input-ipc-server`` is also suitable for purposes like remote
|
||||
control (however, the IPC protocol itself is not "secure" and not
|
||||
intended to be so).
|
||||
|
||||
2. Using libmpv. This is generally recommended when mpv is used as playback
|
||||
backend for a completely different application. The provided C API is
|
||||
very close to CLI mechanisms and the scripting API.
|
||||
|
||||
Note that even though libmpv has different defaults, it can be configured
|
||||
to work exactly like the CLI player (except command line parsing is
|
||||
unavailable).
|
||||
|
||||
See `EMBEDDING INTO OTHER PROGRAMS (LIBMPV)`_.
|
||||
|
||||
3. As a user script (`LUA SCRIPTING`_, `JAVASCRIPT`_, `C PLUGINS`_). This is
|
||||
recommended when the goal is to "enhance" the CLI player. Scripts get
|
||||
access to the entire client API of mpv.
|
||||
|
||||
This is the standard way to create third-party extensions for the player.
|
||||
|
||||
All these access the client API, which is the sum of the various mechanisms
|
||||
provided by the player core, as documented here: `OPTIONS`_,
|
||||
`List of Input Commands`_, `Properties`_, `List of events`_ (also see C API),
|
||||
`Hooks`_.
|
||||
|
||||
TAKING SCREENSHOTS
|
||||
==================
|
||||
|
||||
@@ -783,10 +875,10 @@ PROTOCOLS
|
||||
|
||||
``ytdl://...``
|
||||
|
||||
By default, the youtube-dl hook script (enabled by default for mpv CLI)
|
||||
only looks at http URLs. Prefixing an URL with ``ytdl://`` forces it to
|
||||
be always processed by the script. This can also be used to invoke special
|
||||
youtube-dl functionality like playing a video by ID or invoking search.
|
||||
By default, the youtube-dl hook script only looks at http(s) URLs. Prefixing
|
||||
an URL with ``ytdl://`` forces it to be always processed by the script. This
|
||||
can also be used to invoke special youtube-dl functionality like playing a
|
||||
video by ID or invoking search.
|
||||
|
||||
Keep in mind that you can't pass youtube-dl command line options by this,
|
||||
and you have to use ``--ytdl-raw-options`` instead.
|
||||
@@ -811,29 +903,15 @@ PROTOCOLS
|
||||
|
||||
``bluray://`` is an alias.
|
||||
|
||||
``dvd://[title|[starttitle]-endtitle][/device]`` ``--dvd-device=PATH``
|
||||
``dvd://[title][/device]`` ``--dvd-device=PATH``
|
||||
|
||||
Play a DVD. DVD menus are not supported. If no title is given, the longest
|
||||
title is auto-selected.
|
||||
title is auto-selected. Without ``--dvd-device``, it will probably try
|
||||
to open an actual optical drive, if available and implemented for the OS.
|
||||
|
||||
``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same
|
||||
thing.
|
||||
|
||||
``dvdread://...:``
|
||||
|
||||
Play a DVD using the old libdvdread code. This is what MPlayer and
|
||||
older mpv versions used for ``dvd://``. Use is discouraged. It's
|
||||
provided only for compatibility and for transition, and to work
|
||||
around outstanding dvdnav bugs (see "DVD library choices" above).
|
||||
|
||||
``tv://[channel][/input_id]`` ``--tv-...``
|
||||
|
||||
Analogue TV via V4L. Also useful for webcams. (Linux only.)
|
||||
|
||||
``pvr://`` ``--pvr-...``
|
||||
|
||||
PVR. (Linux only.)
|
||||
|
||||
``dvb://[cardnumber@]channel`` ``--dvbin-...``
|
||||
|
||||
Digital TV via DVB. (Linux only.)
|
||||
@@ -857,9 +935,17 @@ PROTOCOLS
|
||||
demuxer name, and ``options`` is the (pseudo-)filename passed to the
|
||||
demuxer.
|
||||
|
||||
For example, ``mpv av://lavfi:mandelbrot`` makes use of the libavfilter
|
||||
wrapper included in libavdevice, and will use the ``mandelbrot`` source
|
||||
filter to generate input data.
|
||||
.. admonition:: Example
|
||||
|
||||
::
|
||||
|
||||
mpv av://v4l2:/dev/video0 --profile=low-latency --untimed
|
||||
|
||||
This plays video from the first v4l input with nearly the lowest latency
|
||||
possible. It's a good replacement for the removed ``tv://`` input.
|
||||
Using ``--untimed`` is a hack to output a captured frame immediately,
|
||||
instead of respecting the input framerate. (There may be better ways to
|
||||
handle this in the future.)
|
||||
|
||||
``avdevice://`` is an alias.
|
||||
|
||||
@@ -972,6 +1058,8 @@ works like in older mpv releases. The profiles are currently defined as follows:
|
||||
|
||||
.. include:: stats.rst
|
||||
|
||||
.. include:: console.rst
|
||||
|
||||
.. include:: lua.rst
|
||||
|
||||
.. include:: javascript.rst
|
||||
@@ -1009,7 +1097,11 @@ behavior of mpv.
|
||||
of ``--v`` options passed to the command line.
|
||||
|
||||
``MPV_LEAK_REPORT``
|
||||
If set to ``1``, enable internal talloc leak reporting.
|
||||
If set to ``1``, enable internal talloc leak reporting. If set to another
|
||||
value, disable leak reporting. If unset, use the default, which normally is
|
||||
``0``. If mpv was built with ``--enable-ta-leak-report``, the default is
|
||||
``1``. If leak reporting was disabled at compile time (``NDEBUG`` in
|
||||
custom ``CFLAGS``), this environment variable is ignored.
|
||||
|
||||
``LADSPA_PATH``
|
||||
Specifies the search path for LADSPA plugins. If it is unset, fully
|
||||
|
||||
+329
-80
@@ -10,6 +10,8 @@ Track Selection
|
||||
language codes, Matroska, MPEG-TS and NUT use ISO 639-2 three-letter
|
||||
language codes, while OGM uses a free-form identifier. See also ``--aid``.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``mpv dvd://1 --alang=hu,en`` chooses the Hungarian language track
|
||||
@@ -23,6 +25,8 @@ Track Selection
|
||||
language codes, Matroska uses ISO 639-2 three letter language codes while
|
||||
OGM uses a free-form identifier. See also ``--sid``.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``mpv dvd://1 --slang=hu,en`` chooses the Hungarian subtitle track on
|
||||
@@ -33,6 +37,8 @@ Track Selection
|
||||
``--vlang=<...>``
|
||||
Equivalent to ``--alang`` and ``--slang``, for video tracks.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
``--aid=<ID|auto|no>``
|
||||
Select audio track. ``auto`` selects the default, ``no`` disables audio.
|
||||
See also ``--alang``. mpv normally prints available audio tracks on the
|
||||
@@ -700,11 +706,15 @@ Program Behavior
|
||||
Load a Lua script. The second option allows you to load multiple scripts by
|
||||
separating them with the path separator (``:`` on Unix, ``;`` on Windows).
|
||||
|
||||
``--scripts`` is a path list option. See `List Options`_ for details.
|
||||
|
||||
``--script-opts=key1=value1,key2=value2,...``
|
||||
Set options for scripts. A script can query an option by key. If an
|
||||
option is used and what semantics the option value has depends entirely on
|
||||
the loaded scripts. Values not claimed by any scripts are ignored.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
``--merge-files``
|
||||
Pretend that all files passed to mpv are concatenated into a single, big
|
||||
file. This uses timeline/EDL support internally.
|
||||
@@ -714,6 +724,13 @@ Program Behavior
|
||||
subdirectory (usually ``~/.config/mpv/watch_later/``).
|
||||
See ``quit-watch-later`` input command.
|
||||
|
||||
``--resume-playback-check-mtime``
|
||||
Only restore the playback position from the ``watch_later`` configuration
|
||||
subdirectory (usually ``~/.config/mpv/watch_later/``) if the file's
|
||||
modification time is the same as at the time of saving. This may prevent
|
||||
skipping forward in files with the same name which have different content.
|
||||
(Default: ``no``)
|
||||
|
||||
``--profile=<profile1,profile2,...>``
|
||||
Use the given profile(s), ``--profile=help`` displays a list of the
|
||||
defined profiles.
|
||||
@@ -737,6 +754,8 @@ Program Behavior
|
||||
|
||||
The special name ``all`` resets as many options as possible.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--reset-on-next-file=pause``
|
||||
@@ -761,7 +780,8 @@ Program Behavior
|
||||
(Default: disabled)
|
||||
|
||||
``--show-profile=<profile>``
|
||||
Show the description and content of a profile.
|
||||
Show the description and content of a profile. Lists all profiles if no
|
||||
parameter is provided.
|
||||
|
||||
``--use-filedir-conf``
|
||||
Look for a file-specific configuration file in the same directory as the
|
||||
@@ -827,6 +847,8 @@ Program Behavior
|
||||
information to mpv. Take note that SOCKS proxies aren't supported and
|
||||
https URLs also bypass the proxy. This is a limitation in FFmpeg.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
- ``--ytdl-raw-options=username=user,password=pass``
|
||||
@@ -839,6 +861,12 @@ Program Behavior
|
||||
binding (default: yes). By default, the ``i`` key is used (``I`` to make
|
||||
the overlay permanent).
|
||||
|
||||
``--load-osd-console=<yes|no>``
|
||||
Enable the builtin script that shows a console on a key binding and lets
|
||||
you enter commands (default: yes). By default,. The ``´`` key is used to
|
||||
show the console, and ``ESC`` to hide it again. (This is based on a user
|
||||
script called ``repl.lua``.)
|
||||
|
||||
``--player-operation-mode=<cplayer|pseudo-gui>``
|
||||
For enabling "pseudo GUI mode", which means that the defaults for some
|
||||
options are changed. This option should not normally be used directly, but
|
||||
@@ -881,25 +909,41 @@ Video
|
||||
The argument selects the drop methods, and can be one of the following:
|
||||
|
||||
<no>
|
||||
Disable any framedropping.
|
||||
Disable any frame dropping. Not recommended, for testing only.
|
||||
<vo>
|
||||
Drop late frames on video output (default). This still decodes and
|
||||
filters all frames, but doesn't render them on the VO. It tries to query
|
||||
the display FPS (X11 only, not correct on multi-monitor systems), or
|
||||
assumes infinite display FPS if that fails. Drops are indicated in
|
||||
the terminal status line as ``Dropped:`` field. If the decoder is too slow,
|
||||
in theory all frames would have to be dropped (because all frames are
|
||||
too late) - to avoid this, frame dropping stops if the effective
|
||||
framerate is below 10 FPS.
|
||||
filters all frames, but doesn't render them on the VO. Drops are
|
||||
indicated in the terminal status line as ``Dropped:`` field.
|
||||
|
||||
In audio sync. mode, this drops frames that are outdated at the time of
|
||||
display. If the decoder is too slow, in theory all frames would have to
|
||||
be dropped (because all frames are too late) - to avoid this, frame
|
||||
dropping stops if the effective framerate is below 10 FPS.
|
||||
|
||||
In display-sync. modes (see ``--video-sync``), this affects only how
|
||||
A/V drops or repeats frames. If this mode is disabled, A/V desync will
|
||||
in theory not affect video scheduling anymore (much like the
|
||||
``display-resample-desync`` mode). However, even if disabled, frames
|
||||
will still be skipped (i.e. dropped) according to the ratio between
|
||||
video and display frequencies.
|
||||
|
||||
This is the recommended mode, and the default.
|
||||
<decoder>
|
||||
Old, decoder-based framedrop mode. (This is the same as ``--framedrop=yes``
|
||||
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 unwatchable choppy output, or even freeze the display
|
||||
completely. Not recommended.
|
||||
completely.
|
||||
|
||||
This uses a heuristic which may not make sense, and in general cannot
|
||||
achieve good results, because the decoder's frame dropping cannot be
|
||||
controlled in a predictable manner. Not recommended.
|
||||
|
||||
Even if you want to use this, prefer ``decoder+vo`` for better results.
|
||||
|
||||
The ``--vd-lavc-framedrop`` option controls what frames to drop.
|
||||
<decoder+vo>
|
||||
Enable both modes. Not recommended.
|
||||
Enable both modes. Not recommended. Better than just ``decoder`` mode.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -925,8 +969,7 @@ Video
|
||||
frame, so if this is not done, there is some likeliness that the VO has
|
||||
to drop some frames if rendering the first frame takes longer than needed.
|
||||
|
||||
|
||||
``--display-fps=<fps>``
|
||||
``--override-display-fps=<fps>``
|
||||
Set the display FPS used with the ``--video-sync=display-*`` modes. By
|
||||
default, a detected value is used. Keep in mind that setting an incorrect
|
||||
value (even if slightly incorrect) can ruin video playback. On multi-monitor
|
||||
@@ -936,24 +979,68 @@ Video
|
||||
Set this option only if you have reason to believe the automatically
|
||||
determined value is wrong.
|
||||
|
||||
``--display-fps=<fps>``
|
||||
Deprecated alias for ``--override-display-fps``.
|
||||
|
||||
``--hwdec=<api>``
|
||||
Specify the hardware video decoding API that should be used if possible.
|
||||
Whether hardware decoding is actually done depends on the video codec. If
|
||||
hardware decoding is not possible, mpv will fall back on software decoding.
|
||||
|
||||
Hardware decoding is not enabled by default, because it is typically an
|
||||
additional source of errors. It is worth using only if your CPU is too
|
||||
slow to decode a specific video.
|
||||
|
||||
.. note::
|
||||
|
||||
Use the ``Ctrl+h`` shortcut to toggle hardware decoding at runtime. It
|
||||
toggles this option between ``auto`` and ``no``.
|
||||
|
||||
Always enabling HW decoding by putting it into the config file is
|
||||
discouraged. If you use the Ubuntu package, delete ``/etc/mpv/mpv.conf``,
|
||||
as the package tries to enable HW decoding by default by setting
|
||||
``hwdec=vaapi`` (which is less than ideal, and may even cause
|
||||
sub-optimal wrappers to be used). Or at least change it to
|
||||
``hwdec=auto-safe``.
|
||||
|
||||
Use one of the auto modes if you want to enable hardware decoding.
|
||||
Explicitly selecting the mode is mostly meant for testing and debugging.
|
||||
It's a bad idea to put explicit selection into the config file if you
|
||||
want thing to just keep working after updates and so on.
|
||||
|
||||
.. note::
|
||||
|
||||
Even if enabled, hardware decoding is still only white-listed for some
|
||||
codecs. See ``--hwdec-codecs`` to enable hardware decoding in more cases.
|
||||
|
||||
.. admonition:: Which method to choose?
|
||||
|
||||
- If you only want to enable hardware decoding at runtime, don't set the
|
||||
parameter, or put ``hwdec=no`` into your ``mpv.conf`` (relevant on
|
||||
distros which force-enable it by default, such as on Ubuntu). Use the
|
||||
``Ctrl+h`` default binding to enable it at runtime.
|
||||
- If you're not sure, but want hardware decoding always enabled by
|
||||
default, put ``hwdec=auto-safe`` into your ``mpv.conf``, and
|
||||
acknowledge that this use case is not "really" supported and may cause
|
||||
problems.
|
||||
- If you want to test available hardware decoding methods, pass
|
||||
``--hwdec=auto --hwdec-codecs=all`` and look at the terminal output.
|
||||
- If you're a developer, or want to perform elaborate tests, you may
|
||||
need any of the other possible option values.
|
||||
|
||||
``<api>`` can be one of the following:
|
||||
|
||||
:no: always use software decoding (default)
|
||||
:auto: enable best hw decoder (see below)
|
||||
:auto: forcibly enable any hw decoder found (see below)
|
||||
:yes: exactly the same as ``auto``
|
||||
:auto-safe: enable any whitelisted hw decoder (see below)
|
||||
:auto-copy: enable best hw decoder with copy-back (see below)
|
||||
:vdpau: requires ``--vo=gpu`` with ``--gpu-context=x11``,
|
||||
or ``--vo=vdpau`` (Linux only)
|
||||
:vdpau: requires ``--vo=gpu`` with X11, or ``--vo=vdpau`` (Linux only)
|
||||
:vdpau-copy: copies video back into system RAM (Linux with some GPUs only)
|
||||
:vaapi: requires ``--vo=gpu`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with some GPUs only)
|
||||
:videotoolbox: requires ``--vo=gpu`` (OS X 10.8 and up),
|
||||
or ``--vo=opengl-cb`` (iOS 9.0 and up)
|
||||
or ``--vo=libmpv`` (iOS 9.0 and up)
|
||||
:videotoolbox-copy: copies video back into system RAM (OS X 10.8 or iOS 9.0 and up)
|
||||
:dxva2: requires ``--vo=gpu`` with ``--gpu-context=d3d11``,
|
||||
``--gpu-context=angle`` or ``--gpu-context=dxinterop``
|
||||
@@ -980,22 +1067,49 @@ Video
|
||||
work, it will always fall back to software decoding, instead of trying the
|
||||
next method (might matter on some Linux systems).
|
||||
|
||||
``auto-safe`` is similar to ``auto``, but allows only whitelisted methods
|
||||
that are considered "safe". This is supposed to be a reasonable way to
|
||||
enable hardware decdoding by default in a config file (even though you
|
||||
shouldn't do that anyway; prefer runtime enabling with ``Ctrl+h``). Unlike
|
||||
``auto``, this will not try to enable unknown or known-to-be-bad methods. In
|
||||
addition, this may disable hardware decoding in other situations when it's
|
||||
known to cause problems, but currently this mechanism is quite primitive.
|
||||
(As an example for something that still causes problems: certain
|
||||
combinations of HEVC and Intel chips on Windows tend to cause mpv to crash,
|
||||
most likely due to driver bugs.)
|
||||
|
||||
``auto-copy-safe`` selects the union of methods selected with ``auto-safe``
|
||||
and ``auto-copy``.
|
||||
|
||||
``auto-copy`` selects only modes that copy the video data back to system
|
||||
memory after decoding. This selects modes like ``vaapi-copy`` (and so on).
|
||||
If none of these work, hardware decoding is disabled. This mode is always
|
||||
guaranteed to incur no additional loss compared to software decoding, and
|
||||
will allow CPU processing with video filters.
|
||||
If none of these work, hardware decoding is disabled. This mode is usually
|
||||
guaranteed to incur no additional quality loss compared to software
|
||||
decoding (assuming modern codecs and an error free video stream), and will
|
||||
allow CPU processing with video filters. This mode works with all video
|
||||
filters and VOs.
|
||||
|
||||
Because these copy the decoded video back to system RAM, they're often less
|
||||
less efficient than the direct modes, and may not help too much over
|
||||
software decoding.
|
||||
|
||||
.. note::
|
||||
|
||||
Most non-copy methods only work with the OpenGL GPU backend. Currently,
|
||||
only the ``nvdec`` and ``cuda`` methods work with Vulkan.
|
||||
|
||||
The ``vaapi`` mode, if used with ``--vo=gpu``, requires Mesa 11 and most
|
||||
likely works with Intel GPUs only. It also requires the opengl EGL backend.
|
||||
likely works with Intel and AMD GPUs only. It also requires the opengl EGL
|
||||
backend.
|
||||
|
||||
The ``cuda`` and ``cuda-copy`` modes provides deinterlacing in the decoder
|
||||
which is useful as there is no other deinterlacing mechanism in the gpu
|
||||
output path. To use this deinterlacing you must pass the option:
|
||||
``vd-lavc-o=deint=[weave|bob|adaptive]``.
|
||||
Pass ``weave`` (or leave the option unset) to not attempt any
|
||||
deinterlacing. ``cuda`` should always be preferred unless the ``gpu``
|
||||
vo is not being used or filters are required.
|
||||
deinterlacing. ``cuda`` should always be preferred over ``cuda-copy`` unless
|
||||
the ``gpu`` vo is not being used or filters are required. Using ``nvdec``
|
||||
should be preferred on Nvidia hardware.
|
||||
|
||||
``nvdec`` is a newer implementation of CUVID/CUDA decoding, which uses the
|
||||
FFmpeg decoders for file parsing. Experimental, is known not to correctly
|
||||
@@ -1003,27 +1117,6 @@ Video
|
||||
is not supported. Since this uses FFmpeg's codec parsers, it is expected
|
||||
that this generally causes fewer issues than ``cuda``.
|
||||
|
||||
Most video filters will not work with hardware decoding as they are
|
||||
primarily implemented on the CPU. Some exceptions are ``vdpaupp``,
|
||||
``vdpaurb`` and ``vavpp``. See `VIDEO FILTERS`_ for more details.
|
||||
|
||||
The ``...-copy`` modes (e.g. ``dxva2-copy``) allow you to use hardware
|
||||
decoding with any VO, backend or filter. Because these copy the decoded
|
||||
video back to system RAM, they're likely less efficient than the direct
|
||||
modes (like e.g. ``dxva2``), and probably not more efficient than software
|
||||
decoding except for some codecs (e.g. HEVC).
|
||||
|
||||
.. note::
|
||||
|
||||
When using this switch, hardware decoding is still only done for some
|
||||
codecs. See ``--hwdec-codecs`` to enable hardware decoding for more
|
||||
codecs.
|
||||
|
||||
.. note::
|
||||
|
||||
Most non-copy methods only work with the OpenGL GPU backend. Currently,
|
||||
only the ``nvdec`` and ``cuda`` methods work with Vulkan.
|
||||
|
||||
.. admonition:: Quality reduction with hardware decoding
|
||||
|
||||
In theory, hardware decoding does not reduce video quality (at least
|
||||
@@ -1088,13 +1181,13 @@ Video
|
||||
This option is for troubleshooting hwdec interop issues. Since it's a
|
||||
debugging option, its semantics may change at any time.
|
||||
|
||||
This is useful for the ``gpu`` and ``opengl-cb`` VOs for selecting which
|
||||
This is useful for the ``gpu`` and ``libmpv`` VOs for selecting which
|
||||
hwdec interop context to use exactly. Effectively it also can be used
|
||||
to block loading of certain backends.
|
||||
|
||||
If set to ``auto`` (default), the behavior depends on the VO: for ``gpu``,
|
||||
it does nothing, and the interop context is loaded on demand (when the
|
||||
decoder probes for ``--hwdec`` support). For ``opengl-cb``, which has
|
||||
decoder probes for ``--hwdec`` support). For ``libmpv``, which has
|
||||
has no on-demand loading, this is equivalent to ``all``.
|
||||
|
||||
The empty string is equivalent to ``auto``.
|
||||
@@ -1371,6 +1464,11 @@ Video
|
||||
(default: 3). If this is a number, then fallback will be triggered if
|
||||
N frames fail to decode in a row. 1 is equivalent to ``yes``.
|
||||
|
||||
Setting this to a higher number might break the playback start fallback: if
|
||||
a fallback happens, parts of the file will be skipped, approximately by to
|
||||
the number of packets that could not be decoded. Values below an unspecified
|
||||
count will not have this problem, because mpv retains the packets.
|
||||
|
||||
``--vd-lavc-dr=<yes|no>``
|
||||
Enable direct rendering (default: yes). If this is set to ``yes``, the
|
||||
video will be decoded directly to GPU video memory (or staging buffers).
|
||||
@@ -1403,6 +1501,8 @@ Video
|
||||
mechanism, like ``bug``, ``gray``, ``idct``, ``ec``, ``vismv``,
|
||||
``skip_top`` (was ``st``), ``skip_bottom`` (was ``sb``), ``debug``.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``--vd-lavc-o=debug=pict``
|
||||
@@ -1662,6 +1762,8 @@ Audio
|
||||
unneeded and pass all unknown options through the AVOption system is
|
||||
welcome. A full list of AVOptions can be found in the FFmpeg manual.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
``--ad-spdif-dtshd=<yes|no>``, ``--dtshd``, ``--no-dtshd``
|
||||
If DTS is passed through, use DTS-HD.
|
||||
|
||||
@@ -1749,7 +1851,7 @@ Audio
|
||||
``--audio-files=<files>``
|
||||
Play audio from an external file while viewing a video.
|
||||
|
||||
This is a list option. See `List Options`_ for details.
|
||||
This is a path list option. See `List Options`_ for details.
|
||||
|
||||
``--audio-file=<file>``
|
||||
CLI/config file only alias for ``--audio-files-append``. Each use of this
|
||||
@@ -1830,6 +1932,8 @@ Audio
|
||||
``--audio-file-paths=<path1:path2:...>``
|
||||
Equivalent to ``--sub-file-paths`` option, but for auto-loaded audio files.
|
||||
|
||||
This is a path list option. See `List Options`_ for details.
|
||||
|
||||
``--audio-client-name=<name>``
|
||||
The application name the player reports to the audio API. Can be useful
|
||||
if you want to force a different audio profile (e.g. with PulseAudio),
|
||||
@@ -1903,7 +2007,7 @@ Subtitles
|
||||
and ``--secondary-sid`` to select the second index. (The index is printed
|
||||
on the terminal output after the ``--sid=`` in the list of streams.)
|
||||
|
||||
``--sub-files`` is a list option (see `List Options`_ for details), and
|
||||
``--sub-files`` is a path list option (see `List Options`_ for details), and
|
||||
can take multiple file names separated by ``:`` (Unix) or ``;`` (Windows),
|
||||
while ``--sub-file`` takes a single filename, but can be used multiple
|
||||
times to add multiple files. Technically, ``--sub-file`` is a CLI/config
|
||||
@@ -1995,6 +2099,8 @@ Subtitles
|
||||
``--sub-ass-force-style=<[Style.]Param=Value[,...]>``
|
||||
Override some style or script info parameters.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--sub-ass-force-style=FontName=Arial,Default.Bold=1``
|
||||
@@ -2275,7 +2381,7 @@ Subtitles
|
||||
- ``/path/to/video/subtitles/``
|
||||
- the ``sub`` configuration subdirectory (usually ``~/.config/mpv/sub/``)
|
||||
|
||||
This is a list option. See `List Options`_ for details.
|
||||
This is a path list option. See `List Options`_ for details.
|
||||
|
||||
``--sub-visibility``, ``--no-sub-visibility``
|
||||
Can be used to disable display of subtitles, but still select and decode
|
||||
@@ -2510,8 +2616,8 @@ Window
|
||||
``--fs-screen=<all|current|0-32>``
|
||||
In multi-monitor configurations (i.e. a single desktop that spans across
|
||||
multiple displays), this option tells mpv which screen to go fullscreen to.
|
||||
If ``default`` is provided mpv will fallback on using the behavior
|
||||
depending on what the user provided with the ``screen`` option.
|
||||
If ``current`` is used mpv will fallback on what the user provided with
|
||||
the ``screen`` option.
|
||||
|
||||
.. admonition:: Note (X11)
|
||||
|
||||
@@ -2763,6 +2869,23 @@ Window
|
||||
For example, ``--window-scale=0.5`` would show the window at half the
|
||||
video size.
|
||||
|
||||
``--window-minimized=<yes|no>``
|
||||
Whether the video window is minimized or not. Setting this will minimize,
|
||||
or unminimze, the video window if the current VO supports it. Note that
|
||||
some VOs may support minimization while not supporting unminimization
|
||||
(eg: Wayland).
|
||||
|
||||
Whether this option and ``--window-maximized`` work on program start or
|
||||
at runtime, and whether they're (at runtime) updated to reflect the actual
|
||||
window state, heavily depends on the VO and the windowing system. Some VOs
|
||||
simply do not implement them or parts of them, while other VOs may be
|
||||
restricted by the windowing systems (especially Wayland).
|
||||
|
||||
``--window-maximized=<yes|no>``
|
||||
Whether the video window is maximized or not. Setting this will maximize,
|
||||
or unmaximize, the video window if the current VO supports it. See
|
||||
``--window-minimized`` for further remarks.
|
||||
|
||||
``--cursor-autohide=<number|no|always>``
|
||||
Make mouse cursor automatically hide after given number of milliseconds.
|
||||
``no`` will disable cursor autohide. ``always`` means the cursor will stay
|
||||
@@ -2815,7 +2938,7 @@ Window
|
||||
- ``--monitoraspect=16:9`` or ``--monitoraspect=1.7777``
|
||||
|
||||
``--hidpi-window-scale``, ``--no-hidpi-window-scale``
|
||||
(OS X and X11 only)
|
||||
(OS X, X11, and Wayland only)
|
||||
Scale the window size according to the backing scale factor (default: yes).
|
||||
On regular HiDPI resolutions the window opens with double the size but appears
|
||||
as having the same size as on none-HiDPI resolutions. This is the default OS X
|
||||
@@ -3061,6 +3184,8 @@ Demuxer
|
||||
be found in the FFmpeg manual. Note that some options may conflict
|
||||
with mpv options.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``--demuxer-lavf-o=fflags=+ignidx``
|
||||
@@ -3092,6 +3217,18 @@ Demuxer
|
||||
the first song in the stream. Well, you won't get anything useful either
|
||||
way if the seek is outside of mpv's cache.
|
||||
|
||||
``--demuxer-lavf-propagate-opts=<yes|no>``
|
||||
Propagate FFmpeg-level options to recursively opened connections (default:
|
||||
yes). This is needed because FFmpeg will apply these settings to nested
|
||||
AVIO contexts automatically. On the other hand, this could break in certain
|
||||
situations - it's the FFmpeg API, you just can't win.
|
||||
|
||||
This affects in particular the ``--timeout`` option and anything passed
|
||||
with ``--demuxer-lavf-o``.
|
||||
|
||||
If this option is deemed unnecessary at some point in the future, it will
|
||||
be removed without notice.
|
||||
|
||||
``--demuxer-mkv-subtitle-preroll=<yes|index|no>``, ``--mkv-subtitle-preroll``
|
||||
Try harder to show embedded soft subtitles when seeking somewhere. Normally,
|
||||
it can happen that the subtitle at the seek target is not shown due to how
|
||||
@@ -3368,6 +3505,8 @@ Input
|
||||
like any other binding). See `INPUT.CONF`_.
|
||||
|
||||
``--input-file=<filename>``
|
||||
Deprecated. Use ``--input-ipc-server``.
|
||||
|
||||
Read commands from the given file. Mostly useful with a FIFO. Since
|
||||
mpv 0.7.0 also understands JSON commands (see `JSON IPC`_), but you can't
|
||||
get replies or events. Use ``--input-ipc-server`` for something
|
||||
@@ -3403,12 +3542,8 @@ Input
|
||||
|
||||
See `JSON IPC`_ for details.
|
||||
|
||||
``--input-appleremote=<yes|no>``
|
||||
(OS X only)
|
||||
Enable/disable Apple Remote support. Enabled by default (except for libmpv).
|
||||
|
||||
``--input-gamepad=<yes|no>``
|
||||
Enable/disable SDL2 Gamepad support. Enabled by default (except for libmpv).
|
||||
Enable/disable SDL2 Gamepad support. Disabled by default.
|
||||
|
||||
``--input-cursor``, ``--no-input-cursor``
|
||||
Permit mpv to receive pointer events reported by the video output
|
||||
@@ -3833,6 +3968,21 @@ Software Scaler
|
||||
``--sws-cvs=<v>``
|
||||
Software scaler chroma vertical shifting. See ``--sws-scaler``.
|
||||
|
||||
``--sws-bitexact=<yes|no>``
|
||||
Unknown functionality (default: no). Consult libswscale source code. The
|
||||
primary purpose of this, as far as libswscale API goes), is to produce
|
||||
exactly the same output for the same input on all platforms (output has the
|
||||
same "bits" everywhere, thus "bitexact"). Typically disables optimizations.
|
||||
|
||||
``--sws-fast=<yes|no>``
|
||||
Allow optimizations that help with performance, but reduce quality (default:
|
||||
no).
|
||||
|
||||
VOs like ``drm`` and ``x11`` will benefit a lot from using ``--sws-fast``.
|
||||
You may need to set other options, like ``--sws-scaler``. The builtin
|
||||
``sws-fast`` profile sets this option and some others to gain performance
|
||||
for reduced quality.
|
||||
|
||||
``--sws-allow-zimg=<yes|no>``
|
||||
Allow using zimg (if the component using the internal swscale wrapper
|
||||
explicitly allows so). In this case, zimg *may* be used, if the internal
|
||||
@@ -3846,10 +3996,30 @@ Software Scaler
|
||||
correctly, a verbose priority log message will indicate whether zimg is
|
||||
being used.
|
||||
|
||||
Currently, barely anything uses this.
|
||||
Most things which need software conversion can make use of this.
|
||||
|
||||
``--zimg--scaler=<point|bilinear|bicubic|spline16|lanczos>``
|
||||
Zimg luma scaler to use (default: bilinear).
|
||||
``--zimg-scaler=<point|bilinear|bicubic|spline16|spline36|lanczos>``
|
||||
Zimg luma scaler to use (default: lanczos).
|
||||
|
||||
``--zimg-scaler-param-a=<default|float>``, ``--zimg-scaler-param-b=<default|float>``
|
||||
Set scaler parameters. By default, these are set to the special string
|
||||
``default``, which maps to a scaler-specific default value. Ignored if the
|
||||
scaler is not tunable.
|
||||
|
||||
``lanczos``
|
||||
``--zimg-scaler-param-a`` is the number of taps.
|
||||
|
||||
``bicubic``
|
||||
a and b are the bicubic b and c parameters.
|
||||
|
||||
``--zimg-scaler-chroma=...``
|
||||
Same as ``--zimg-scaler``, for for chroma interpolation (default: bilinear).
|
||||
|
||||
``--zimg-scaler-chroma-param-a``, ``--zimg-scaler-chroma-param-b``
|
||||
Same as ``--zimg-scaler-param-a`` / ``--zimg-scaler-param-b``, for chroma.
|
||||
|
||||
``--zimg-dither=<no|ordered|random|error-diffusion>``
|
||||
Dithering (default: random).
|
||||
|
||||
``--zimg-fast=<yes|no>``
|
||||
Allow optimizations that help with performance, but reduce quality (default:
|
||||
@@ -3904,6 +4074,8 @@ It also sets the defaults for the ``lavrresample`` audio filter.
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
Terminal
|
||||
--------
|
||||
|
||||
@@ -4125,6 +4297,32 @@ Cache
|
||||
|
||||
Currently, this is used for ``--cache-on-disk`` only.
|
||||
|
||||
``--stream-buffer-size=<bytesize>``
|
||||
Size of the low level stream byte buffer (default: 128KB). This is used as
|
||||
buffer between demuxer and low level I/O (e.g. sockets). Generally, this
|
||||
can be very small, and the main purpose is similar to the internal buffer
|
||||
FILE in the C standard library will have.
|
||||
|
||||
Half of the buffer is always used for guaranteed seek back, which is
|
||||
important for unseekable input.
|
||||
|
||||
There are known cases where this can help performance to set a large buffer:
|
||||
|
||||
1. mp4 files. libavformat may trigger many small seeks in both
|
||||
directions, depending on how the file was muxed.
|
||||
|
||||
2. Certain network filesystems, which do not have a cache, and where
|
||||
small reads can be inefficient.
|
||||
|
||||
In other cases, setting this to a large value can reduce performance.
|
||||
|
||||
Usually, read accesses are at half the buffer size, but it may happen that
|
||||
accesses are done alternating with smaller and larger sizes (this is due to
|
||||
the internal ring buffer wrap-around).
|
||||
|
||||
See ``--list-options`` for defaults and value range. ``<bytesize>`` options
|
||||
accept suffixes such as ``KiB`` and ``MiB``.
|
||||
|
||||
Network
|
||||
-------
|
||||
|
||||
@@ -4141,6 +4339,8 @@ Network
|
||||
``--http-header-fields=<field1,field2>``
|
||||
Set custom HTTP fields when accessing HTTP stream.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
::
|
||||
@@ -4184,9 +4384,10 @@ Network
|
||||
Specify a referrer path or URL for HTTP requests.
|
||||
|
||||
``--network-timeout=<seconds>``
|
||||
Specify the network timeout in seconds. This affects at least HTTP. The
|
||||
special value 0 (default) uses the FFmpeg/Libav defaults. If a protocol
|
||||
is used which does not support timeouts, this option is silently ignored.
|
||||
Specify the network timeout in seconds (default: 60 seconds). This affects
|
||||
at least HTTP. The special value 0 uses the FFmpeg/Libav defaults. If a
|
||||
protocol is used which does not support timeouts, this option is silently
|
||||
ignored.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -4195,8 +4396,10 @@ Network
|
||||
option accept different units (seconds instead of microseconds, causing
|
||||
mpv to pass it huge values), it will also overflow FFmpeg internal
|
||||
calculations. The worst is that merely setting the option will put RTSP
|
||||
into listening mode, which breaks any client uses. Do not use this
|
||||
option with RTSP URLs.
|
||||
into listening mode, which breaks any client uses. At time of this
|
||||
writing, the fix was not made effective yet. For this reason, this
|
||||
option is ignored (or should be ignored) on RTSP URLs. You can still
|
||||
set the timeout option directly with ``--demuxer-lavf-o``.
|
||||
|
||||
``--rtsp-transport=<lavf|udp|tcp|http>``
|
||||
Select RTSP transport method (default: tcp). This selects the underlying
|
||||
@@ -4329,7 +4532,7 @@ GPU renderer options
|
||||
-----------------------
|
||||
|
||||
The following video options are currently all specific to ``--vo=gpu`` and
|
||||
``--vo=opengl-cb`` only, which are the only VOs that implement them.
|
||||
``--vo=libmpv`` only, which are the only VOs that implement them.
|
||||
|
||||
``--scale=<filter>``
|
||||
The filter function to use when upscaling video.
|
||||
@@ -4656,7 +4859,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
require driver-specific hacks if using multiple monitors, to ensure mpv
|
||||
syncs to the right one. Compositing window managers can also lead to bad
|
||||
results, as can missing or incorrect display FPS information (see
|
||||
``--display-fps``).
|
||||
``--override-display-fps``).
|
||||
|
||||
``--vulkan-swap-mode=<mode>``
|
||||
Controls the presentation mode of the vulkan swapchain. This is similar
|
||||
@@ -4694,8 +4897,8 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
GPUs. It's worth noting that if async compute is enabled, and the device
|
||||
supports more compute queues than graphics queues (bound by the restrictions
|
||||
set by ``--vulkan-queue-count``), mpv will internally try and prefer the
|
||||
use of compute shaders over fragment shaders wherever possible. Not enabled
|
||||
by default, since it seems to cause issues with some drivers.
|
||||
use of compute shaders over fragment shaders wherever possible. Enabled by
|
||||
default, although Nvidia users may want to disable it.
|
||||
|
||||
``--d3d11-warp=<yes|no|auto>``
|
||||
Use WARP (Windows Advanced Rasterization Platform) with the D3D11 GPU
|
||||
@@ -4746,6 +4949,19 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
from Windows 10. Thus on older systems it will only automatically
|
||||
utilize the rgba8 output format.
|
||||
|
||||
``--d3d11-output-csp=<auto|srgb|linear|pq|bt.2020>``
|
||||
Select a specific D3D11 output color space to utilize for D3D11 rendering.
|
||||
"auto" is the default, which will select the color space of the desktop
|
||||
on which the swap chain is located.
|
||||
|
||||
Values other than "srgb" and "pq" have had issues in testing, so they
|
||||
are mostly available for manual testing.
|
||||
|
||||
.. note::
|
||||
|
||||
Swap chain color space configuration is only available from an API
|
||||
available from Windows 10. Thus on older systems it will not work.
|
||||
|
||||
``--d3d11va-zero-copy=<yes|no>``
|
||||
By default, when using hardware decoding with ``--gpu-api=d3d11``, the
|
||||
video image will be copied (GPU-to-GPU) from the decoder surface to a
|
||||
@@ -4758,7 +4974,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
|
||||
Currently only relevant for ``--gpu-api=d3d11``.
|
||||
|
||||
``--wayland-frame-wait-offset=<-100..3000>``
|
||||
``--wayland-frame-wait-offset=<-500..3000>``
|
||||
Control the amount of offset (in microseconds) to add to wayland's frame wait
|
||||
(default 1000). The wayland context assumes that if frame callback or presentation
|
||||
feedback isn't received within a certain amount of time then the video is being
|
||||
@@ -4789,11 +5005,15 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
This option is deprecated, since there is only one reasonable value.
|
||||
It may be removed in the future.
|
||||
|
||||
``--glsl-shaders=<file-list>``
|
||||
``--glsl-shader=<file>``, ``--glsl-shaders=<file-list>``
|
||||
Custom GLSL hooks. These are a flexible way to add custom fragment shaders,
|
||||
which can be injected at almost arbitrary points in the rendering pipeline,
|
||||
and access all previous intermediate textures. Each use of the option will
|
||||
add another file to the internal list of shaders (see `List Options`_).
|
||||
and access all previous intermediate textures.
|
||||
|
||||
Each use of the ``--glsl-shader`` option will add another file to the
|
||||
internal list of shaders, while ``--glsl-shaders`` takes a list of files,
|
||||
and overwrites the internal list with it. The latter is a path list option
|
||||
(see `List Options`_ for details).
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
@@ -5274,7 +5494,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
auto
|
||||
auto-select (default)
|
||||
cocoa
|
||||
Cocoa/OS X (deprecated, use --vo=opengl-cb instead)
|
||||
Cocoa/OS X (deprecated, use --vo=libmpv instead)
|
||||
win
|
||||
Win32/WGL
|
||||
winvk
|
||||
@@ -5303,11 +5523,6 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
X11/EGL
|
||||
android
|
||||
Android/EGL. Requires ``--wid`` be set to an ``android.view.Surface``.
|
||||
vdpauglx
|
||||
Use vdpau presentation with GLX as backing. Experimental use only.
|
||||
Using this will have no advantage (other than additional bugs or
|
||||
performance problems), and is for doing experiments only. Will not
|
||||
be used automatically.
|
||||
|
||||
``--gpu-api=<type>``
|
||||
Controls which type of graphics APIs will be accepted:
|
||||
@@ -5784,6 +5999,8 @@ Miscellaneous
|
||||
The default includes a common list of tags, call mpv with ``--list-options``
|
||||
to see it.
|
||||
|
||||
This is a string list option. See `List Options`_ for details.
|
||||
|
||||
``--mc=<seconds/frame>``
|
||||
Maximum A-V sync correction per frame (in seconds)
|
||||
|
||||
@@ -5940,6 +6157,8 @@ Miscellaneous
|
||||
other options such as e.g. user agent are not available with all protocols,
|
||||
and printing errors for unknown options would end up being too noisy.)
|
||||
|
||||
This is a key/value list option. See `List Options`_ for details.
|
||||
|
||||
``--vo-mmcss-profile=<name>``
|
||||
(Windows only.)
|
||||
Set the MMCSS profile for the video renderer thread (default: ``Playback``).
|
||||
@@ -5970,7 +6189,7 @@ Miscellaneous
|
||||
it slightly less intrusive. (In mpv 0.28.0 and before, this was not quite
|
||||
strictly enforced.)
|
||||
|
||||
This is a list option. See `List Options`_ for details.
|
||||
This is a path list option. See `List Options`_ for details.
|
||||
|
||||
``--external-file=<file>``
|
||||
CLI/config file only alias for ``--external-files-append``. Each use of this
|
||||
@@ -6080,3 +6299,33 @@ Miscellaneous
|
||||
|
||||
See the FFmpeg libavfilter documentation for details on the available
|
||||
filters.
|
||||
|
||||
``--metadata-codepage=<codepage>``
|
||||
Codepage for various input metadata (default: ``utf-8``). This affects how
|
||||
file tags, chapter titles, etc. are interpreted. You can for example set
|
||||
this to ``auto`` to enable autodetection of the codepage. (This is not the
|
||||
default because non-UTF-8 codepages are an obscure fringe use-case.)
|
||||
|
||||
See ``--sub-codepage`` option on how codepages are specified and further
|
||||
details regarding autodetection and codepage conversion. (The underlying
|
||||
code is the same.)
|
||||
|
||||
Conversion is not applied to metadata that is updated at runtime.
|
||||
|
||||
|
||||
Debugging
|
||||
---------
|
||||
|
||||
``--unittest=<name>``
|
||||
Run an internal unit test. There are multiple, and the name specifies which.
|
||||
|
||||
The special value ``all-simple`` runs all tests which do not need further
|
||||
setup (other arguments and such). Some tests may need additional arguments
|
||||
to do anything useful.
|
||||
|
||||
On success, the player binary exits with exit status 0, otherwise it returns
|
||||
with an undefined non-0 exit status (it may crash or abort itself on test
|
||||
failures).
|
||||
|
||||
This is only enabled if built with ``--enable-tests``, and should normally
|
||||
be enabled and used by developers only.
|
||||
+34
-2
@@ -330,8 +330,10 @@ Configurable Options
|
||||
user has set them. (It will not overwrite them if all of them are set to
|
||||
default values.)
|
||||
|
||||
Currently, this is supported for the ``bottombar`` layout only. The other
|
||||
layouts do not change if this option is set.
|
||||
Currently, this is supported for the ``bottombar`` and ``topbar`` layout
|
||||
only. The other layouts do not change if this option is set. Separately,
|
||||
if window controls are present (see below), they will be affected
|
||||
regardless of which osc layout is in use.
|
||||
|
||||
The border is static and appears even if the OSC is configured to appear
|
||||
only on mouse interaction. If the OSC is invisible, the border is simply
|
||||
@@ -341,6 +343,36 @@ Configurable Options
|
||||
``--sub-use-margins`` option is set to ``yes``, the default). This may be
|
||||
fixed later.
|
||||
|
||||
This does not work correctly with video outputs like ``--vo=xv``, which
|
||||
render OSD into the unscaled video.
|
||||
|
||||
``windowcontrols``
|
||||
Default: auto (Show window controls if there is no window border)
|
||||
|
||||
Whether to show window management controls over the video, and if so,
|
||||
which side of the window to place them. This may be desirable when the
|
||||
window has no decorations, either because they have been explicitly
|
||||
disabled (``border=no``) or because the current platform doesn't support
|
||||
them (eg: gnome-shell with wayland).
|
||||
|
||||
The set of window controls is fixed, offering ``minimize``, ``maximize``,
|
||||
and ``quit``. Not all platforms implement ``minimize`` and ``maximize``,
|
||||
but ``quit`` will always work.
|
||||
|
||||
``windowcontrols_alignment``
|
||||
Default: right
|
||||
|
||||
If window controls are shown, indicates which side should they be aligned
|
||||
to.
|
||||
|
||||
Supports ``left`` and ``right`` which will place the controls on those
|
||||
respective sides.
|
||||
|
||||
``greenandgrumpy``
|
||||
Default: no
|
||||
|
||||
Set to ``yes`` to reduce festivity (i.e. disable santa hat in December.)
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ stats:
|
||||
==== ==================
|
||||
1 Show usual stats
|
||||
2 Show frame timings
|
||||
3 Input cache stats
|
||||
==== ==================
|
||||
|
||||
Font
|
||||
@@ -53,6 +54,8 @@ Configurable Options
|
||||
Default: 1
|
||||
``key_page_2``
|
||||
Default: 2
|
||||
``key_page_3``
|
||||
Default: 3
|
||||
|
||||
Key bindings for page switching while stats are displayed.
|
||||
|
||||
|
||||
+56
-5
@@ -85,6 +85,9 @@ libavfilter bridge.
|
||||
Video filters are managed in lists. There are a few commands to manage the
|
||||
filter list.
|
||||
|
||||
``--vf-append=filter``
|
||||
Appends the filter given as arguments to the filter list.
|
||||
|
||||
``--vf-add=filter``
|
||||
Appends the filter given as arguments to the filter list. (Passing multiple
|
||||
filters is currently still possible, but deprecated.)
|
||||
@@ -93,12 +96,24 @@ filter list.
|
||||
Prepends the filters given as arguments to the filter list. (Passing
|
||||
multiple filters is currently still possible, but deprecated.)
|
||||
|
||||
``--vf-remove=filter``
|
||||
Deletes the filter from the list. The filter can be either given the way it
|
||||
was added (filter name and its full argument list), or by label (prefixed
|
||||
with ``@``). Matching of filters works as follows: if either of the compared
|
||||
filters has a label set, only the labels are compared. If none of the
|
||||
filters have a label, the filter name, arguments, and argument order are
|
||||
compared. (Passing multiple filters is currently still possible, but
|
||||
deprecated.)
|
||||
|
||||
``-vf-toggle=filter``
|
||||
Add the given filter to the list if it was not present yet, or remove it
|
||||
from the list if it was present. Matching of filters works as described in
|
||||
``--vf-remove``.
|
||||
|
||||
``--vf-del=filter``
|
||||
Deletes the filter. The filter can even given the way it was added (filter
|
||||
name and its full argument list), by label (prefixed with ``@``), or as
|
||||
index number. Index numbers start at 0, negative numbers address the end of
|
||||
the list (-1 is the last). (Passing multiple filters is currently still
|
||||
possible, but deprecated.)
|
||||
Sort of like ``--vf-remove``, but also accepts an index number. Index
|
||||
numbers start at 0, negative numbers address the end of the list (-1 is the
|
||||
last). Deprecated.
|
||||
|
||||
``--vf-clr``
|
||||
Completely empties the filter list.
|
||||
@@ -710,3 +725,39 @@ Available mpv-only filters are:
|
||||
Print computed fingerprints the the terminal (default: no). This is
|
||||
mostly for testing and such. Scripts should use ``vf-metadata`` to
|
||||
read information from this filter instead.
|
||||
|
||||
``gpu=...``
|
||||
Convert video to RGB using the OpenGL renderer normally used with
|
||||
``--vo=gpu``. This requires that the EGL implementation supports off-screen
|
||||
rendering on the default display. (This is the case with Mesa.)
|
||||
|
||||
Sub-options:
|
||||
|
||||
``w=<pixels>``, ``h=<pixels>``
|
||||
Size of the output in pixels (default: 0). If not positive, this will
|
||||
use the size of the first filtered input frame.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is highly experimental. Performance is bad, and it will not work
|
||||
everywhere in the first place. Some features are not supported.
|
||||
|
||||
.. warning::
|
||||
|
||||
This does not do OSD rendering. If you see OSD, then it has been
|
||||
rendered by the VO backend. (Subtitles are rendered by the ``gpu``
|
||||
filter, if possible.)
|
||||
|
||||
.. warning::
|
||||
|
||||
If you use this with encoding mode, keep in mind that encoding mode will
|
||||
convert the RGB filter's output back to yuv420p in software, using the
|
||||
configured software scaler. Using ``zimg`` might improve this, but in
|
||||
any case it might go against your goals when using this filter.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not use this with ``--vo=gpu``. It will apply filtering twice, since
|
||||
most ``--vo=gpu`` options are unconditionally applied to the ``gpu``
|
||||
filter. There is no mechanism in mpv to prevent this.
|
||||
|
||||
@@ -71,6 +71,9 @@ Available video output drivers are:
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever X11 is present.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
|
||||
``vdpau`` (X11 only)
|
||||
@@ -383,6 +386,9 @@ Available video output drivers are:
|
||||
Depends on support of true color by modern terminals to display the images
|
||||
at full color range. On Windows it requires an ansi terminal such as mintty.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
``--vo-tct-algo=<algo>``
|
||||
Select how to write the pixels to the terminal.
|
||||
|
||||
@@ -482,6 +488,9 @@ Available video output drivers are:
|
||||
environment (e.g. no X). Does not support hardware acceleration (if you
|
||||
need this, check the ``drm`` backend for ``gpu`` VO).
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
``--drm-connector=[<gpu_number>.]<name>``
|
||||
@@ -583,4 +592,7 @@ Available video output drivers are:
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever Wayland is present.
|
||||
|
||||
Since mpv 0.30.0, you may need to use ``--profile=sw-fast`` to get decent
|
||||
performance.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
@@ -144,7 +144,6 @@ Mac OS X
|
||||
* Native OpenGL backend.
|
||||
* Cocoa event loop is independent from MPlayer's event loop, so user
|
||||
actions like accessing menus and live resizing do not block the playback.
|
||||
* Apple Remote support.
|
||||
* Media Keys support.
|
||||
* VDA support using libavcodec hwaccel API instead of FFmpeg's decoder with up
|
||||
to 2-2.5x reduction in CPU usage.
|
||||
@@ -248,7 +247,7 @@ Command Line Switches
|
||||
``-msglevel`` ``--msg-level`` (changed semantics)
|
||||
``-msgmodule`` ``--msg-module``
|
||||
``-name`` ``--x11-name``
|
||||
``-noar`` ``--no-input-appleremote``
|
||||
``-noar`` ``(removed; replaced by MediaPlayer framework)``
|
||||
``-noautosub`` ``--no-sub-auto``
|
||||
``-noconsolecontrols`` ``--no-input-terminal``
|
||||
``-nosound`` ``--no-audio``
|
||||
|
||||
@@ -20,24 +20,28 @@ Release procedure
|
||||
|
||||
While on master:
|
||||
|
||||
- Update the `RELEASE_NOTES` file.
|
||||
- Update the `RELEASE_NOTES` file, replacing the previous release notes.
|
||||
|
||||
- Update the `VERSION` file.
|
||||
|
||||
- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst`
|
||||
(in particular, update the last version numbers if necessary)
|
||||
|
||||
- Commit changes.
|
||||
- Create signed commit with changes.
|
||||
|
||||
- Create signed tag v0.X.Y.
|
||||
|
||||
- Add -UNKNOWN suffix to version in `VERSION` file.
|
||||
|
||||
- Commit changes, push branch and tag to GitHub.
|
||||
- Commit changes, push release branch (`release/0.X`) and tag to GitHub.
|
||||
|
||||
- Create a new GitHub release using the content of `RELEASE_NOTES` related to
|
||||
the new version.
|
||||
|
||||
If necessary (to e.g. exclude commits already on master), the release can
|
||||
be done on a branch with different commit history. The release branch **must**
|
||||
then be merged to master so `git describe` will pick up the tag.
|
||||
|
||||
Release notes template
|
||||
----------------------
|
||||
|
||||
|
||||
+421
-7
@@ -15,7 +15,7 @@ player/*.c:
|
||||
* parse command line, add files from the command line to playlist
|
||||
(m_config_parse_mp_command_line())
|
||||
* check help options etc. (call handle_help_options()), possibly exit
|
||||
* call play_files() function that works down the playlist:
|
||||
* call mp_play_files() function that works down the playlist:
|
||||
* run idle loop (idle_loop()), until there are files in the
|
||||
playlist or an exit command was given (only if --idle it set)
|
||||
* actually load and play a file in play_current_file():
|
||||
@@ -30,7 +30,7 @@ player/*.c:
|
||||
* determine next entry on the playlist to play
|
||||
* loop, or exit if no next file or quit is requested
|
||||
(see enum stop_play_reason)
|
||||
* call exit_player_with_rc()
|
||||
* call mp_destroy()
|
||||
* run_playloop():
|
||||
* calls fill_audio_out_buffers()
|
||||
This checks whether new audio needs to be decoded, and pushes it
|
||||
@@ -44,9 +44,9 @@ player/*.c:
|
||||
|
||||
Things worth saying about the playback core:
|
||||
- most state is in MPContext (core.h), which is not available to the
|
||||
subsystems
|
||||
subsystems (and should not be made available)
|
||||
- the currently played tracks are in mpctx->current_tracks, and decoder
|
||||
state in track.d_video/d_audio/d_sub
|
||||
state in track.dec/d_sub
|
||||
- the other subsystems rarely call back into the frontend, and the frontend
|
||||
polls them instead (probably a good thing)
|
||||
- one exceptions are wakeup callbacks, which notify a "higher" component
|
||||
@@ -68,6 +68,7 @@ ta.h & ta.c:
|
||||
as in it doesn't require valgrind.) You can enable it by setting the
|
||||
MPV_LEAK_REPORT environment variable to "1":
|
||||
export MPV_LEAK_REPORT=1
|
||||
Or permanently by building with --enable-ta-leak-report.
|
||||
This will list all unfree'd allocations on exit.
|
||||
|
||||
Documentation can be found here:
|
||||
@@ -103,13 +104,45 @@ options/options.h, options/options.c
|
||||
options.c. Most default values for options and MPOpts are in
|
||||
mp_default_opts at the end of options.c.
|
||||
|
||||
MPOpts is unfortunately quite monolithic, and virtually accessed by
|
||||
everything.But some components (like video outputs and video filters) have
|
||||
their own sub-option tables separate from MPOpts.
|
||||
MPOpts is unfortunately quite monolithic, but is being incrementally broken
|
||||
up into sub-structs. Many components have their own sub-option structs
|
||||
separate from MPOpts. New options should be bound to the component that uses
|
||||
them. Add a new option table/struct if needed.
|
||||
|
||||
The global MPOpts still contains the sub-structs as fields, which serves to
|
||||
link them to the option parser. For example, an entry like this may be
|
||||
typical:
|
||||
|
||||
OPT_SUBSTRUCT("", demux_opts, demux_conf, 0),
|
||||
|
||||
This directs the option access code to include all options in demux_conf
|
||||
into the global option list, with no prefix (""), and as part of the
|
||||
MPOpts.demux_opts field. The MPOpts.demux_opts field is actually not
|
||||
accessed anywhere, and instead demux.c does this:
|
||||
|
||||
struct m_config_cache *opts_cache =
|
||||
m_config_cache_alloc(demuxer, global, &demux_conf);
|
||||
struct demux_opts *opts = opts_cache->opts;
|
||||
|
||||
... to get a copy of its options.
|
||||
|
||||
See m_config.h (below) how to access options.
|
||||
|
||||
The actual option parser is spread over m_option.c, m_config.c, and
|
||||
parse_commandline.c, and uses the option table in options.c.
|
||||
|
||||
options/m_config.h & m_config.c:
|
||||
Code for querying and managing options. This (unfortunately) contains both
|
||||
declarations for the "legacy-ish" global m_config struct, and ways to access
|
||||
options in a threads-safe way anywhere, like m_config_cache_alloc().
|
||||
|
||||
m_config_cache_alloc() lets anyone read, observe, and write options in any
|
||||
thread. The only state it needs is struct mpv_global, which is an opaque
|
||||
type that can be passed "down" the component hierarchy. For safety reasons,
|
||||
you should not pass down any pointers to option structs (like MPOpts), but
|
||||
instead pass down mpv_global, and use m_config_cache_alloc() (or similar)
|
||||
to get a synchronized copy of the options.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input coming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
@@ -241,3 +274,384 @@ sub/:
|
||||
etc/:
|
||||
The file input.conf is actually integrated into the mpv binary by the
|
||||
build system. It contains the default keybindings.
|
||||
|
||||
Best practices and Concepts within mpv
|
||||
======================================
|
||||
|
||||
General contribution etc.
|
||||
-------------------------
|
||||
|
||||
See: DOCS/contribute.md
|
||||
|
||||
Error checking
|
||||
--------------
|
||||
|
||||
If an error is relevant, it should be handled. If it's interesting, log the
|
||||
error. However, mpv often keeps errors silent and reports failures somewhat
|
||||
coarsely by propagating them upwards the caller chain. This is OK, as long as
|
||||
the errors are not very interesting, or would require a developer to debug it
|
||||
anyway (in which case using a debugger would be more convenient, and the
|
||||
developer would need to add temporary debug printfs to get extremely detailed
|
||||
information which would not be appropriate during normal operation).
|
||||
|
||||
Basically, keep a balance on error reporting. But always check them, unless you
|
||||
have a good argument not to.
|
||||
|
||||
Memory allocation errors (OOM) are a special class of errors. Normally such
|
||||
allocation failures are not handled "properly". Instead, abort() is called.
|
||||
(New code should use MP_HANDLE_OOM() for this.) This is done out of laziness and
|
||||
for convenience, and due to the fact that MPlayer/mplayer2 never handled it
|
||||
correctly. (MPlayer varied between handling it correctly, trying to do so but
|
||||
failing, and just not caring, while mplayer2 started using abort() for it.)
|
||||
|
||||
This is justifiable in a number of ways. Error handling paths are notoriously
|
||||
untested and buggy, so merely having them won't make your program more reliable.
|
||||
Having these error handling paths also complicates non-error code, due to the
|
||||
need to roll back state at any point after a memory allocation.
|
||||
|
||||
Take any larger body of code, that is supposed to handle OOM, and test whether
|
||||
the error paths actually work, for example by overriding malloc with a version
|
||||
that randomly fails. You will find bugs quickly, and often they will be very
|
||||
annoying to fix (if you can even reproduce them).
|
||||
|
||||
In addition, a clear indication that something went wrong may be missing. On
|
||||
error your program may exhibit "degraded" behavior by design. Consider a video
|
||||
encoder dropping frames somewhere in the middle of a video due to temporary
|
||||
allocation failures, instead of just exiting with an errors. In other cases, it
|
||||
may open conceptual security holes. Failing fast may be better.
|
||||
|
||||
mpv uses GPU APIs, which may be break on allocation errors (because driver
|
||||
authors will have the same issues as described here), or don't even have a real
|
||||
concept for dealing with OOM (OpenGL).
|
||||
|
||||
libmpv is often used by GUIs, which I predict always break if OOM happens.
|
||||
|
||||
Last but not least, OSes like Linux use "overcommit", which basically means that
|
||||
your program may crash any time OOM happens, even if it doesn't use malloc() at
|
||||
all!
|
||||
|
||||
But still, don't just assume malloc() always succeeds. Use MP_HANDLE_OOM(). The
|
||||
ta* APIs do this for you. The reason for this is that dereferencing a NULL
|
||||
pointer can have security relevant consequences if large offsets are involved.
|
||||
Also, a clear error message is better than a random segfault.
|
||||
|
||||
Some big memory allocations are checked anyway. For example, all code must
|
||||
assume that allocating video frames or packets can fail. (The above example
|
||||
of dropping video frames during encoding is entirely possible in mpv.)
|
||||
|
||||
Undefined behavior
|
||||
------------------
|
||||
|
||||
Undefined behavior (UB) is a concept in the C language. C is famous for being a
|
||||
language that makes it almost impossible to write working code, because
|
||||
undefined behavior is so easily triggered, compilers will happily abuse it to
|
||||
generate "faster" code, debugging tools will shout at you, and sometimes it
|
||||
even means your code doesn't work.
|
||||
|
||||
There is a lot of literature on this topic. Read it.
|
||||
|
||||
(In C's defense, UB exists in other languages too, but since they're not used
|
||||
for low level infrastructure, and/or these languages are at times not rigorously
|
||||
defined, simply nobody cares. However, the C standard committee is still guilty
|
||||
for not addressing this. I'll admit that I can't even tell from the standard's
|
||||
gibberish whether some specific behavior is UB or not. It's written like tax
|
||||
law.)
|
||||
|
||||
In mpv, we generally try to avoid undefined behavior. For one, we want portable
|
||||
and reliable operation. But more importantly, we want clean output from
|
||||
debugging tools, in order to find real bugs more quickly and effectively.
|
||||
|
||||
Avoid the "works in practice" argument. Once debugging tools come into play, or
|
||||
simply when "in practice" stops being true, this will all get back to you in a
|
||||
bad way.
|
||||
|
||||
Global state, library safety
|
||||
----------------------------
|
||||
|
||||
Mutable global state is when code uses global variables that are not read-only.
|
||||
This must be avoided in mpv. Always use context structs that the caller of
|
||||
your code needs to allocate, and whose pointers are passed to your functions.
|
||||
|
||||
Library safety means that your code (or library) can be used by a library
|
||||
without causing conflicts with other library users in the same process. To any
|
||||
piece of code, a "safe" library's API can simply be used, without having to
|
||||
worry about other API users that may be around somewhere.
|
||||
|
||||
Libraries are often not library safe, because they they use global mutable state
|
||||
or other "global" resources. Typical examples include use of signals, simple
|
||||
global variables (like hsearch() in libc), or internal caches not protected by
|
||||
locks.
|
||||
|
||||
A surprisingly high number of libraries are not library safe because they need
|
||||
global initialization. Typically they provide an API function, which
|
||||
"initializes" the library, and which must be called before calling any other
|
||||
API functions. Often, you are to provide global configuration parameters, which
|
||||
can change the behavior of the library. If two libraries A and B use library C,
|
||||
but A and B initialize C with different parameters, something "bad" may happen.
|
||||
In addition, these global initialization functions are often not thread-safe. So
|
||||
if A and B try to initialize C at the same time (from different threads and
|
||||
without knowing about each other), it may cause undefined behavior. (libcurl is
|
||||
a good example of both of these issues. FFmpeg and some TLS libraries used to be
|
||||
affected, but improved.)
|
||||
|
||||
This is so bad because library A and B from the previous example most likely
|
||||
have no way to cooperate, because they're from different authors and have no
|
||||
business knowing each others. They'd need a library D, which wraps library C
|
||||
in a safe way. Unfortunately, typically something worse happens: libraries get
|
||||
"infected" by the unsafeness of its sub-libraries, and export a global init API
|
||||
just to initialize the sub-libraries. In the previous example, libraries A and B
|
||||
would export global init APIs just to init library C, even though the rest of
|
||||
A/B are clean and library safe. (Again, libcurl is an example of this, if you
|
||||
subtract other historic anti-features.)
|
||||
|
||||
The main problem with library safety is that its lack propagates to all
|
||||
libraries using the library.
|
||||
|
||||
We require libmpv to be library safe. This is not really possible, because some
|
||||
libraries are not library safe (FFmpeg, Xlib, partially ALSA). However, for
|
||||
ideological reasons, there is no global init API, and best effort is made to try
|
||||
to avoid problems.
|
||||
|
||||
libmpv has some features that are not library safe, but which are disabled by
|
||||
default (such as terminal usage aka stdout, or JSON IPC blocking SIGPIPE for
|
||||
internal convenience).
|
||||
|
||||
A notable, very disgustingly library unsafe behavior of libmpv is calling
|
||||
abort() on some memory allocation failure. See error checking section.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
All logging and terminal output in mpv goes through the functions and macros
|
||||
provided in common/msg.h. This is in part for library safety, and in part to
|
||||
make sure users can silence all output, or to redirect the output elsewhere,
|
||||
like a log file or the internal console.lua script.
|
||||
|
||||
Locking
|
||||
-------
|
||||
|
||||
See generally available literature. In mpv, we use pthread for this.
|
||||
|
||||
Always keep locking clean. Don't skip locking just because it will work "in
|
||||
practice". (See undefined behavior section.) If your use case is simple, you may
|
||||
use C11 atomics (osdep/atomic.h for partial C99 support), but most likely you
|
||||
will only hurt yourself and others.
|
||||
|
||||
Always make clear which fields in a struct are protected by which lock. If a
|
||||
field is immutable, or simply not thread-safe (e.g. state for a single worker
|
||||
thread), document it as well.
|
||||
|
||||
Internal mpv APIs are assumed to be not thread-safe by default. If they have
|
||||
special guarantees (such as being usable by more than one thread at a time),
|
||||
these should be explicitly documented.
|
||||
|
||||
All internal mpv APIs must be free of global state. Even if a component is not
|
||||
thread-safe, multiple threads can use _different_ instances of it without any
|
||||
locking.
|
||||
|
||||
On a side note, recursive locks may seem convenient at first, but introduce
|
||||
additional problems with condition variables and locking hierarchies. They
|
||||
should be avoided.
|
||||
|
||||
Locking hierarchy
|
||||
-----------------
|
||||
|
||||
A simple way to avoid deadlocks with classic locking is to define a locking
|
||||
hierarchy or lock order. If all threads acquire locks in the same order, no
|
||||
deadlocks will happen.
|
||||
|
||||
For example, a "leaf" lock is a lock that is below all other locks in the
|
||||
hierarchy. You can acquire it any time, as long as you don't acquire other
|
||||
locks while holding it.
|
||||
|
||||
Unfortunately, C has no way to declare or check the lock order, so you should at
|
||||
least document it.
|
||||
|
||||
In addition, try to avoid exposing locks to the outside. Making the declaration
|
||||
of a lock private to a specific .c file (and _not_ exporting accessors or
|
||||
lock/unlock functions that manipulate the lock) is a good idea. Your component's
|
||||
API may acquire internal locks, but should release them when returning. Keeping
|
||||
the entire locking in a single file makes it easy to check it.
|
||||
|
||||
Avoiding callback hell
|
||||
----------------------
|
||||
|
||||
mpv code is separated in components, like the "frontend" (i.e. MPContext mpctx),
|
||||
VOs, AOs, demuxers, and more. The frontend usually calls "down" the usage
|
||||
hierarchy: mpctx almost on top, then things like vo/ao, and utility code on the
|
||||
very bottom.
|
||||
|
||||
"Callback hell" is when when components call both up and down the hierarchy,
|
||||
which for example leads to accidentally recursion, reentrancy problems, or
|
||||
locking nightmares. This is avoided by (mostly) calling only down the hierarchy.
|
||||
Basically the call graph forms a DAG. The other direction is handled by event
|
||||
queues, wakeup callbacks, and similar mechanisms.
|
||||
|
||||
Typically, a component provides an API, and does not know anything about its
|
||||
user. The API user (component higher in the hierarchy) polls the state of the
|
||||
lower component when needed.
|
||||
|
||||
This also enforces some level of modularization, and with some luck the locking
|
||||
hierarchy. (Basically, locks of lower components automatically become leaf
|
||||
locks.) Another positive effect is simpler memory management.
|
||||
|
||||
(Also see e.g.: http://250bpm.com/blog:24)
|
||||
|
||||
Wakeup callbacks
|
||||
----------------
|
||||
|
||||
This is a common concept in mpv. Even the public API uses it. It's used when an
|
||||
API has internal threads (or otherwise triggers asynchronous events), but the
|
||||
component call hierarchy needs to be kept. The wakeup callback is the only
|
||||
exception to the call hierarchy, and always calls up.
|
||||
|
||||
For example, vo spawns a thread that the API user (the mpv frontend) does not
|
||||
need to know about. vo simply provides a single-threaded API (or that looks like
|
||||
one). This API needs a way to notify the API user of new events. But the vo
|
||||
event producer is on the vo thread - it can't simply invoke a callback back into
|
||||
the API user, because then the API user has to deal with locking, despite not
|
||||
using threads. In addition, this will probably cause problems like mentioned in
|
||||
the "callback hell" section, especially lock order issues.
|
||||
|
||||
The solution is the wakeup callback. It merely unblocks the API user from
|
||||
waiting, and the API user then uses the normal vo API to examine whether or
|
||||
which state changed. As a concept, it documents what a wakeup callback is
|
||||
allowed to do and what not, to avoid the aforementioned problems.
|
||||
|
||||
Generally, you are not allowed to call any API from the wakeup callback. You
|
||||
just do whatever is needed to unblock your thread. For example, if it's waiting
|
||||
on a mutex/condition variable, acquire the mutex, set a change flag, signal
|
||||
the condition variable, unlock, return. (This mutex must not be held when
|
||||
calling the API. It must be a leaf lock.)
|
||||
|
||||
Restricting the wakeup callback like this sidesteps any reentrancy issues and
|
||||
other complexities. The API implementation can simply hold internal (and
|
||||
non-recursive) locks while invoking the wakeup callback.
|
||||
|
||||
The API user still needs to deal with locking (probably), but there's only the
|
||||
need to implement a single "receiver", that can handle the entire API of the
|
||||
used component. (Or multiple APIs - MPContext for example has only 1 wakeup
|
||||
callback that handles all AOs, VOs, input, demuxers, and more. It simple re-runs
|
||||
the playloop.)
|
||||
|
||||
You could get something more advanced by turning this into a message queue. The
|
||||
API would append a message to the queue, and the API user can read it. But then
|
||||
you still need a way to "wakeup" the API user (unless you force the API user
|
||||
to block on your API, which will make things inconvenient for the API user). You
|
||||
also need to worry about what happens if the message queue overruns (you either
|
||||
lose messages or have unbounded memory usage). In the mpv public API, the
|
||||
distinction between message queue and wakeup callback is sort of blurry, because
|
||||
it does provide a message queue, but an additional wakeup callback, so API
|
||||
users are not required to call mpv_wait_event() with a high timeout.
|
||||
|
||||
mpv itself prefers using wakeup callbacks over a generic event queue, because
|
||||
most times an event queue is not needed (or complicates things), and it is
|
||||
better to do it manually.
|
||||
|
||||
(You could still abstract the API user side of wakeup callback handling, and
|
||||
avoid reimplementing it all the time. Although mp_dispatch_queue already
|
||||
provides mechanisms for this.)
|
||||
|
||||
Condition variables
|
||||
-------------------
|
||||
|
||||
They're used whenever a thread needs to wait for something, without nonsense
|
||||
like sleep calls or busy waiting. mpv uses the standard pthread API for this.
|
||||
There's a lot of literature on it. Read it.
|
||||
|
||||
For initial understanding, it may be helpful to know that condition variables
|
||||
are not variables that signal a condition. pthread_cond_t does not have any
|
||||
state per-se. Maybe pthread_cond_t would better be named pthread_interrupt_t,
|
||||
because its sole purpose is to interrupt a thread waiting via pthread_cond_wait()
|
||||
(or similar). The "something" in "waiting for something" can be called
|
||||
predicate (to avoid confusing it with "condition"). Consult literature for the
|
||||
proper terms.
|
||||
|
||||
The very short version is...
|
||||
|
||||
Shared declarations:
|
||||
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t cond_var;
|
||||
struct something state_var; // protected by lock, changes signaled by cond_var
|
||||
|
||||
Waiter thread:
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
// Wait for a change in state_var. We want to wait until predicate_fulfilled()
|
||||
// returns true.
|
||||
// Must be a loop for 2 reasons:
|
||||
// 1. cond_var may be associated with other conditions too
|
||||
// 2. pthread_cond_wait() can have sporadic wakeups
|
||||
while (!predicate_fulfilled(&state_var)) {
|
||||
// This unlocks, waits for cond_var to be signaled, and then locks again.
|
||||
// The _whole_ point of cond_var is that unlocking and waiting for the
|
||||
// signal happens atomically.
|
||||
pthread_cond_wait(&cond_var, &lock);
|
||||
}
|
||||
|
||||
// Here you may react to the state change. The state cannot change
|
||||
// asynchronously as long as you still hold the lock (and didn't release
|
||||
// and reacquire it).
|
||||
// ...
|
||||
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Signaler thread:
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
|
||||
// Something changed. Update the shared variable with the new state.
|
||||
update_state(&state_var);
|
||||
|
||||
// Notify that something changed. This will wake up the waiter thread if
|
||||
// it's blocked in pthread_cond_wait(). If not, nothing happens.
|
||||
pthread_cond_broadcast(&cond_var);
|
||||
|
||||
// Fun fact: good implementations wake up the waiter only when the lock is
|
||||
// released, to reduce kernel scheduling overhead.
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
Some basic rules:
|
||||
1. Always access your state under proper locking
|
||||
2. Always check your predicate before every call to pthread_cond_wait()
|
||||
(And don't call pthread_cond_wait() if the predicate is fulfilled.)
|
||||
3. Always call pthread_cond_wait() in a loop
|
||||
(And only if your predicate failed without releasing the lock..)
|
||||
4. Always call pthread_cond_broadcast()/_signal() inside of its associated
|
||||
lock
|
||||
|
||||
mpv sometimes violates rule 3, and leaves "retrying" (i.e. looping) to the
|
||||
caller.
|
||||
|
||||
Common pitfalls:
|
||||
- Thinking that pthread_cond_t is some kind of semaphore, or holds any
|
||||
application state or the user predicate (it _only_ wakes up threads
|
||||
that are at the same time blocking on pthread_cond_wait() and friends,
|
||||
nothing else)
|
||||
- Changing the predicate, but not updating all pthread_cond_broadcast()/
|
||||
_signal() calls correctly
|
||||
- Forgetting that pthread_cond_wait() unlocks the lock (other threads can
|
||||
and must acquire the lock)
|
||||
- Holding multiple nested locks while trying to wait (=> deadlock, violates
|
||||
the lock order anyway)
|
||||
- Waiting for a predicate correctly, but unlocking/relocking before acting
|
||||
on it (unlocking allows arbitrary state changes)
|
||||
- Confusing which lock/condition var. is used to manage a bit of state
|
||||
|
||||
Generally available literature probably has better examples and explanations.
|
||||
|
||||
Using condition variables the proper way is generally preferred over using more
|
||||
messy variants of them. (Just saying because on win32, "SetEvent" exists, and
|
||||
it's inferior to condition variables. Try to avoid the win32 primitives, even if
|
||||
you're dealing with Windows-only code.)
|
||||
|
||||
Threads
|
||||
-------
|
||||
|
||||
Threading should be conservatively used. Normally, mpv code pretends to be
|
||||
single-threaded, and provides thread-unsafe APIs. Threads are used coarsely,
|
||||
and if you can avoid messing with threads, you should. For example, VOs and AOs
|
||||
do not need to deal with threads normally, even though they run on separate
|
||||
threads. The glue code "isolates" them from any threading issues.
|
||||
@@ -20,15 +20,17 @@
|
||||
|
||||
|
||||
* [Wiki](https://github.com/mpv-player/mpv/wiki)
|
||||
* [FAQ](https://github.com/mpv-player/mpv/wiki/FAQ)
|
||||
* [FAQ][FAQ]
|
||||
* [Manual](http://mpv.io/manual/master/)
|
||||
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
**mpv** is a media player based on MPlayer and mplayer2. It supports a wide
|
||||
variety of video file formats, audio and video codecs, and subtitle types.
|
||||
**mpv** is a free (as in freedom) media player for the command line. It supports
|
||||
a wide variety of media file formats, audio and video codecs, and subtitle types.
|
||||
|
||||
There is a [FAQ][FAQ].
|
||||
|
||||
Releases can be found on the [release list][releases].
|
||||
|
||||
@@ -38,11 +40,14 @@ Releases can be found on the [release list][releases].
|
||||
- A somewhat capable CPU. Hardware decoding might help if the CPU is too slow to
|
||||
decode video in realtime, but must be explicitly enabled with the `--hwdec`
|
||||
option.
|
||||
- A not too crappy GPU. mpv is not intended to be used with bad GPUs. There are
|
||||
many caveats with drivers or system compositors causing tearing, stutter,
|
||||
etc. On Windows, you might want to make sure the graphics drivers are
|
||||
current. In some cases, ancient fallback video output methods can help
|
||||
(such as `--vo=xv` on Linux), but this use is not recommended or supported.
|
||||
- A not too crappy GPU. mpv's focus is not on power-efficient playback on
|
||||
embedded or integrated GPUs (for example, hardware decoding is not even
|
||||
enabled by default). Low power GPUs may cause issues like tearing, stutter,
|
||||
etc. The main video output uses shaders for video rendering and scaling,
|
||||
rather than GPU fixed function hardware. On Windows, you might want to make
|
||||
sure the graphics drivers are current. In some cases, ancient fallback video
|
||||
output methods can help (such as `--vo=xv` on Linux), but this use is not
|
||||
recommended or supported.
|
||||
|
||||
|
||||
## Downloads
|
||||
@@ -113,7 +118,7 @@ Essential dependencies (incomplete list):
|
||||
- uchardet (optional, for subtitle charset detection)
|
||||
- nvdec and vaapi libraries for hardware decoding on Linux (optional)
|
||||
|
||||
Libass dependencies:
|
||||
Libass dependencies (when building libass):
|
||||
|
||||
- gcc or clang, yasm on x86 and x86_64
|
||||
- fribidi, freetype, fontconfig development headers (for libass)
|
||||
@@ -121,14 +126,14 @@ Libass dependencies:
|
||||
particularly for correct rendering of non-English text on OSX, and
|
||||
Arabic/Indic scripts on any platform)
|
||||
|
||||
FFmpeg dependencies:
|
||||
FFmpeg dependencies (when building FFmpeg):
|
||||
|
||||
- gcc or clang, yasm on x86 and x86_64
|
||||
- OpenSSL or GnuTLS (have to be explicitly enabled when compiling FFmpeg)
|
||||
- libx264/libmp3lame/libfdk-aac if you want to use encoding (have to be
|
||||
explicitly enabled when compiling FFmpeg)
|
||||
- For native DASH playback, FFmpeg needs to be built with --enable-libxml2
|
||||
(although there are security implications, and DAHS support has lots of bugs).
|
||||
(although there are security implications, and DASH support has lots of bugs).
|
||||
- AV1 decoding support requires dav1d.
|
||||
- For good nvidia support on Linux, make sure nv-codec-headers is installed
|
||||
and can be found by configure.
|
||||
@@ -185,6 +190,11 @@ for ideas on what you could contribute with.
|
||||
GPLv2 "or later" by default, LGPLv2.1 "or later" with `--enable-lgpl`.
|
||||
See [details.](https://github.com/mpv-player/mpv/blob/master/Copyright)
|
||||
|
||||
## History
|
||||
|
||||
This software is based on the MPlayer project. Before mpv existed as a project,
|
||||
the code base was briefly developed under the mplayer2 project. For details,
|
||||
see the [FAQ][FAQ].
|
||||
|
||||
## Contact
|
||||
|
||||
@@ -195,6 +205,7 @@ Most activity happens on the IRC channel and the github issue tracker.
|
||||
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
|
||||
[FAQ]: https://github.com/mpv-player/mpv/wiki/FAQ
|
||||
[releases]: https://github.com/mpv-player/mpv/releases
|
||||
[mpv-build]: https://github.com/mpv-player/mpv-build
|
||||
[issue-tracker]: https://github.com/mpv-player/mpv/issues
|
||||
|
||||
+52
-79
@@ -1,9 +1,7 @@
|
||||
Release 0.30.0
|
||||
Release 0.31.0
|
||||
==============
|
||||
|
||||
This release requires FFmpeg 4.0 or newer.
|
||||
This release has replaced the internal Vulkan rendering abstraction
|
||||
with libplacebo's more up-to-date implementation.
|
||||
|
||||
|
||||
Features
|
||||
@@ -12,54 +10,32 @@ Features
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- vo_gpu: vulkan: hwdec_cuda: Add support for Vulkan interop
|
||||
- command: support for async commands
|
||||
- input: support for named arguments
|
||||
- cocoa-cb: add support for mac 10.14 Dark mode and run time switching
|
||||
- vo_gpu: switch to Vulkan API implementation based on libplacebo
|
||||
- context_drm_egl: Add support for presentation feedback
|
||||
- vo_gpu: implement error diffusion for dithering
|
||||
- vo/gpu: hwdec_vdpau: Support direct mode for 4:4:4 content
|
||||
- vo_gpu: hwdec_vaapi: Add Vulkan interop
|
||||
- video, TOOLS: add vf_fingerprint and a skip-logo script
|
||||
- vo_gpu: d3d11: add support for presentation feedback
|
||||
- vo_drm: 30bpp support
|
||||
- cocoa-cb: add support for 10bit opengl rendering
|
||||
- video: add pure gamma TRC curves for 2.0, 2.4 and 2.6
|
||||
- vo_drm: Implement N-buffering and presentation feedback
|
||||
- Reintroduce vo_wayland as vo_wlshm
|
||||
- video: add zimg wrapper
|
||||
- wayland: add presentation time
|
||||
- input: add gamepad support through SDL2
|
||||
- vo_gpu/d3d11: allow configurable swapchain color space for native HDR output
|
||||
- player: add mechanisms for better logging as preperation for console.lua
|
||||
- audio: add ao_audiotrack for Android
|
||||
- input: introduce a pseudo key name that grabs all text input
|
||||
- wayland: add grab zone for resizing window with mouse
|
||||
- osc: implement pseudo client side decorations via OSC
|
||||
- vf_gpu: add video filter using vo_gpu's renderer
|
||||
- added console.lua: interactive REPL for input commands
|
||||
- osc: display Santa hat for idle logo in December
|
||||
- vd_lavc: add option to autoselect known good hwdec implementations
|
||||
- mac: replace old event tap for media key support with MediaPlayer
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- cocoa-cb: use libmpv's advanced rendering control and timing
|
||||
- vo_gpu: improve tone mapping desaturation
|
||||
- vo_gpu: redesign peak detection algorithm
|
||||
- vo_gpu: allow boosting dark scenes when tone mapping
|
||||
- osc: improve look of seekranges
|
||||
- vo_gpu: x11: remove special vdpau probing, use EGL by default
|
||||
- demux: sort filenames naturally when playing a directory / archive
|
||||
- stream_dvb: rewrite channel switching, remove old stream control
|
||||
- filters: extend vf_format so that it can convert color parameters
|
||||
- input: disable gamepad code by default
|
||||
- vo_gpu: allow vdpau usage under EGL
|
||||
- stream, demux: redo origin policy
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- demux, stream: rip out the classic stream cache
|
||||
- vo_gpu: removed internal Vulkan implementation
|
||||
- Remove libdvdread support in favor of libdvdnav
|
||||
- demux, stream: remove much of the optical disc support
|
||||
- Remove classic Linux analog TV support, and DVB runtime controls
|
||||
- demux, stream: remove old rar support in favor of libarchive
|
||||
- vo_gpu: remove mali-fbdev
|
||||
- vo_gpu: remove vdpau/GLX backend
|
||||
- vf_vapourynth: remove Lua backend
|
||||
- vo_gpu: remove hwdec_d3d11eglrgb
|
||||
- mac: remove Apple Remote support
|
||||
- client API: deprecate tick event
|
||||
|
||||
|
||||
Options and Commands
|
||||
@@ -68,67 +44,64 @@ Options and Commands
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- command: add a subprocess command
|
||||
- vo_gpu: added --tone-mapping-max-boost, --hdr-peak-decay-rate, --hdr-scene-threshold-low/high
|
||||
- player: add --demuxer-cache-wait option
|
||||
- Implement backwards playback
|
||||
- demux: add a on-disk cache
|
||||
- demux, command: add another stream recording mechanism, `dump-cache` command
|
||||
- ao_pulse: add --pulse-allow-suspended
|
||||
- command: add video-add/video-remove/video-reload commands
|
||||
- demux_cue: added --demuxer-cue-codepage to select CUE sheet charset
|
||||
- input: add keybind command
|
||||
- command: add sub-start & sub-end properties
|
||||
- video/d3d11: added --d3d11-adapter to select a rendering adapter by name
|
||||
- vo_gpu/d3d11: added --d3d11-output-format for configuring swap chain format
|
||||
- vo_gpu/d3d11: add --d3d11-output-csp for configuring swap chain color space
|
||||
- command: add command-list property
|
||||
- command, input: add input-bindings property
|
||||
- command: add --window-maximized and make --window-minimized settable
|
||||
- command: add property returning hidpi scale
|
||||
- command, lua: add a way to share data between scripts
|
||||
- client API, lua: add new API for setting OSD overlays
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- vo_gpu: split --linear-scaling into --linear-upscaling and --linear-downscaling
|
||||
- vo_gpu: split --tone-mapping-desaturate into strength and exponent
|
||||
- drm: rename --drm-osd-plane-id to --drm-draw-plane, --drm-video-plane-id to --drm-drmprime-video-plane, --drm-osd-size to --drm-draw-surface-size
|
||||
- add a builtin "sw-fast" profile, which restores performance settings
|
||||
for software video conversion. These were switched to higher quality.
|
||||
This affects video outputs like vo_x11 and vo_drm, and screenshots.
|
||||
- options: make --show-profile without parameters list all profiles
|
||||
- wayland: support for --hidpi-window-scale option
|
||||
- stream_lavf: set --network-timeout to 60 seconds by default
|
||||
- x11: scale window-scale by DPI
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate --video-aspect, replaced by --video-aspect-override
|
||||
- options: deprecate --record-file, --spirv-compiler
|
||||
|
||||
- deprecate --input-file
|
||||
- deprecate --video-sync=display-adrop
|
||||
- deprecate all input section commands
|
||||
- deprecate --display-fps, replaced by --override-display-fps
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- options: remove deprecated --chapter option
|
||||
- demux: remove "program" property
|
||||
- stream: remove "disc-titles", "disc-title", "disc-title-list", and "angle" properties
|
||||
- cocoa-cb: remove --macos-title-bar-style, replaced by --macos-title-bar-material and --macos-title-bar-appearance
|
||||
- spirv: remove support for --spirv-compiler=nvidia (shaderc is the only remaining option)
|
||||
- remove deprecated --playlist-pos alias
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- context_drm_egl: implement n-buffering
|
||||
- cocoa-cb: add support for custom colored title bar
|
||||
- vo_gpu: x11egl: support Mesa OML sync extension
|
||||
- demux, demux_edl: add extension for tracks sourced from separate streams
|
||||
- context_drm_egl: Use eglGetPlatformDisplayEXT if available
|
||||
- vo_gpu: vulkan: add Android context
|
||||
- wayland: use xdg-decoration if available
|
||||
- vo_gpu: glx: use GLX_OML_sync_control for better vsync reporting
|
||||
- demux: runtime option changing for cache and stream recording
|
||||
- x11: fix ICC profiling for multiple monitors
|
||||
- zsh completion: move generation to runtime and improve
|
||||
- vo_drm, vo_x11: allow use of zimg
|
||||
- vo_sdl: Support mouse wheel input, Improve mouse button input
|
||||
- video: fix track switching work for external images
|
||||
- vo_gpu: fix infinite scaler reinit spam
|
||||
- f_output_chain: fix possible crash when changing filters
|
||||
- vo_gpu: opengl: add hack for ancient Mesa/GLX
|
||||
- demux_lavf: export demuxer_id for more formats which have it
|
||||
- lua: make later key bindings always have higher priority
|
||||
- drm_common: fix display FPS estimation for interlaced modes
|
||||
- vo_drm: fix incorrect pixel format possibly being picked for 30-bit
|
||||
- zsh completion: fix pcre usage to really be optional
|
||||
- options: increase consistency between list options and document them
|
||||
- osc: reduce unnecesary CPU usage by using property notifications
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.29.1..v0.30.0`
|
||||
A complete changelog can be seen by running `git log v0.30.0..v0.31.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.29.1...v0.30.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.29.1..v0.30.0
|
||||
https://github.com/mpv-player/mpv/compare/v0.30.0...v0.31.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.30.0..v0.31.0
|
||||
+8
-22
@@ -61,29 +61,19 @@ def libraries(objfile, result = dict()):
|
||||
|
||||
return result
|
||||
|
||||
def leafs(libs_dict, processed = []):
|
||||
result = []
|
||||
processed = set(processed)
|
||||
|
||||
for objfile, libs in libs_dict.items():
|
||||
if libs <= processed:
|
||||
result.append(objfile)
|
||||
|
||||
return result
|
||||
|
||||
def lib_path(binary):
|
||||
return os.path.join(os.path.dirname(binary), 'lib')
|
||||
|
||||
def lib_name(lib):
|
||||
return os.path.join("@executable_path", "lib", os.path.basename(lib))
|
||||
|
||||
def process_libraries(libs_dict, binary, processed = []):
|
||||
ls = leafs(libs_dict, processed)
|
||||
diff = set(ls) - set(processed)
|
||||
if diff == set([binary]):
|
||||
return
|
||||
def process_libraries(libs_dict, binary):
|
||||
libs_set = set(libs_dict)
|
||||
# Remove binary from libs_set to prevent a duplicate of the binary being
|
||||
# added to the libs directory.
|
||||
libs_set.remove(binary)
|
||||
|
||||
for src in diff:
|
||||
for src in libs_set:
|
||||
name = lib_name(src)
|
||||
dst = os.path.join(lib_path(binary), os.path.basename(src))
|
||||
|
||||
@@ -94,12 +84,10 @@ def process_libraries(libs_dict, binary, processed = []):
|
||||
if src in libs_dict[binary]:
|
||||
install_name_tool_change(src, name, binary)
|
||||
|
||||
for p in processed:
|
||||
for p in libs_set:
|
||||
if p in libs_dict[src]:
|
||||
install_name_tool_change(p, lib_name(p), dst)
|
||||
|
||||
return ls
|
||||
|
||||
def process_swift_libraries(binary):
|
||||
command = ['xcrun', '--find', 'swift-stdlib-tool']
|
||||
swiftStdlibTool = subprocess.check_output(command, universal_newlines=True).strip()
|
||||
@@ -130,9 +118,7 @@ def main():
|
||||
libs = libraries(binary)
|
||||
|
||||
print(">> copying and processing all linked libraries")
|
||||
libs_processed = process_libraries(libs, binary)
|
||||
while libs_processed is not None:
|
||||
libs_processed = process_libraries(libs, binary, libs_processed)
|
||||
process_libraries(libs, binary)
|
||||
|
||||
print(">> removing rpath definitions towards dev tools")
|
||||
remove_dev_tools_rapths(binary)
|
||||
|
||||
@@ -22,7 +22,7 @@ OS2Version: 3
|
||||
OS2_WeightWidthSlopeOnly: 0
|
||||
OS2_UseTypoMetrics: 0
|
||||
CreationTime: 1408646554
|
||||
ModificationTime: 1492879730
|
||||
ModificationTime: 1576096543
|
||||
PfmFamily: 81
|
||||
TTFWeight: 400
|
||||
TTFWidth: 5
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
StartChar: uniE112
|
||||
Encoding: 57618 57618 30
|
||||
Width: 768
|
||||
VWidth: 1176
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
512 40 m 1
|
||||
0 40 l 1
|
||||
0 168 l 1
|
||||
512 168 l 1
|
||||
512 40 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -0,0 +1,20 @@
|
||||
StartChar: uniE113
|
||||
Encoding: 57619 57619 31
|
||||
Width: 622
|
||||
VWidth: 1178
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
768 42 m 5
|
||||
0 42 l 5
|
||||
0 746 l 5
|
||||
768 746 l 5
|
||||
768 42 l 5
|
||||
704 106 m 5
|
||||
704 618 l 5
|
||||
64 618 l 5
|
||||
64 106 l 5
|
||||
704 106 l 5
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -0,0 +1,31 @@
|
||||
StartChar: uniE114
|
||||
Encoding: 57620 57620 32
|
||||
Width: 896
|
||||
VWidth: 1178
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
768 298 m 1
|
||||
576 298 l 1
|
||||
576 42 l 1
|
||||
0 42 l 1
|
||||
0 490 l 1
|
||||
192 490 l 1
|
||||
192 746 l 1
|
||||
768 746 l 1
|
||||
768 298 l 1
|
||||
704 362 m 1
|
||||
704 618 l 1
|
||||
256 618 l 1
|
||||
256 490 l 1
|
||||
576 490 l 1
|
||||
576 362 l 1
|
||||
704 362 l 1
|
||||
512 106 m 1
|
||||
512 362 l 1
|
||||
64 362 l 1
|
||||
64 106 l 1
|
||||
512 106 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
@@ -0,0 +1,27 @@
|
||||
StartChar: uniE115
|
||||
Encoding: 57621 57621 33
|
||||
Width: 844
|
||||
VWidth: 1112
|
||||
Flags: HW
|
||||
LayerCount: 2
|
||||
Fore
|
||||
SplineSet
|
||||
671 40 m 1
|
||||
575 40 l 1
|
||||
335 277 l 1
|
||||
98 40 l 1
|
||||
-1 40 l 1
|
||||
-1 136 l 1
|
||||
236 376 l 1
|
||||
-1 613 l 1
|
||||
-1 712 l 1
|
||||
98 712 l 1
|
||||
335 475 l 1
|
||||
575 712 l 1
|
||||
671 712 l 1
|
||||
671 613 l 1
|
||||
434 376 l 1
|
||||
671 139 l 1
|
||||
671 40 l 1
|
||||
EndSplineSet
|
||||
EndChar
|
||||
+13
-3
@@ -410,10 +410,16 @@ void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= mp_aframe_get_size(f));
|
||||
|
||||
if (av_frame_make_writable(f->av_frame) < 0)
|
||||
return; // go complain to ffmpeg
|
||||
|
||||
int num_planes = mp_aframe_get_planes(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
f->av_frame->extended_data[n] += samples * sstride;
|
||||
for (int n = 0; n < num_planes; n++) {
|
||||
memmove(f->av_frame->extended_data[n],
|
||||
f->av_frame->extended_data[n] + samples * sstride,
|
||||
(f->av_frame->nb_samples - samples) * sstride);
|
||||
}
|
||||
|
||||
f->av_frame->nb_samples -= samples;
|
||||
|
||||
@@ -592,7 +598,11 @@ int mp_aframe_pool_allocate(struct mp_aframe_pool *pool, struct mp_aframe *frame
|
||||
{
|
||||
int planes = mp_aframe_get_planes(frame);
|
||||
size_t sstride = mp_aframe_get_sstride(frame);
|
||||
int plane_size = MP_ALIGN_UP(sstride * MPMAX(samples, 1), 32);
|
||||
// FFmpeg hardcodes similar hidden possibly-requirements in a number of
|
||||
// places: av_frame_get_buffer(), libavcodec's get_buffer(), mem.c,
|
||||
// probably more.
|
||||
int align_samples = MP_ALIGN_UP(MPMAX(samples, 1), 32);
|
||||
int plane_size = MP_ALIGN_UP(sstride * align_samples, 64);
|
||||
int size = plane_size * planes;
|
||||
|
||||
if (size <= 0 || mp_aframe_is_allocated(frame))
|
||||
|
||||
+11
-15
@@ -48,7 +48,7 @@ struct priv {
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
AVRational codec_timebase;
|
||||
bool eof_returned;
|
||||
struct lavc_state state;
|
||||
|
||||
struct mp_decoder public;
|
||||
};
|
||||
@@ -81,7 +81,7 @@ static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
struct MPOpts *mpopts = mp_get_config_group(ctx, da->global, GLOBAL_CONFIG);
|
||||
struct MPOpts *mpopts = mp_get_config_group(ctx, da->global, &mp_opt_root);
|
||||
struct ad_lavc_params *opts =
|
||||
mp_get_config_group(ctx, da->global, &ad_lavc_conf);
|
||||
AVCodecContext *lavc_context;
|
||||
@@ -159,10 +159,10 @@ static void reset(struct mp_filter *da)
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
ctx->eof_returned = false;
|
||||
ctx->state = (struct lavc_state){0};
|
||||
}
|
||||
|
||||
static bool send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
static int send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -177,16 +177,12 @@ static bool send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
|
||||
|
||||
int ret = avcodec_send_packet(avctx, mpkt ? &pkt : NULL);
|
||||
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
return false;
|
||||
|
||||
if (ret < 0)
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
static int receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -198,7 +194,7 @@ static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
// over in case we get new packets at some point in the future.
|
||||
// (Dont' reset the filter itself, we want to keep other state.)
|
||||
avcodec_flush_buffers(priv->avctx);
|
||||
return false;
|
||||
return ret;
|
||||
} else if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
}
|
||||
@@ -209,14 +205,14 @@ static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
#endif
|
||||
|
||||
if (!priv->avframe->buf[0])
|
||||
return true;
|
||||
return ret;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_aframe *mpframe = mp_aframe_from_avframe(priv->avframe);
|
||||
if (!mpframe) {
|
||||
MP_ERR(da, "Converting libavcodec frame to mpv frame failed.\n");
|
||||
return true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (priv->force_channel_map.num)
|
||||
@@ -264,14 +260,14 @@ static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
return true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *ad)
|
||||
{
|
||||
struct priv *priv = ad->priv;
|
||||
|
||||
lavc_process(ad, &priv->eof_returned, send_packet, receive_frame);
|
||||
lavc_process(ad, &priv->state, send_packet, receive_frame);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_lavc_filter = {
|
||||
|
||||
+7
-3
@@ -36,6 +36,7 @@
|
||||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_audiotrack;
|
||||
extern const struct ao_driver audio_out_audiounit;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
extern const struct ao_driver audio_out_coreaudio_exclusive;
|
||||
@@ -54,6 +55,9 @@ extern const struct ao_driver audio_out_sdl;
|
||||
|
||||
static const struct ao_driver * const audio_out_drivers[] = {
|
||||
// native:
|
||||
#if HAVE_ANDROID
|
||||
&audio_out_audiotrack,
|
||||
#endif
|
||||
#if HAVE_AUDIOUNIT
|
||||
&audio_out_audiounit,
|
||||
#endif
|
||||
@@ -132,11 +136,11 @@ static const struct m_obj_list ao_obj_list = {
|
||||
#define OPT_BASE_STRUCT struct ao_opts
|
||||
const struct m_sub_options ao_conf = {
|
||||
.opts = (const struct m_option[]) {
|
||||
OPT_SETTINGSLIST("ao", audio_driver_list, 0, &ao_obj_list, ),
|
||||
OPT_SETTINGSLIST("ao", audio_driver_list, UPDATE_AUDIO, &ao_obj_list, ),
|
||||
OPT_STRING("audio-device", audio_device, UPDATE_AUDIO),
|
||||
OPT_STRING("audio-client-name", audio_client_name, UPDATE_AUDIO),
|
||||
OPT_DOUBLE("audio-buffer", audio_buffer, M_OPT_MIN | M_OPT_MAX,
|
||||
.min = 0, .max = 10),
|
||||
OPT_DOUBLE("audio-buffer", audio_buffer,
|
||||
UPDATE_AUDIO | M_OPT_MIN | M_OPT_MAX, .min = 0, .max = 10),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(OPT_BASE_STRUCT),
|
||||
|
||||
@@ -0,0 +1,717 @@
|
||||
/*
|
||||
* Android AudioTrack audio output driver.
|
||||
* Copyright (C) 2018 Aman Gupta <aman@tmm1.net>
|
||||
* Copyright (C) 2012-2015 VLC authors and VideoLAN, VideoLabs
|
||||
* Authors: Thomas Guillem <thomas@gllm.fr>
|
||||
* Ming Hu <tewilove@gmail.com>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/format.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/threads.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "misc/jni.h"
|
||||
|
||||
struct priv {
|
||||
jobject audiotrack;
|
||||
jint samplerate;
|
||||
jint channel_config;
|
||||
jint format;
|
||||
jint size;
|
||||
|
||||
jobject timestamp;
|
||||
int64_t timestamp_fetched;
|
||||
bool timestamp_set;
|
||||
int timestamp_stable;
|
||||
|
||||
uint32_t written_frames; /* requires uint32_t rollover semantics */
|
||||
uint32_t playhead_pos;
|
||||
uint32_t playhead_offset;
|
||||
bool reset_pending;
|
||||
|
||||
void *chunk;
|
||||
int chunksize;
|
||||
jbyteArray bytearray;
|
||||
jshortArray shortarray;
|
||||
jfloatArray floatarray;
|
||||
jobject bbuf;
|
||||
|
||||
int cfg_pcm_float;
|
||||
int cfg_session_id;
|
||||
|
||||
bool needs_timestamp_offset;
|
||||
int64_t timestamp_offset;
|
||||
|
||||
bool thread_terminate;
|
||||
bool thread_created;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t lock;
|
||||
pthread_cond_t wakeup;
|
||||
};
|
||||
|
||||
struct JNIByteBuffer {
|
||||
jclass clazz;
|
||||
jmethodID clear;
|
||||
struct MPJniField mapping[];
|
||||
} ByteBuffer = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIByteBuffer, member)
|
||||
{"java/nio/ByteBuffer", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"java/nio/ByteBuffer", "clear", "()Ljava/nio/Buffer;", MP_JNI_METHOD, OFFSET(clear), 1},
|
||||
{0},
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioTrack {
|
||||
jclass clazz;
|
||||
jmethodID ctor;
|
||||
jmethodID release;
|
||||
jmethodID getState;
|
||||
jmethodID getPlayState;
|
||||
jmethodID play;
|
||||
jmethodID stop;
|
||||
jmethodID flush;
|
||||
jmethodID pause;
|
||||
jmethodID write;
|
||||
jmethodID writeFloat;
|
||||
jmethodID writeV23;
|
||||
jmethodID writeShortV23;
|
||||
jmethodID writeBufferV21;
|
||||
jmethodID getPlaybackHeadPosition;
|
||||
jmethodID getTimestamp;
|
||||
jmethodID getLatency;
|
||||
jmethodID getMinBufferSize;
|
||||
jmethodID getNativeOutputSampleRate;
|
||||
jint STATE_INITIALIZED;
|
||||
jint PLAYSTATE_STOPPED;
|
||||
jint PLAYSTATE_PAUSED;
|
||||
jint PLAYSTATE_PLAYING;
|
||||
jint MODE_STREAM;
|
||||
jint ERROR;
|
||||
jint ERROR_BAD_VALUE;
|
||||
jint ERROR_INVALID_OPERATION;
|
||||
jint WRITE_BLOCKING;
|
||||
jint WRITE_NON_BLOCKING;
|
||||
struct MPJniField mapping[];
|
||||
} AudioTrack = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioTrack, member)
|
||||
{"android/media/AudioTrack", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioTrack", "<init>", "(IIIIIII)V", MP_JNI_METHOD, OFFSET(ctor), 1},
|
||||
{"android/media/AudioTrack", "release", "()V", MP_JNI_METHOD, OFFSET(release), 1},
|
||||
{"android/media/AudioTrack", "getState", "()I", MP_JNI_METHOD, OFFSET(getState), 1},
|
||||
{"android/media/AudioTrack", "getPlayState", "()I", MP_JNI_METHOD, OFFSET(getPlayState), 1},
|
||||
{"android/media/AudioTrack", "play", "()V", MP_JNI_METHOD, OFFSET(play), 1},
|
||||
{"android/media/AudioTrack", "stop", "()V", MP_JNI_METHOD, OFFSET(stop), 1},
|
||||
{"android/media/AudioTrack", "flush", "()V", MP_JNI_METHOD, OFFSET(flush), 1},
|
||||
{"android/media/AudioTrack", "pause", "()V", MP_JNI_METHOD, OFFSET(pause), 1},
|
||||
{"android/media/AudioTrack", "write", "([BII)I", MP_JNI_METHOD, OFFSET(write), 1},
|
||||
{"android/media/AudioTrack", "write", "([FIII)I", MP_JNI_METHOD, OFFSET(writeFloat), 1},
|
||||
{"android/media/AudioTrack", "write", "([BIII)I", MP_JNI_METHOD, OFFSET(writeV23), 0},
|
||||
{"android/media/AudioTrack", "write", "([SIII)I", MP_JNI_METHOD, OFFSET(writeShortV23), 0},
|
||||
{"android/media/AudioTrack", "write", "(Ljava/nio/ByteBuffer;II)I", MP_JNI_METHOD, OFFSET(writeBufferV21), 1},
|
||||
{"android/media/AudioTrack", "getTimestamp", "(Landroid/media/AudioTimestamp;)Z", MP_JNI_METHOD, OFFSET(getTimestamp), 1},
|
||||
{"android/media/AudioTrack", "getPlaybackHeadPosition", "()I", MP_JNI_METHOD, OFFSET(getPlaybackHeadPosition), 1},
|
||||
{"android/media/AudioTrack", "getLatency", "()I", MP_JNI_METHOD, OFFSET(getLatency), 1},
|
||||
{"android/media/AudioTrack", "getMinBufferSize", "(III)I", MP_JNI_STATIC_METHOD, OFFSET(getMinBufferSize), 1},
|
||||
{"android/media/AudioTrack", "getNativeOutputSampleRate", "(I)I", MP_JNI_STATIC_METHOD, OFFSET(getNativeOutputSampleRate), 1},
|
||||
{"android/media/AudioTrack", "WRITE_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_BLOCKING), 0},
|
||||
{"android/media/AudioTrack", "WRITE_NON_BLOCKING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(WRITE_NON_BLOCKING), 0},
|
||||
{"android/media/AudioTrack", "STATE_INITIALIZED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STATE_INITIALIZED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_STOPPED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_STOPPED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_PAUSED", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PAUSED), 1},
|
||||
{"android/media/AudioTrack", "PLAYSTATE_PLAYING", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(PLAYSTATE_PLAYING), 1},
|
||||
{"android/media/AudioTrack", "MODE_STREAM", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(MODE_STREAM), 1},
|
||||
{"android/media/AudioTrack", "ERROR", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR), 1},
|
||||
{"android/media/AudioTrack", "ERROR_BAD_VALUE", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_BAD_VALUE), 1},
|
||||
{"android/media/AudioTrack", "ERROR_INVALID_OPERATION", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_INVALID_OPERATION), 1},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioFormat {
|
||||
jclass clazz;
|
||||
jint ENCODING_PCM_8BIT;
|
||||
jint ENCODING_PCM_16BIT;
|
||||
jint ENCODING_PCM_FLOAT;
|
||||
jint ENCODING_IEC61937;
|
||||
jint ENCODING_AC3;
|
||||
jint CHANNEL_OUT_MONO;
|
||||
jint CHANNEL_OUT_STEREO;
|
||||
jint CHANNEL_OUT_FRONT_LEFT;
|
||||
jint CHANNEL_OUT_FRONT_RIGHT;
|
||||
jint CHANNEL_OUT_BACK_LEFT;
|
||||
jint CHANNEL_OUT_BACK_RIGHT;
|
||||
jint CHANNEL_OUT_FRONT_CENTER;
|
||||
jint CHANNEL_OUT_LOW_FREQUENCY;
|
||||
jint CHANNEL_OUT_BACK_CENTER;
|
||||
jint CHANNEL_OUT_5POINT1;
|
||||
jint CHANNEL_OUT_SIDE_LEFT;
|
||||
jint CHANNEL_OUT_SIDE_RIGHT;
|
||||
struct MPJniField mapping[];
|
||||
} AudioFormat = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioFormat, member)
|
||||
{"android/media/AudioFormat", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_8BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_8BIT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_16BIT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_16BIT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_PCM_FLOAT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_PCM_FLOAT), 1},
|
||||
{"android/media/AudioFormat", "ENCODING_AC3", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_AC3), 0},
|
||||
{"android/media/AudioFormat", "ENCODING_IEC61937", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ENCODING_IEC61937), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_MONO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_MONO), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_STEREO", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_STEREO), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_5POINT1", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_5POINT1), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_LEFT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_RIGHT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_FRONT_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_FRONT_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_LOW_FREQUENCY", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_LOW_FREQUENCY), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_LEFT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_RIGHT), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_BACK_CENTER", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_BACK_CENTER), 1},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_SIDE_LEFT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_SIDE_LEFT), 0},
|
||||
{"android/media/AudioFormat", "CHANNEL_OUT_SIDE_RIGHT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(CHANNEL_OUT_SIDE_RIGHT), 0},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioManager {
|
||||
jclass clazz;
|
||||
jint ERROR_DEAD_OBJECT;
|
||||
jint STREAM_MUSIC;
|
||||
struct MPJniField mapping[];
|
||||
} AudioManager = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioManager, member)
|
||||
{"android/media/AudioManager", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioManager", "STREAM_MUSIC", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(STREAM_MUSIC), 1},
|
||||
{"android/media/AudioManager", "ERROR_DEAD_OBJECT", "I", MP_JNI_STATIC_FIELD_AS_INT, OFFSET(ERROR_DEAD_OBJECT), 0},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
struct JNIAudioTimestamp {
|
||||
jclass clazz;
|
||||
jmethodID ctor;
|
||||
jfieldID framePosition;
|
||||
jfieldID nanoTime;
|
||||
struct MPJniField mapping[];
|
||||
} AudioTimestamp = {.mapping = {
|
||||
#define OFFSET(member) offsetof(struct JNIAudioTimestamp, member)
|
||||
{"android/media/AudioTimestamp", NULL, NULL, MP_JNI_CLASS, OFFSET(clazz), 1},
|
||||
{"android/media/AudioTimestamp", "<init>", "()V", MP_JNI_METHOD, OFFSET(ctor), 1},
|
||||
{"android/media/AudioTimestamp", "framePosition", "J", MP_JNI_FIELD, OFFSET(framePosition), 1},
|
||||
{"android/media/AudioTimestamp", "nanoTime", "J", MP_JNI_FIELD, OFFSET(nanoTime), 1},
|
||||
{0}
|
||||
#undef OFFSET
|
||||
}};
|
||||
|
||||
static int AudioTrack_New(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
|
||||
jobject audiotrack = MP_JNI_NEW(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.ctor,
|
||||
AudioManager.STREAM_MUSIC,
|
||||
p->samplerate,
|
||||
p->channel_config,
|
||||
p->format,
|
||||
p->size,
|
||||
AudioTrack.MODE_STREAM,
|
||||
p->cfg_session_id
|
||||
);
|
||||
if (!audiotrack || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTrack Init failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (MP_JNI_CALL_INT(audiotrack, AudioTrack.getState) != AudioTrack.STATE_INITIALIZED) {
|
||||
MP_JNI_CALL_VOID(audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteLocalRef(env, audiotrack);
|
||||
MP_ERR(ao, "AudioTrack.getState failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
p->audiotrack = (*env)->NewGlobalRef(env, audiotrack);
|
||||
(*env)->DeleteLocalRef(env, audiotrack);
|
||||
if (!p->audiotrack)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int AudioTrack_Recreate(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteGlobalRef(env, p->audiotrack);
|
||||
p->audiotrack = NULL;
|
||||
return AudioTrack_New(ao);
|
||||
}
|
||||
|
||||
static uint32_t AudioTrack_getPlaybackHeadPosition(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return 0;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
uint32_t pos = 0;
|
||||
int64_t now = mp_raw_time_us();
|
||||
int state = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlayState);
|
||||
|
||||
int stable_count = 20;
|
||||
int64_t wait = p->timestamp_stable < stable_count ? 50000 : 3000000;
|
||||
|
||||
if (state == AudioTrack.PLAYSTATE_PLAYING && p->format != AudioFormat.ENCODING_IEC61937 &&
|
||||
(p->timestamp_fetched == 0 || now - p->timestamp_fetched >= wait)) {
|
||||
if (!p->timestamp_fetched)
|
||||
p->timestamp_stable = 0;
|
||||
|
||||
int64_t utime1 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
if (MP_JNI_CALL_BOOL(p->audiotrack, AudioTrack.getTimestamp, p->timestamp)) {
|
||||
p->timestamp_set = true;
|
||||
p->timestamp_fetched = now;
|
||||
if (p->timestamp_stable < stable_count) {
|
||||
uint32_t fpos = 0xFFFFFFFFL & MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.framePosition);
|
||||
int64_t utime2 = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
//MP_VERBOSE(ao, "getTimestamp: fpos= %u / time= %"PRId64" / now= %"PRId64" / stable= %d\n", fpos, utime2, now, p->timestamp_stable);
|
||||
if (utime1 != utime2 && utime2 != 0 && fpos != 0) {
|
||||
p->timestamp_stable++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* AudioTrack's framePosition and playbackHeadPosition return a signed integer,
|
||||
* but documentation states it should be interpreted as a 32-bit unsigned integer.
|
||||
*/
|
||||
if (p->timestamp_set) {
|
||||
pos = 0xFFFFFFFFL & MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.framePosition);
|
||||
uint32_t fpos = pos;
|
||||
int64_t utime = MP_JNI_GET_LONG(p->timestamp, AudioTimestamp.nanoTime) / 1000;
|
||||
if (utime == 0)
|
||||
fpos = pos = 0;
|
||||
if (p->needs_timestamp_offset) {
|
||||
if (utime != 0 && !p->timestamp_offset)
|
||||
p->timestamp_offset = now - utime;
|
||||
utime += p->timestamp_offset;
|
||||
}
|
||||
if (fpos != 0 && utime != 0 && state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
double diff = (double)(now - utime) / 1e6;
|
||||
pos += diff * ao->samplerate;
|
||||
}
|
||||
//MP_VERBOSE(ao, "position = %u via getTimestamp (state = %d / fpos= %u / time= %"PRId64")\n", pos, state, fpos, utime);
|
||||
} else {
|
||||
pos = 0xFFFFFFFFL & MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlaybackHeadPosition);
|
||||
//MP_VERBOSE(ao, "playbackHeadPosition = %u (reset_pending=%d)\n", pos, p->reset_pending);
|
||||
}
|
||||
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
if (p->reset_pending) {
|
||||
// after a flush(), playbackHeadPosition will not reset to 0 right away.
|
||||
// sometimes, it will never reset at all.
|
||||
// save the initial offset after the reset, to subtract it going forward.
|
||||
if (p->playhead_offset == 0)
|
||||
p->playhead_offset = pos;
|
||||
p->reset_pending = false;
|
||||
MP_VERBOSE(ao, "IEC/playbackHead offset = %d\n", pos);
|
||||
}
|
||||
|
||||
// usually shortly after a flush(), playbackHeadPosition will reset to 0.
|
||||
// clear out the position and offset to avoid regular "rollover" below
|
||||
if (pos == 0 && p->playhead_offset != 0) {
|
||||
MP_VERBOSE(ao, "IEC/playbackHeadPosition %d -> %d (flush)\n", p->playhead_pos, pos);
|
||||
p->playhead_offset = 0;
|
||||
p->playhead_pos = 0;
|
||||
}
|
||||
|
||||
// sometimes on a new AudioTrack instance, playbackHeadPosition will reset
|
||||
// to 0 shortly after playback starts for no reason.
|
||||
if (pos == 0 && p->playhead_pos != 0) {
|
||||
MP_VERBOSE(ao, "IEC/playbackHeadPosition %d -> %d (reset)\n", p->playhead_pos, pos);
|
||||
p->playhead_offset = 0;
|
||||
p->playhead_pos = 0;
|
||||
p->written_frames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
p->playhead_pos = pos;
|
||||
return p->playhead_pos - p->playhead_offset;
|
||||
}
|
||||
|
||||
static double AudioTrack_getLatency(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return 0;
|
||||
|
||||
uint32_t playhead = AudioTrack_getPlaybackHeadPosition(ao);
|
||||
uint32_t diff = p->written_frames - playhead;
|
||||
double delay = diff / (double)(ao->samplerate);
|
||||
if (!p->timestamp_set &&
|
||||
p->format != AudioFormat.ENCODING_IEC61937)
|
||||
delay += (double)MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getLatency)/1000.0;
|
||||
if (delay > 1.0) {
|
||||
//MP_WARN(ao, "getLatency: written=%u playhead=%u diff=%u delay=%f\n", p->written_frames, playhead, diff, delay);
|
||||
p->timestamp_fetched = 0;
|
||||
return 0;
|
||||
}
|
||||
return MPCLAMP(delay, 0.0, 1.0);
|
||||
}
|
||||
|
||||
static int AudioTrack_write(struct ao *ao, int len)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack)
|
||||
return -1;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
void *buf = p->chunk;
|
||||
|
||||
jint ret;
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
(*env)->SetShortArrayRegion(env, p->shortarray, 0, len / 2, buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeShortV23, p->shortarray, 0, len / 2, AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
if (ret > 0) ret *= 2;
|
||||
|
||||
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
(*env)->SetFloatArrayRegion(env, p->floatarray, 0, len / sizeof(float), buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeFloat, p->floatarray, 0, len / sizeof(float), AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
if (ret > 0) ret *= sizeof(float);
|
||||
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
jobject bbuf = MP_JNI_CALL_OBJECT(p->bbuf, ByteBuffer.clear);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.writeBufferV21, p->bbuf, len, AudioTrack.WRITE_BLOCKING);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
|
||||
} else {
|
||||
(*env)->SetByteArrayRegion(env, p->bytearray, 0, len, buf);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
ret = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.write, p->bytearray, 0, len);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) < 0) return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void uninit_jni(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
mp_jni_reset_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log);
|
||||
mp_jni_reset_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log);
|
||||
}
|
||||
|
||||
static int init_jni(struct ao *ao)
|
||||
{
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (mp_jni_init_jfields(env, &AudioTrack, AudioTrack.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &ByteBuffer, ByteBuffer.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioTimestamp, AudioTimestamp.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioManager, AudioManager.mapping, 1, ao->log) < 0 ||
|
||||
mp_jni_init_jfields(env, &AudioFormat, AudioFormat.mapping, 1, ao->log) < 0) {
|
||||
uninit_jni(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void *playthread(void *arg)
|
||||
{
|
||||
struct ao *ao = arg;
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
mpthread_set_name("audiotrack");
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->thread_terminate) {
|
||||
int state = AudioTrack.PLAYSTATE_PAUSED;
|
||||
if (p->audiotrack) {
|
||||
state = MP_JNI_CALL_INT(p->audiotrack, AudioTrack.getPlayState);
|
||||
}
|
||||
if (state == AudioTrack.PLAYSTATE_PLAYING) {
|
||||
int read_samples = p->chunksize / ao->sstride;
|
||||
int64_t ts = mp_time_us();
|
||||
ts += (read_samples / (double)(ao->samplerate)) * 1e6;
|
||||
ts += AudioTrack_getLatency(ao) * 1e6;
|
||||
int samples = ao_read_data(ao, &p->chunk, read_samples, ts);
|
||||
int write_samples = read_samples;
|
||||
int ret = AudioTrack_write(ao, write_samples * ao->sstride);
|
||||
if (ret >= 0) {
|
||||
p->written_frames += ret / ao->sstride;
|
||||
} else if (ret == AudioManager.ERROR_DEAD_OBJECT) {
|
||||
MP_WARN(ao, "AudioTrack.write failed with ERROR_DEAD_OBJECT. Recreating AudioTrack...\n");
|
||||
if (AudioTrack_Recreate(ao) < 0) {
|
||||
MP_ERR(ao, "AudioTrack_Recreate failed\n");
|
||||
}
|
||||
} else {
|
||||
MP_ERR(ao, "AudioTrack.write failed with %d\n", ret);
|
||||
}
|
||||
} else {
|
||||
struct timespec wait = mp_rel_time_to_timespec(0.300);
|
||||
pthread_cond_timedwait(&p->wakeup, &p->lock, &wait);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (p->audiotrack) {
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.stop);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.flush);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->thread_terminate = true;
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
|
||||
if (p->thread_created)
|
||||
pthread_join(p->thread, NULL);
|
||||
|
||||
if (p->audiotrack) {
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.release);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
(*env)->DeleteGlobalRef(env, p->audiotrack);
|
||||
p->audiotrack = NULL;
|
||||
}
|
||||
|
||||
if (p->bytearray) {
|
||||
(*env)->DeleteGlobalRef(env, p->bytearray);
|
||||
p->bytearray = NULL;
|
||||
}
|
||||
|
||||
if (p->shortarray) {
|
||||
(*env)->DeleteGlobalRef(env, p->shortarray);
|
||||
p->shortarray = NULL;
|
||||
}
|
||||
|
||||
if (p->floatarray) {
|
||||
(*env)->DeleteGlobalRef(env, p->floatarray);
|
||||
p->floatarray = NULL;
|
||||
}
|
||||
|
||||
if (p->bbuf) {
|
||||
(*env)->DeleteGlobalRef(env, p->bbuf);
|
||||
p->bbuf = NULL;
|
||||
}
|
||||
|
||||
if (p->timestamp) {
|
||||
(*env)->DeleteGlobalRef(env, p->timestamp);
|
||||
p->timestamp = NULL;
|
||||
}
|
||||
|
||||
if (p->chunk) {
|
||||
free(p->chunk);
|
||||
p->chunk = NULL;
|
||||
}
|
||||
|
||||
pthread_cond_destroy(&p->wakeup);
|
||||
pthread_mutex_destroy(&p->lock);
|
||||
|
||||
uninit_jni(ao);
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
if (!env)
|
||||
return -1;
|
||||
|
||||
pthread_mutex_init(&p->lock, NULL);
|
||||
pthread_cond_init(&p->wakeup, NULL);
|
||||
|
||||
if (init_jni(ao) < 0)
|
||||
return -1;
|
||||
|
||||
if (af_fmt_is_spdif(ao->format)) {
|
||||
p->format = AudioFormat.ENCODING_IEC61937;
|
||||
} else if (ao->format == AF_FORMAT_U8) {
|
||||
p->format = AudioFormat.ENCODING_PCM_8BIT;
|
||||
} else if (p->cfg_pcm_float && (ao->format == AF_FORMAT_FLOAT || ao->format == AF_FORMAT_FLOATP)) {
|
||||
ao->format = AF_FORMAT_FLOAT;
|
||||
p->format = AudioFormat.ENCODING_PCM_FLOAT;
|
||||
} else {
|
||||
ao->format = AF_FORMAT_S16;
|
||||
p->format = AudioFormat.ENCODING_PCM_16BIT;
|
||||
}
|
||||
|
||||
if (AudioTrack.getNativeOutputSampleRate) {
|
||||
jint samplerate = MP_JNI_CALL_STATIC_INT(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.getNativeOutputSampleRate,
|
||||
AudioManager.STREAM_MUSIC
|
||||
);
|
||||
if (MP_JNI_EXCEPTION_LOG(ao) == 0) {
|
||||
ao->samplerate = samplerate;
|
||||
MP_VERBOSE(ao, "AudioTrack.nativeOutputSampleRate = %d\n", samplerate);
|
||||
}
|
||||
}
|
||||
p->samplerate = ao->samplerate;
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
} else if (ao->channels.num == 1) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_MONO;
|
||||
} else if (ao->channels.num == 6) {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_5POINT1;
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP6(FL, FR, FC, LFE, BL, BR);
|
||||
} else {
|
||||
p->channel_config = AudioFormat.CHANNEL_OUT_STEREO;
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
}
|
||||
|
||||
jint buffer_size = MP_JNI_CALL_STATIC_INT(
|
||||
AudioTrack.clazz,
|
||||
AudioTrack.getMinBufferSize,
|
||||
p->samplerate,
|
||||
p->channel_config,
|
||||
p->format
|
||||
);
|
||||
if (buffer_size <= 0 || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTrack.getMinBufferSize returned an invalid size: %d", buffer_size);
|
||||
return -1;
|
||||
}
|
||||
p->chunksize = buffer_size;
|
||||
p->chunk = malloc(buffer_size);
|
||||
|
||||
int min = 0.200 * p->samplerate * af_fmt_to_bytes(ao->format);
|
||||
int max = min * 3 / 2;
|
||||
p->size = MPCLAMP(buffer_size * 2, min, max);
|
||||
MP_VERBOSE(ao, "Setting bufferSize = %d (driver=%d, min=%d, max=%d)\n", p->size, buffer_size, min, max);
|
||||
ao->device_buffer = p->size / af_fmt_to_bytes(ao->format);
|
||||
|
||||
jobject timestamp = MP_JNI_NEW(AudioTimestamp.clazz, AudioTimestamp.ctor);
|
||||
if (!timestamp || MP_JNI_EXCEPTION_LOG(ao) < 0) {
|
||||
MP_FATAL(ao, "AudioTimestamp could not be created\n");
|
||||
return -1;
|
||||
}
|
||||
p->timestamp = (*env)->NewGlobalRef(env, timestamp);
|
||||
(*env)->DeleteLocalRef(env, timestamp);
|
||||
|
||||
if (p->format == AudioFormat.ENCODING_IEC61937) {
|
||||
jshortArray shortarray = (*env)->NewShortArray(env, p->chunksize / 2);
|
||||
p->shortarray = (*env)->NewGlobalRef(env, shortarray);
|
||||
(*env)->DeleteLocalRef(env, shortarray);
|
||||
} else if (p->format == AudioFormat.ENCODING_PCM_FLOAT) {
|
||||
jfloatArray floatarray = (*env)->NewFloatArray(env, p->chunksize / sizeof(float));
|
||||
p->floatarray = (*env)->NewGlobalRef(env, floatarray);
|
||||
(*env)->DeleteLocalRef(env, floatarray);
|
||||
} else if (AudioTrack.writeBufferV21) {
|
||||
jobject bbuf = (*env)->NewDirectByteBuffer(env, p->chunk, p->chunksize);
|
||||
p->bbuf = (*env)->NewGlobalRef(env, bbuf);
|
||||
(*env)->DeleteLocalRef(env, bbuf);
|
||||
} else {
|
||||
jbyteArray bytearray = (*env)->NewByteArray(env, p->chunksize);
|
||||
p->bytearray = (*env)->NewGlobalRef(env, bytearray);
|
||||
(*env)->DeleteLocalRef(env, bytearray);
|
||||
}
|
||||
|
||||
/* create AudioTrack object */
|
||||
if (AudioTrack_New(ao) != 0) {
|
||||
MP_FATAL(ao, "Failed to create AudioTrack\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (pthread_create(&p->thread, NULL, playthread, ao)) {
|
||||
MP_ERR(ao, "pthread creation failed\n");
|
||||
goto error;
|
||||
}
|
||||
p->thread_created = true;
|
||||
|
||||
return 1;
|
||||
|
||||
error:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void stop(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack) {
|
||||
MP_ERR(ao, "AudioTrack does not exist to stop!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.pause);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.flush);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
|
||||
p->playhead_offset = 0;
|
||||
p->reset_pending = true;
|
||||
p->written_frames = 0;
|
||||
p->timestamp_fetched = 0;
|
||||
p->timestamp_set = false;
|
||||
p->timestamp_offset = 0;
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->audiotrack) {
|
||||
MP_ERR(ao, "AudioTrack does not exist to start!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
JNIEnv *env = MP_JNI_GET_ENV(ao);
|
||||
MP_JNI_CALL_VOID(p->audiotrack, AudioTrack.play);
|
||||
MP_JNI_EXCEPTION_LOG(ao);
|
||||
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_audiotrack = {
|
||||
.description = "Android AudioTrack audio output",
|
||||
.name = "audiotrack",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = stop,
|
||||
.resume = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("pcm-float", cfg_pcm_float, 0),
|
||||
OPT_INT("session-id", cfg_session_id, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "audiotrack",
|
||||
};
|
||||
+2
-2
@@ -146,7 +146,7 @@ static int init(struct ao *ao)
|
||||
// enough frames for at least 0.25 seconds
|
||||
ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize);
|
||||
// but at least one!
|
||||
ac->framecount = FFMAX(ac->framecount, 1);
|
||||
ac->framecount = MPMAX(ac->framecount, 1);
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
ac->lastpts = AV_NOPTS_VALUE;
|
||||
@@ -324,7 +324,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
talloc_free(tempdata);
|
||||
|
||||
int taken = FFMIN(bufpos, orig_samples);
|
||||
int taken = MPMIN(bufpos, orig_samples);
|
||||
ectx->samples_since_last_pts += taken;
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
@@ -148,7 +148,7 @@ struct ao_driver {
|
||||
void (*uninit)(struct ao *ao);
|
||||
// push based: see ao_reset()
|
||||
// pull based: stop the audio callback
|
||||
void (*reset)(struct ao*ao);
|
||||
void (*reset)(struct ao *ao);
|
||||
// push based: see ao_pause()
|
||||
void (*pause)(struct ao *ao);
|
||||
// push based: see ao_resume()
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This script simply downloads waf to the current directory
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" CC="${CC}" CXX="${CXX}" pytho
|
||||
--variant="${MPV_VARIANT}" \
|
||||
--prefix="${MPV_INSTALL_PREFIX}" \
|
||||
--enable-{gl,iconv,lcms2,libass,libass-osd,libmpv-shared,lua,jpeg,plain-gl,zlib} \
|
||||
--enable-{apple-remote,cocoa,coreaudio,gl-cocoa,macos-cocoa-cb,macos-touchbar,videotoolbox-gl}
|
||||
--enable-{cocoa,coreaudio,gl-cocoa,macos-cocoa-cb,macos-touchbar,videotoolbox-gl}
|
||||
|
||||
python3 ./waf build --variant="${MPV_VARIANT}" -j4
|
||||
|
||||
|
||||
+40
-8
@@ -96,10 +96,10 @@ char *mp_format_time(double time, bool fractions)
|
||||
// Set rc to the union of rc and rc2
|
||||
void mp_rect_union(struct mp_rect *rc, const struct mp_rect *rc2)
|
||||
{
|
||||
rc->x0 = FFMIN(rc->x0, rc2->x0);
|
||||
rc->y0 = FFMIN(rc->y0, rc2->y0);
|
||||
rc->x1 = FFMAX(rc->x1, rc2->x1);
|
||||
rc->y1 = FFMAX(rc->y1, rc2->y1);
|
||||
rc->x0 = MPMIN(rc->x0, rc2->x0);
|
||||
rc->y0 = MPMIN(rc->y0, rc2->y0);
|
||||
rc->x1 = MPMAX(rc->x1, rc2->x1);
|
||||
rc->y1 = MPMAX(rc->y1, rc2->y1);
|
||||
}
|
||||
|
||||
// Returns whether or not a point is contained by rc
|
||||
@@ -112,10 +112,10 @@ bool mp_rect_contains(struct mp_rect *rc, int x, int y)
|
||||
// Return false if the result is empty.
|
||||
bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2)
|
||||
{
|
||||
rc->x0 = FFMAX(rc->x0, rc2->x0);
|
||||
rc->y0 = FFMAX(rc->y0, rc2->y0);
|
||||
rc->x1 = FFMIN(rc->x1, rc2->x1);
|
||||
rc->y1 = FFMIN(rc->y1, rc2->y1);
|
||||
rc->x0 = MPMAX(rc->x0, rc2->x0);
|
||||
rc->y0 = MPMAX(rc->y0, rc2->y0);
|
||||
rc->x1 = MPMIN(rc->x1, rc2->x1);
|
||||
rc->y1 = MPMIN(rc->y1, rc2->y1);
|
||||
|
||||
return rc->x1 > rc->x0 && rc->y1 > rc->y0;
|
||||
}
|
||||
@@ -318,3 +318,35 @@ char **mp_dup_str_array(void *tctx, char **s)
|
||||
MP_TARRAY_APPEND(tctx, r, num_r, NULL);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Return rounded down integer log 2 of v, i.e. position of highest set bit.
|
||||
// mp_log2(0) == 0
|
||||
// mp_log2(1) == 0
|
||||
// mp_log2(31) == 4
|
||||
// mp_log2(32) == 5
|
||||
unsigned int mp_log2(uint32_t v)
|
||||
{
|
||||
#if defined(__GNUC__) && __GNUC__ >= 4
|
||||
return v ? 31 - __builtin_clz(v) : 0;
|
||||
#else
|
||||
for (int x = 31; x >= 0; x--) {
|
||||
if (v & (((uint32_t)1) << x))
|
||||
return x;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// If a power of 2, return it, otherwise return the next highest one, or 0.
|
||||
// mp_round_next_power_of_2(65) == 128
|
||||
// mp_round_next_power_of_2(64) == 64
|
||||
// mp_round_next_power_of_2(0) == 1
|
||||
// mp_round_next_power_of_2(UINT32_MAX) == 0
|
||||
uint32_t mp_round_next_power_of_2(uint32_t v)
|
||||
{
|
||||
for (int n = 0; n < 30; n++) {
|
||||
if ((1 << n) >= v)
|
||||
return 1 << n;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -88,6 +88,9 @@ bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2);
|
||||
bool mp_rect_contains(struct mp_rect *rc, int x, int y);
|
||||
bool mp_rect_equals(struct mp_rect *rc1, struct mp_rect *rc2);
|
||||
|
||||
unsigned int mp_log2(uint32_t v);
|
||||
uint32_t mp_round_next_power_of_2(uint32_t v);
|
||||
|
||||
int mp_snprintf_cat(char *str, size_t size, const char *format, ...)
|
||||
PRINTF_ATTRIBUTE(3, 4);
|
||||
|
||||
|
||||
+19
-17
@@ -77,25 +77,25 @@ struct mux_stream {
|
||||
#define OPT_BASE_STRUCT struct encode_opts
|
||||
const struct m_sub_options encode_config = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_STRING("o", file, M_OPT_FIXED | CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE),
|
||||
OPT_STRING("of", format, M_OPT_FIXED),
|
||||
OPT_KEYVALUELIST("ofopts", fopts, M_OPT_FIXED | M_OPT_HAVE_HELP),
|
||||
OPT_STRING("ovc", vcodec, M_OPT_FIXED),
|
||||
OPT_KEYVALUELIST("ovcopts", vopts, M_OPT_FIXED | M_OPT_HAVE_HELP),
|
||||
OPT_STRING("oac", acodec, M_OPT_FIXED),
|
||||
OPT_KEYVALUELIST("oacopts", aopts, M_OPT_FIXED | M_OPT_HAVE_HELP),
|
||||
OPT_FLOATRANGE("ovoffset", voffset, M_OPT_FIXED, -1000000.0, 1000000.0,
|
||||
OPT_STRING("o", file, CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE),
|
||||
OPT_STRING("of", format, 0),
|
||||
OPT_KEYVALUELIST("ofopts", fopts, M_OPT_HAVE_HELP),
|
||||
OPT_STRING("ovc", vcodec, 0),
|
||||
OPT_KEYVALUELIST("ovcopts", vopts, M_OPT_HAVE_HELP),
|
||||
OPT_STRING("oac", acodec, 0),
|
||||
OPT_KEYVALUELIST("oacopts", aopts, M_OPT_HAVE_HELP),
|
||||
OPT_FLOATRANGE("ovoffset", voffset, 0, -1000000.0, 1000000.0,
|
||||
.deprecation_message = "--audio-delay (once unbroken)"),
|
||||
OPT_FLOATRANGE("oaoffset", aoffset, M_OPT_FIXED, -1000000.0, 1000000.0,
|
||||
OPT_FLOATRANGE("oaoffset", aoffset, 0, -1000000.0, 1000000.0,
|
||||
.deprecation_message = "--audio-delay (once unbroken)"),
|
||||
OPT_FLAG("orawts", rawts, M_OPT_FIXED),
|
||||
OPT_FLAG("ovfirst", video_first, M_OPT_FIXED,
|
||||
OPT_FLAG("orawts", rawts, 0),
|
||||
OPT_FLAG("ovfirst", video_first, 0,
|
||||
.deprecation_message = "no replacement"),
|
||||
OPT_FLAG("oafirst", audio_first, M_OPT_FIXED,
|
||||
OPT_FLAG("oafirst", audio_first, 0,
|
||||
.deprecation_message = "no replacement"),
|
||||
OPT_FLAG("ocopy-metadata", copy_metadata, M_OPT_FIXED),
|
||||
OPT_KEYVALUELIST("oset-metadata", set_metadata, M_OPT_FIXED),
|
||||
OPT_STRINGLIST("oremove-metadata", remove_metadata, M_OPT_FIXED),
|
||||
OPT_FLAG("ocopy-metadata", copy_metadata, 0),
|
||||
OPT_KEYVALUELIST("oset-metadata", set_metadata, 0),
|
||||
OPT_STRINGLIST("oremove-metadata", remove_metadata, 0),
|
||||
|
||||
OPT_REMOVED("ocopyts", "ocopyts is now the default"),
|
||||
OPT_REMOVED("oneverdrop", "no replacement"),
|
||||
@@ -720,7 +720,7 @@ int encode_lavc_getstatus(struct encode_lavc_context *ctx,
|
||||
|
||||
double now = mp_time_sec();
|
||||
float minutes, megabytes, fps, x;
|
||||
float f = FFMAX(0.0001, relative_position);
|
||||
float f = MPMAX(0.0001, relative_position);
|
||||
|
||||
pthread_mutex_lock(&ctx->lock);
|
||||
|
||||
@@ -830,7 +830,9 @@ static void encoder_2pass_prepare(struct encoder_context *p)
|
||||
|
||||
if (p->encoder->flags & AV_CODEC_FLAG_PASS2) {
|
||||
MP_INFO(p, "Reading 2-pass log: %s\n", filename);
|
||||
struct stream *s = stream_open(filename, p->global);
|
||||
struct stream *s = stream_create(filename,
|
||||
STREAM_ORIGIN_DIRECT | STREAM_READ,
|
||||
NULL, p->global);
|
||||
if (s) {
|
||||
struct bstr content = stream_read_complete(s, p, 1000000000);
|
||||
if (content.start) {
|
||||
|
||||
+119
-40
@@ -30,7 +30,6 @@
|
||||
#include "osdep/atomic.h"
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "misc/ring.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
@@ -43,6 +42,8 @@
|
||||
#include "msg.h"
|
||||
#include "msg_control.h"
|
||||
|
||||
#define TERM_BUF 100
|
||||
|
||||
struct mp_log_root {
|
||||
struct mpv_global *global;
|
||||
// --- protected by mp_msg_lock
|
||||
@@ -58,6 +59,7 @@ struct mp_log_root {
|
||||
bool force_stderr;
|
||||
struct mp_log_buffer **buffers;
|
||||
int num_buffers;
|
||||
struct mp_log_buffer *early_buffer;
|
||||
FILE *log_file;
|
||||
FILE *stats_file;
|
||||
char *log_path;
|
||||
@@ -83,8 +85,13 @@ struct mp_log {
|
||||
|
||||
struct mp_log_buffer {
|
||||
struct mp_log_root *root;
|
||||
struct mp_ring *ring;
|
||||
struct mp_log_buffer_entry **entries; // ringbuffer
|
||||
int capacity; // total space in entries[]
|
||||
int entry0; // first (oldest) entry index
|
||||
int num_entries; // number of valid entries after entry0
|
||||
uint64_t dropped; // number of skipped entries
|
||||
int level;
|
||||
bool silent;
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
void *wakeup_cb_ctx;
|
||||
};
|
||||
@@ -116,8 +123,11 @@ static void update_loglevel(struct mp_log *log)
|
||||
log->level = mp_msg_find_level(root->msg_levels[n * 2 + 1]);
|
||||
}
|
||||
log->terminal_level = log->level;
|
||||
for (int n = 0; n < log->root->num_buffers; n++)
|
||||
log->level = MPMAX(log->level, log->root->buffers[n]->level);
|
||||
for (int n = 0; n < log->root->num_buffers; n++) {
|
||||
int buffer_level = log->root->buffers[n]->level;
|
||||
if (buffer_level != MP_LOG_BUFFER_MSGL_TERM)
|
||||
log->level = MPMAX(log->level, buffer_level);
|
||||
}
|
||||
if (log->root->log_file)
|
||||
log->level = MPMAX(log->level, MSGL_DEBUG);
|
||||
if (log->root->stats_file)
|
||||
@@ -294,6 +304,15 @@ static void write_log_file(struct mp_log *log, int lev, char *text)
|
||||
fflush(root->log_file);
|
||||
}
|
||||
|
||||
static struct mp_log_buffer_entry *log_buffer_read(struct mp_log_buffer *buffer)
|
||||
{
|
||||
assert(buffer->num_entries);
|
||||
struct mp_log_buffer_entry *res = buffer->entries[buffer->entry0];
|
||||
buffer->entry0 = (buffer->entry0 + 1) % buffer->capacity;
|
||||
buffer->num_entries -= 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
|
||||
{
|
||||
struct mp_log_root *root = log->root;
|
||||
@@ -303,27 +322,21 @@ static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
|
||||
if (buffer_level == MP_LOG_BUFFER_MSGL_TERM)
|
||||
buffer_level = log->terminal_level;
|
||||
if (lev <= buffer_level && lev != MSGL_STATUS) {
|
||||
// Assuming a single writer (serialized by msg lock)
|
||||
int avail = mp_ring_available(buffer->ring) / sizeof(void *);
|
||||
if (avail < 1)
|
||||
continue;
|
||||
struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry);
|
||||
if (avail > 1) {
|
||||
*entry = (struct mp_log_buffer_entry) {
|
||||
.prefix = talloc_strdup(entry, log->verbose_prefix),
|
||||
.level = lev,
|
||||
.text = talloc_strdup(entry, text),
|
||||
};
|
||||
} else {
|
||||
// write overflow message to signal that messages might be lost
|
||||
*entry = (struct mp_log_buffer_entry) {
|
||||
.prefix = "overflow",
|
||||
.level = MSGL_FATAL,
|
||||
.text = "log message buffer overflow\n",
|
||||
};
|
||||
if (buffer->num_entries == buffer->capacity) {
|
||||
struct mp_log_buffer_entry *skip = log_buffer_read(buffer);
|
||||
talloc_free(skip);
|
||||
buffer->dropped += 1;
|
||||
}
|
||||
mp_ring_write(buffer->ring, (unsigned char *)&entry, sizeof(entry));
|
||||
if (buffer->wakeup_cb)
|
||||
struct mp_log_buffer_entry *entry = talloc_ptrtype(NULL, entry);
|
||||
*entry = (struct mp_log_buffer_entry) {
|
||||
.prefix = talloc_strdup(entry, log->verbose_prefix),
|
||||
.level = lev,
|
||||
.text = talloc_strdup(entry, text),
|
||||
};
|
||||
int pos = (buffer->entry0 + buffer->num_entries) % buffer->capacity;
|
||||
buffer->entries[pos] = entry;
|
||||
buffer->num_entries += 1;
|
||||
if (buffer->wakeup_cb && !buffer->silent)
|
||||
buffer->wakeup_cb(buffer->wakeup_cb_ctx);
|
||||
}
|
||||
}
|
||||
@@ -548,6 +561,9 @@ bool mp_msg_has_log_file(struct mpv_global *global)
|
||||
void mp_msg_uninit(struct mpv_global *global)
|
||||
{
|
||||
struct mp_log_root *root = global->log->root;
|
||||
if (root->early_buffer)
|
||||
mp_msg_log_buffer_destroy(root->early_buffer);
|
||||
assert(root->num_buffers == 0);
|
||||
if (root->stats_file)
|
||||
fclose(root->stats_file);
|
||||
talloc_free(root->stats_path);
|
||||
@@ -559,6 +575,32 @@ void mp_msg_uninit(struct mpv_global *global)
|
||||
global->log = NULL;
|
||||
}
|
||||
|
||||
void mp_msg_set_early_logging(struct mpv_global *global, bool enable)
|
||||
{
|
||||
struct mp_log_root *root = global->log->root;
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
|
||||
if (enable != !!root->early_buffer) {
|
||||
if (enable) {
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
struct mp_log_buffer *buf =
|
||||
mp_msg_log_buffer_new(global, TERM_BUF, MP_LOG_BUFFER_MSGL_TERM,
|
||||
NULL, NULL);
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
assert(!root->early_buffer); // no concurrent calls to this function
|
||||
root->early_buffer = buf;
|
||||
} else {
|
||||
struct mp_log_buffer *buf = root->early_buffer;
|
||||
root->early_buffer = NULL;
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
mp_msg_log_buffer_destroy(buf);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
}
|
||||
|
||||
struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
|
||||
int size, int level,
|
||||
void (*wakeup_cb)(void *ctx),
|
||||
@@ -568,16 +610,34 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
|
||||
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
|
||||
if (level == MP_LOG_BUFFER_MSGL_TERM) {
|
||||
size = TERM_BUF;
|
||||
|
||||
// The first thing which creates a terminal-level log buffer gets the
|
||||
// early log buffer, if it exists. This is supposed to enable a script
|
||||
// to grab log messages from before it was initialized. It's OK that
|
||||
// this works only for 1 script and only once.
|
||||
if (root->early_buffer) {
|
||||
struct mp_log_buffer *buffer = root->early_buffer;
|
||||
root->early_buffer = NULL;
|
||||
buffer->wakeup_cb = wakeup_cb;
|
||||
buffer->wakeup_cb_ctx = wakeup_cb_ctx;
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
assert(size > 0);
|
||||
|
||||
struct mp_log_buffer *buffer = talloc_ptrtype(NULL, buffer);
|
||||
*buffer = (struct mp_log_buffer) {
|
||||
.root = root,
|
||||
.level = level,
|
||||
.ring = mp_ring_new(buffer, sizeof(void *) * size),
|
||||
.entries = talloc_array(buffer, struct mp_log_buffer_entry *, size),
|
||||
.capacity = size,
|
||||
.wakeup_cb = wakeup_cb,
|
||||
.wakeup_cb_ctx = wakeup_cb_ctx,
|
||||
};
|
||||
if (!buffer->ring)
|
||||
abort();
|
||||
|
||||
MP_TARRAY_APPEND(root, root->buffers, root->num_buffers, buffer);
|
||||
|
||||
@@ -587,6 +647,13 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void mp_msg_log_buffer_set_silent(struct mp_log_buffer *buffer, bool silent)
|
||||
{
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
buffer->silent = silent;
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
}
|
||||
|
||||
void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer)
|
||||
{
|
||||
if (!buffer)
|
||||
@@ -606,12 +673,8 @@ void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer)
|
||||
|
||||
found:
|
||||
|
||||
while (1) {
|
||||
struct mp_log_buffer_entry *e = mp_msg_log_buffer_read(buffer);
|
||||
if (!e)
|
||||
break;
|
||||
talloc_free(e);
|
||||
}
|
||||
while (buffer->num_entries)
|
||||
talloc_free(log_buffer_read(buffer));
|
||||
talloc_free(buffer);
|
||||
|
||||
atomic_fetch_add(&root->reload_counter, 1);
|
||||
@@ -622,13 +685,29 @@ found:
|
||||
// Thread-safety: one buffer can be read by a single thread only.
|
||||
struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer)
|
||||
{
|
||||
void *ptr = NULL;
|
||||
int read = mp_ring_read(buffer->ring, (unsigned char *)&ptr, sizeof(ptr));
|
||||
if (read == 0)
|
||||
return NULL;
|
||||
if (read != sizeof(ptr))
|
||||
abort();
|
||||
return ptr;
|
||||
struct mp_log_buffer_entry *res = NULL;
|
||||
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
|
||||
if (!buffer->silent && buffer->num_entries) {
|
||||
if (buffer->dropped) {
|
||||
res = talloc_ptrtype(NULL, res);
|
||||
*res = (struct mp_log_buffer_entry) {
|
||||
.prefix = "overflow",
|
||||
.level = MSGL_FATAL,
|
||||
.text = talloc_asprintf(res,
|
||||
"log message buffer overflow: %"PRId64" messages skipped\n",
|
||||
buffer->dropped),
|
||||
};
|
||||
buffer->dropped = 0;
|
||||
} else {
|
||||
res = log_buffer_read(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Thread-safety: fully thread-safe, but keep in mind that the lifetime of
|
||||
|
||||
+9
-8
@@ -32,14 +32,15 @@ extern struct mp_log *const mp_null_log;
|
||||
|
||||
// Verbosity levels.
|
||||
enum {
|
||||
MSGL_FATAL, // will exit/abort (note: msg.c doesn't exit or abort)
|
||||
MSGL_ERR, // continues
|
||||
MSGL_WARN, // only warning
|
||||
MSGL_INFO, // -quiet
|
||||
MSGL_STATUS, // exclusively for the playback status line
|
||||
MSGL_V, // -v
|
||||
MSGL_DEBUG, // -v -v
|
||||
MSGL_TRACE, // -v -v -v
|
||||
MSGL_FATAL, // only errors (difference to MSGL_ERR isn't too clear)
|
||||
MSGL_ERR, // only errors
|
||||
MSGL_WARN, // only warnings
|
||||
MSGL_INFO, // what you normally see on the terminal
|
||||
MSGL_STATUS, // exclusively for the playback status line (-quiet disables)
|
||||
MSGL_V, // -v | slightly more information than default
|
||||
MSGL_DEBUG, // -v -v | full debug information; this and numerically below
|
||||
// should not produce "per frame" output
|
||||
MSGL_TRACE, // -v -v -v | anything that might flood the terminal
|
||||
MSGL_STATS, // dumping fine grained stats (--dump-stats)
|
||||
|
||||
MSGL_MAX = MSGL_STATS,
|
||||
|
||||
@@ -11,6 +11,7 @@ void mp_msg_update_msglevels(struct mpv_global *global, struct MPOpts *opts);
|
||||
void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr);
|
||||
bool mp_msg_has_status_line(struct mpv_global *global);
|
||||
bool mp_msg_has_log_file(struct mpv_global *global);
|
||||
void mp_msg_set_early_logging(struct mpv_global *global, bool enable);
|
||||
|
||||
void mp_msg_flush_status_line(struct mp_log *log);
|
||||
|
||||
@@ -30,6 +31,7 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
|
||||
void *wakeup_cb_ctx);
|
||||
void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer);
|
||||
struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer);
|
||||
void mp_msg_log_buffer_set_silent(struct mp_log_buffer *buffer, bool silent);
|
||||
|
||||
int mp_msg_find_level(const char *s);
|
||||
|
||||
|
||||
+5
-1
@@ -32,6 +32,7 @@ struct playlist_entry *playlist_entry_new(const char *filename)
|
||||
struct playlist_entry *e = talloc_zero(NULL, struct playlist_entry);
|
||||
char *local_filename = mp_file_url_to_filename(e, bstr0(filename));
|
||||
e->filename = local_filename ? local_filename : talloc_strdup(e, filename);
|
||||
e->stream_flags = STREAM_ORIGIN_DIRECT;
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -281,7 +282,10 @@ struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel,
|
||||
struct mp_log *log = mp_log_new(NULL, global->log, "!playlist_parser");
|
||||
mp_verbose(log, "Parsing playlist file %s...\n", file);
|
||||
|
||||
struct demuxer_params p = {.force_format = "playlist"};
|
||||
struct demuxer_params p = {
|
||||
.force_format = "playlist",
|
||||
.stream_flags = STREAM_ORIGIN_DIRECT,
|
||||
};
|
||||
struct demuxer *d = demux_open_url(file, &p, cancel, global);
|
||||
if (!d) {
|
||||
talloc_free(log);
|
||||
|
||||
+1
-6
@@ -53,11 +53,8 @@ struct playlist_entry {
|
||||
// Additional refcount. Normally (reserved==0), the entry is owned by the
|
||||
// playlist, and this can be used to keep the entry alive.
|
||||
int reserved;
|
||||
// Any flags from STREAM_ORIGIN_FLAGS. 0 if unknown.
|
||||
// Used to reject loading of unsafe entries from external playlists.
|
||||
// Can have any of the following bit flags set:
|
||||
// STREAM_SAFE_ONLY: only allow streams marked with is_safe
|
||||
// STREAM_NETWORK_ONLY: only allow streams marked with is_network
|
||||
// The value 0 allows everything.
|
||||
int stream_flags;
|
||||
};
|
||||
|
||||
@@ -69,8 +66,6 @@ struct playlist {
|
||||
// current_was_replaced is set to true.
|
||||
struct playlist_entry *current;
|
||||
bool current_was_replaced;
|
||||
|
||||
bool disable_safety;
|
||||
};
|
||||
|
||||
void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value);
|
||||
|
||||
+103
-17
@@ -38,6 +38,7 @@
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
#include "common/recorder.h"
|
||||
#include "misc/charset_conv.h"
|
||||
#include "misc/thread_tools.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "osdep/timer.h"
|
||||
@@ -97,6 +98,7 @@ struct demux_opts {
|
||||
int audio_back_preroll;
|
||||
int back_batch[STREAM_TYPE_COUNT];
|
||||
double back_seek_size;
|
||||
char *meta_cp;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_opts
|
||||
@@ -128,6 +130,7 @@ const struct m_sub_options demux_conf = {
|
||||
OPT_INTRANGE("audio-backward-batch", back_batch[STREAM_AUDIO], 0, 0, 1024),
|
||||
OPT_DOUBLE("demuxer-backward-playback-step", back_seek_size, M_OPT_MIN,
|
||||
.min = 0),
|
||||
OPT_STRING("metadata-codepage", meta_cp, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_opts),
|
||||
@@ -146,6 +149,7 @@ const struct m_sub_options demux_conf = {
|
||||
[STREAM_VIDEO] = 1,
|
||||
[STREAM_AUDIO] = 10,
|
||||
},
|
||||
.meta_cp = "utf-8",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -181,6 +185,8 @@ struct demux_internal {
|
||||
struct sh_stream **streams;
|
||||
int num_streams;
|
||||
|
||||
char *meta_charset;
|
||||
|
||||
// If non-NULL, a stream which is used for global (timed) metadata. It will
|
||||
// be an arbitrary stream, which hopefully will happen to work.
|
||||
struct sh_stream *metadata_stream;
|
||||
@@ -273,6 +279,7 @@ struct demux_internal {
|
||||
int64_t slave_unbuffered_read_bytes; // value repoted from demuxer impl.
|
||||
int64_t hack_unbuffered_read_bytes; // for demux_get_bytes_read_hack()
|
||||
int64_t cache_unbuffered_read_bytes; // for demux_reader_state.bytes_per_second
|
||||
int64_t byte_level_seeks; // for demux_reader_state.byte_level_seeks
|
||||
};
|
||||
|
||||
struct timed_metadata {
|
||||
@@ -442,6 +449,7 @@ static struct demux_packet *find_seek_target(struct demux_queue *queue,
|
||||
double pts, int flags);
|
||||
static void prune_old_packets(struct demux_internal *in);
|
||||
static void dumper_close(struct demux_internal *in);
|
||||
static void demux_convert_tags_charset(struct demuxer *demuxer);
|
||||
|
||||
static uint64_t get_foward_buffered_bytes(struct demux_stream *ds)
|
||||
{
|
||||
@@ -979,13 +987,6 @@ static void demux_add_sh_stream_locked(struct demux_internal *in,
|
||||
|
||||
if (sh->ff_index < 0)
|
||||
sh->ff_index = sh->index;
|
||||
if (sh->demuxer_id < 0) {
|
||||
sh->demuxer_id = 0;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
if (in->streams[n]->type == sh->type)
|
||||
sh->demuxer_id += 1;
|
||||
}
|
||||
}
|
||||
|
||||
MP_TARRAY_APPEND(in, in->streams, in->num_streams, sh);
|
||||
assert(in->streams[sh->index] == sh);
|
||||
@@ -1605,8 +1606,8 @@ static void add_index_entry(struct demux_queue *queue, struct demux_packet *dp,
|
||||
// Needs to honor power-of-2 requirement.
|
||||
size_t new_size = MPMAX(128, queue->index_size * 2);
|
||||
assert(!(new_size & (new_size - 1)));
|
||||
MP_VERBOSE(in, "stream %d: resize index to %zu\n", queue->ds->index,
|
||||
new_size);
|
||||
MP_DBG(in, "stream %d: resize index to %zu\n", queue->ds->index,
|
||||
new_size);
|
||||
// Note: we could tolerate allocation failure, and just discard the
|
||||
// entire index (and prevent the index from being recreated).
|
||||
MP_RESIZE_ARRAY(NULL, queue->index, new_size);
|
||||
@@ -2900,6 +2901,7 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->duration = src->duration;
|
||||
dst->is_network = src->is_network;
|
||||
dst->is_streaming = src->is_streaming;
|
||||
dst->stream_origin = src->stream_origin;
|
||||
dst->priv = src->priv;
|
||||
dst->metadata = mp_tags_dup(dst, src->metadata);
|
||||
}
|
||||
@@ -3102,7 +3104,6 @@ static void demux_init_cuesheet(struct demuxer *demuxer)
|
||||
// Calling this after opening was completed is not allowed. Also, if opening
|
||||
// failed, this must not be called (or trying another demuxer would fail).
|
||||
// Useful so that e.g. subtitles don't keep the file or socket open.
|
||||
// Replaces it with a dummy stream for dumb reasons.
|
||||
// If there's ever the situation where we can't allow the demuxer to close
|
||||
// the stream, this function could ignore the request.
|
||||
void demux_close_stream(struct demuxer *demuxer)
|
||||
@@ -3115,9 +3116,8 @@ void demux_close_stream(struct demuxer *demuxer)
|
||||
|
||||
MP_VERBOSE(demuxer, "demuxer read all data; closing stream\n");
|
||||
free_stream(demuxer->stream);
|
||||
demuxer->stream = stream_memory_open(demuxer->global, NULL, 0); // dummy
|
||||
demuxer->stream->cancel = demuxer->cancel;
|
||||
in->d_user->stream = demuxer->stream;
|
||||
demuxer->stream = NULL;
|
||||
in->d_user->stream = NULL;
|
||||
}
|
||||
|
||||
static void demux_init_ccs(struct demuxer *demuxer, struct demux_opts *opts)
|
||||
@@ -3151,6 +3151,7 @@ struct parent_stream_info {
|
||||
bool seekable;
|
||||
bool is_network;
|
||||
bool is_streaming;
|
||||
int stream_origin;
|
||||
struct mp_cancel *cancel;
|
||||
char *filename;
|
||||
};
|
||||
@@ -3182,6 +3183,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
.filename = talloc_strdup(demuxer, sinfo->filename),
|
||||
.is_network = sinfo->is_network,
|
||||
.is_streaming = sinfo->is_streaming,
|
||||
.stream_origin = sinfo->stream_origin,
|
||||
.access_references = opts->access_references,
|
||||
.events = DEMUX_EVENT_ALL,
|
||||
.duration = -1,
|
||||
@@ -3214,6 +3216,9 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
mp_dbg(log, "Trying demuxer: %s (force-level: %s)\n",
|
||||
desc->name, d_level(check));
|
||||
|
||||
if (stream)
|
||||
stream_seek(stream, 0);
|
||||
|
||||
in->d_thread->params = params; // temporary during open()
|
||||
int ret = demuxer->desc->open(in->d_thread, check);
|
||||
if (ret >= 0) {
|
||||
@@ -3232,6 +3237,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
}
|
||||
demux_init_cuesheet(in->d_thread);
|
||||
demux_init_ccs(demuxer, opts);
|
||||
demux_convert_tags_charset(in->d_thread);
|
||||
demux_copy(in->d_user, in->d_thread);
|
||||
in->duration = in->d_thread->duration;
|
||||
demuxer_sort_chapters(demuxer);
|
||||
@@ -3312,6 +3318,7 @@ static struct demuxer *demux_open(struct stream *stream,
|
||||
.seekable = stream->seekable,
|
||||
.is_network = stream->is_network,
|
||||
.is_streaming = stream->streaming,
|
||||
.stream_origin = stream->stream_origin,
|
||||
.cancel = cancel,
|
||||
.filename = talloc_strdup(NULL, stream->url),
|
||||
};
|
||||
@@ -3323,8 +3330,6 @@ static struct demuxer *demux_open(struct stream *stream,
|
||||
for (int n = 0; demuxer_list[n]; n++) {
|
||||
const struct demuxer_desc *desc = demuxer_list[n];
|
||||
if (!check_desc || desc == check_desc) {
|
||||
if (stream->seekable && (!params || !params->timeline))
|
||||
stream_seek(stream, 0);
|
||||
demuxer = open_given_type(global, log, desc, stream, &sinfo,
|
||||
params, level);
|
||||
if (demuxer) {
|
||||
@@ -3369,9 +3374,8 @@ struct demuxer *demux_open_url(const char *url,
|
||||
struct mp_cancel *cancel,
|
||||
struct mpv_global *global)
|
||||
{
|
||||
struct demuxer_params dummy = {0};
|
||||
if (!params)
|
||||
params = &dummy;
|
||||
return NULL;
|
||||
struct mp_cancel *priv_cancel = mp_cancel_new(NULL);
|
||||
if (cancel)
|
||||
mp_cancel_set_parent(priv_cancel, cancel);
|
||||
@@ -3760,6 +3764,8 @@ static bool queue_seek(struct demux_internal *in, double seek_pts, int flags,
|
||||
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
||||
enum stream_type t, int id)
|
||||
{
|
||||
if (id < 0)
|
||||
return NULL;
|
||||
int num = demux_get_num_stream(d);
|
||||
for (int n = 0; n < num; n++) {
|
||||
struct sh_stream *s = demux_get_stream(d, n);
|
||||
@@ -3977,14 +3983,19 @@ static void update_bytes_read(struct demux_internal *in)
|
||||
int64_t new = in->slave_unbuffered_read_bytes;
|
||||
in->slave_unbuffered_read_bytes = 0;
|
||||
|
||||
int64_t new_seeks = 0;
|
||||
|
||||
struct stream *stream = demuxer->stream;
|
||||
if (stream) {
|
||||
new += stream->total_unbuffered_read_bytes;
|
||||
stream->total_unbuffered_read_bytes = 0;
|
||||
new_seeks += stream->total_stream_seeks;
|
||||
stream->total_stream_seeks = 0;
|
||||
}
|
||||
|
||||
in->cache_unbuffered_read_bytes += new;
|
||||
in->hack_unbuffered_read_bytes += new;
|
||||
in->byte_level_seeks += new_seeks;
|
||||
}
|
||||
|
||||
// must be called not locked
|
||||
@@ -4345,6 +4356,7 @@ void demux_get_reader_state(struct demuxer *demuxer, struct demux_reader_state *
|
||||
.low_level_seeks = in->low_level_seeks,
|
||||
.ts_last = in->demux_ts,
|
||||
.bytes_per_second = in->bytes_per_second,
|
||||
.byte_level_seeks = in->byte_level_seeks,
|
||||
.file_cache_bytes = in->cache ? demux_cache_get_size(in->cache) : -1,
|
||||
};
|
||||
bool any_packets = false;
|
||||
@@ -4396,3 +4408,77 @@ struct demux_chapter *demux_copy_chapter_data(struct demux_chapter *c, int num)
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
static void visit_tags(void *ctx, void (*visit)(void *ctx, void *ta, char **s),
|
||||
struct mp_tags *tags)
|
||||
{
|
||||
for (int n = 0; n < (tags ? tags->num_keys : 0); n++)
|
||||
visit(ctx, tags, &tags->values[n]);
|
||||
}
|
||||
|
||||
static void visit_meta(struct demuxer *demuxer, void *ctx,
|
||||
void (*visit)(void *ctx, void *ta, char **s))
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct sh_stream *sh = in->streams[n];
|
||||
|
||||
visit(ctx, sh, &sh->title);
|
||||
visit_tags(ctx, visit, sh->tags);
|
||||
}
|
||||
|
||||
for (int n = 0; n < demuxer->num_chapters; n++)
|
||||
visit_tags(ctx, visit, demuxer->chapters[n].metadata);
|
||||
|
||||
visit_tags(ctx, visit, demuxer->metadata);
|
||||
}
|
||||
|
||||
|
||||
static void visit_detect(void *ctx, void *ta, char **s)
|
||||
{
|
||||
char **all = ctx;
|
||||
|
||||
if (*s)
|
||||
*all = talloc_asprintf_append_buffer(*all, "%s\n", *s);
|
||||
}
|
||||
|
||||
static void visit_convert(void *ctx, void *ta, char **s)
|
||||
{
|
||||
struct demuxer *demuxer = ctx;
|
||||
struct demux_internal *in = demuxer->in;
|
||||
|
||||
if (!*s)
|
||||
return;
|
||||
|
||||
bstr data = bstr0(*s);
|
||||
bstr conv = mp_iconv_to_utf8(in->log, data, in->meta_charset,
|
||||
MP_ICONV_VERBOSE);
|
||||
if (conv.start && conv.start != data.start) {
|
||||
char *ns = conv.start; // 0-termination is guaranteed
|
||||
// (The old string might not be an alloc, but if it is, it's a talloc
|
||||
// child, and will not leak, even if it stays allocated uselessly.)
|
||||
*s = ns;
|
||||
talloc_steal(ta, *s);
|
||||
}
|
||||
}
|
||||
|
||||
static void demux_convert_tags_charset(struct demuxer *demuxer)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
|
||||
char *cp = in->opts->meta_cp;
|
||||
if (!cp || mp_charset_is_utf8(cp))
|
||||
return;
|
||||
|
||||
char *data = talloc_strdup(NULL, "");
|
||||
visit_meta(demuxer, &data, visit_detect);
|
||||
|
||||
in->meta_charset = (char *)mp_charset_guess(in, in->log, bstr0(data), cp, 0);
|
||||
if (in->meta_charset && !mp_charset_is_utf8(in->meta_charset)) {
|
||||
MP_INFO(demuxer, "Using tag charset: %s\n", in->meta_charset);
|
||||
visit_meta(demuxer, demuxer, visit_convert);
|
||||
}
|
||||
|
||||
talloc_free(data);
|
||||
}
|
||||
@@ -47,6 +47,7 @@ struct demux_reader_state {
|
||||
int64_t file_cache_bytes;
|
||||
double seeking; // current low level seek target, or NOPTS
|
||||
int low_level_seeks; // number of started low level seeks
|
||||
uint64_t byte_level_seeks; // number of byte stream level seeks
|
||||
double ts_last; // approx. timestamp of demuxer position
|
||||
uint64_t bytes_per_second; // low level statistics
|
||||
// Positions that can be seeked to without incurring the latency of a low
|
||||
@@ -196,6 +197,7 @@ typedef struct demuxer {
|
||||
bool fully_read;
|
||||
bool is_network; // opened directly from a network stream
|
||||
bool is_streaming; // implies a "slow" input, such as network or FUSE
|
||||
int stream_origin; // any STREAM_ORIGIN_* (set from source stream)
|
||||
bool access_references; // allow opening other files/URLs
|
||||
|
||||
// Bitmask of DEMUX_EVENT_*
|
||||
|
||||
+9
-4
@@ -78,7 +78,11 @@ static bool try_open(struct timeline *tl, char *filename)
|
||||
|| bstrcasecmp(bstr0(tl->demuxer->filename), bfilename) == 0)
|
||||
return false;
|
||||
|
||||
struct demuxer *d = demux_open_url(filename, NULL, tl->cancel, tl->global);
|
||||
struct demuxer_params p = {
|
||||
.stream_flags = tl->stream_origin,
|
||||
};
|
||||
|
||||
struct demuxer *d = demux_open_url(filename, &p, tl->cancel, tl->global);
|
||||
// Since .bin files are raw PCM data with no headers, we have to explicitly
|
||||
// open them. Also, try to avoid to open files that are most likely not .bin
|
||||
// files, as that would only play noise. Checking the file extension is
|
||||
@@ -87,7 +91,7 @@ static bool try_open(struct timeline *tl, char *filename)
|
||||
// CD sector size (2352 bytes)
|
||||
if (!d && bstr_case_endswith(bfilename, bstr0(".bin"))) {
|
||||
MP_WARN(tl, "CUE: Opening as BIN file!\n");
|
||||
struct demuxer_params p = {.force_format = "rawaudio"};
|
||||
p.force_format = "rawaudio";
|
||||
d = demux_open_url(filename, &p, tl->cancel, tl->global);
|
||||
}
|
||||
if (d) {
|
||||
@@ -265,8 +269,9 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
|
||||
struct stream *s = demuxer->stream;
|
||||
if (check >= DEMUX_CHECK_UNSAFE) {
|
||||
bstr d = stream_peek(s, PROBE_SIZE);
|
||||
if (d.len < 1 || !mp_probe_cue(d))
|
||||
char probe[PROBE_SIZE];
|
||||
int len = stream_read_peek(s, probe, sizeof(probe));
|
||||
if (len < 1 || !mp_probe_cue((bstr){probe, len}))
|
||||
return -1;
|
||||
}
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
|
||||
+2
-1
@@ -296,6 +296,7 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
struct demuxer_params params = {
|
||||
.force_format = "+lavf",
|
||||
.external_stream = demuxer->stream,
|
||||
.stream_flags = demuxer->stream_origin,
|
||||
};
|
||||
|
||||
struct stream *cur = demuxer->stream;
|
||||
@@ -321,7 +322,7 @@ static int d_open(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
// Initialize the playback time. We need to read _some_ data to get the
|
||||
// correct stream-layer time (at least with libdvdnav).
|
||||
stream_peek(demuxer->stream, 1);
|
||||
stream_read_peek(demuxer->stream, &(char){0}, 1);
|
||||
reset_pts(demuxer);
|
||||
|
||||
p->slave = demux_open_url("-", ¶ms, demuxer->cancel, demuxer->global);
|
||||
|
||||
+11
-21
@@ -61,7 +61,6 @@ struct tl_root {
|
||||
|
||||
struct priv {
|
||||
bstr data;
|
||||
bool allow_any;
|
||||
};
|
||||
|
||||
// Parse a time (absolute file time or duration). Currently equivalent to a
|
||||
@@ -198,11 +197,12 @@ static struct demuxer *open_source(struct timeline *root,
|
||||
{
|
||||
for (int n = 0; n < tl->num_parts; n++) {
|
||||
struct demuxer *d = tl->parts[n].source;
|
||||
if (d && strcmp(d->stream->url, filename) == 0)
|
||||
if (d && d->filename && strcmp(d->filename, filename) == 0)
|
||||
return d;
|
||||
}
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = tl->init_fragment,
|
||||
.stream_flags = root->stream_origin,
|
||||
};
|
||||
struct demuxer *d = demux_open_url(filename, ¶ms, root->cancel,
|
||||
root->global);
|
||||
@@ -268,7 +268,8 @@ static struct timeline_par *build_timeline(struct timeline *root,
|
||||
|
||||
if (parts->init_fragment_url && parts->init_fragment_url[0]) {
|
||||
MP_VERBOSE(root, "Opening init fragment...\n");
|
||||
stream_t *s = stream_create(parts->init_fragment_url, STREAM_READ,
|
||||
stream_t *s = stream_create(parts->init_fragment_url,
|
||||
STREAM_READ | root->stream_origin,
|
||||
root->cancel, root->global);
|
||||
if (s) {
|
||||
root->is_network |= s->is_network;
|
||||
@@ -282,6 +283,7 @@ static struct timeline_par *build_timeline(struct timeline *root,
|
||||
}
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = tl->init_fragment,
|
||||
.stream_flags = root->stream_origin,
|
||||
};
|
||||
tl->track_layout = demux_open_url("memory://", ¶ms, root->cancel,
|
||||
root->global);
|
||||
@@ -366,6 +368,8 @@ static struct timeline_par *build_timeline(struct timeline *root,
|
||||
|
||||
if (source && !tl->track_layout && part->is_layout)
|
||||
tl->track_layout = source;
|
||||
|
||||
tl->num_parts++;
|
||||
}
|
||||
|
||||
if (!tl->track_layout) {
|
||||
@@ -385,7 +389,7 @@ static struct timeline_par *build_timeline(struct timeline *root,
|
||||
if (!root->meta)
|
||||
root->meta = tl->track_layout;
|
||||
|
||||
tl->num_parts = parts->num_parts;
|
||||
assert(tl->num_parts == parts->num_parts);
|
||||
return tl;
|
||||
|
||||
error:
|
||||
@@ -393,18 +397,6 @@ error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// For security, don't allow relative or absolute paths, only plain filenames.
|
||||
// Also, make these filenames relative to the edl source file.
|
||||
static void fix_filenames(struct tl_parts *parts, char *source_path)
|
||||
{
|
||||
struct bstr dirname = mp_dirname(source_path);
|
||||
for (int n = 0; n < parts->num_parts; n++) {
|
||||
struct tl_part *part = &parts->parts[n];
|
||||
char *filename = mp_basename(part->filename); // plain filename only
|
||||
part->filename = mp_path_join_bstr(parts, dirname, bstr0(filename));
|
||||
}
|
||||
}
|
||||
|
||||
static void build_mpv_edl_timeline(struct timeline *tl)
|
||||
{
|
||||
struct priv *p = tl->demuxer->priv;
|
||||
@@ -421,8 +413,6 @@ static void build_mpv_edl_timeline(struct timeline *tl)
|
||||
|
||||
for (int n = 0; n < root->num_pars; n++) {
|
||||
struct tl_parts *parts = root->pars[n];
|
||||
if (!p->allow_any)
|
||||
fix_filenames(parts, tl->demuxer->filename);
|
||||
struct timeline_par *par = build_timeline(tl, parts);
|
||||
if (!par)
|
||||
break;
|
||||
@@ -454,12 +444,12 @@ static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
struct stream *s = demuxer->stream;
|
||||
if (s->info && strcmp(s->info->name, "edl") == 0) {
|
||||
p->data = bstr0(s->path);
|
||||
// Source is edl:// and not .edl => allow arbitrary paths
|
||||
p->allow_any = true;
|
||||
return 0;
|
||||
}
|
||||
if (check >= DEMUX_CHECK_UNSAFE) {
|
||||
if (!bstr_equals0(stream_peek(s, strlen(HEADER)), HEADER))
|
||||
char header[sizeof(HEADER) - 1];
|
||||
int len = stream_read_peek(s, header, sizeof(header));
|
||||
if (len != strlen(HEADER) || memcmp(header, HEADER, len) != 0)
|
||||
return -1;
|
||||
}
|
||||
p->data = stream_read_complete(s, demuxer, 1000000);
|
||||
|
||||
+51
-17
@@ -79,6 +79,7 @@ struct demux_lavf_opts {
|
||||
char *sub_cp;
|
||||
int rtsp_transport;
|
||||
int linearize_ts;
|
||||
int propagate_opts;
|
||||
};
|
||||
|
||||
const struct m_sub_options demux_lavf_conf = {
|
||||
@@ -104,6 +105,7 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
{"http", 3})),
|
||||
OPT_CHOICE("demuxer-lavf-linearize-timestamps", linearize_ts, 0,
|
||||
({"no", 0}, {"auto", -1}, {"yes", 1})),
|
||||
OPT_FLAG("demuxer-lavf-propagate-opts", propagate_opts, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_lavf_opts),
|
||||
@@ -118,6 +120,7 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
.sub_cp = "auto",
|
||||
.rtsp_transport = 2,
|
||||
.linearize_ts = -1,
|
||||
.propagate_opts = 1,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -131,7 +134,7 @@ struct format_hack {
|
||||
bool max_probe : 1; // use probescore only if max. probe size reached
|
||||
bool ignore : 1; // blacklisted
|
||||
bool no_stream : 1; // do not wrap struct stream as AVIOContext
|
||||
bool use_stream_ids : 1; // export the native stream IDs
|
||||
bool use_stream_ids : 1; // has a meaningful native stream IDs (export it)
|
||||
bool fully_read : 1; // set demuxer.fully_read flag
|
||||
bool detect_charset : 1; // format is a small text file, possibly not UTF8
|
||||
bool image_format : 1; // expected to contain exactly 1 frame
|
||||
@@ -163,9 +166,12 @@ static const struct format_hack format_hacks[] = {
|
||||
{"sdp", .clear_filepos = true, .is_network = true, .no_seek = true},
|
||||
{"mpeg", .use_stream_ids = true},
|
||||
{"mpegts", .use_stream_ids = true},
|
||||
|
||||
{"mp4", .skipinfo = true, .fix_editlists = true, .no_pcm_seek = true},
|
||||
{"matroska", .skipinfo = true, .no_pcm_seek = true},
|
||||
{"mxf", .use_stream_ids = true},
|
||||
{"avi", .use_stream_ids = true},
|
||||
{"asf", .use_stream_ids = true},
|
||||
{"mp4", .skipinfo = true, .fix_editlists = true, .no_pcm_seek = true,
|
||||
.use_stream_ids = true},
|
||||
{"matroska", .skipinfo = true, .no_pcm_seek = true, .use_stream_ids = true},
|
||||
|
||||
{"v4l2", .no_seek = true},
|
||||
|
||||
@@ -175,7 +181,7 @@ static const struct format_hack format_hacks[] = {
|
||||
|
||||
// Some Ogg shoutcast streams are essentially concatenated OGG files. They
|
||||
// reset timestamps, which causes all sorts of problems.
|
||||
{"ogg", .linearize_audio_ts = true},
|
||||
{"ogg", .linearize_audio_ts = true, .use_stream_ids = true},
|
||||
|
||||
TEXTSUB("aqtitle"), TEXTSUB("jacosub"), TEXTSUB("microdvd"),
|
||||
TEXTSUB("mpl2"), TEXTSUB("mpsub"), TEXTSUB("pjs"), TEXTSUB("realtext"),
|
||||
@@ -232,6 +238,8 @@ typedef struct lavf_priv {
|
||||
int linearize_ts;
|
||||
bool any_ts_fixed;
|
||||
|
||||
AVDictionary *av_opts;
|
||||
|
||||
// Proxying nested streams.
|
||||
struct nested_stream *nested;
|
||||
int num_nested;
|
||||
@@ -282,6 +290,8 @@ static int mp_read(void *opaque, uint8_t *buf, int size)
|
||||
struct demuxer *demuxer = opaque;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *stream = priv->stream;
|
||||
if (!stream)
|
||||
return 0;
|
||||
|
||||
int ret = stream_read_partial(stream, buf, size);
|
||||
|
||||
@@ -295,6 +305,8 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
struct demuxer *demuxer = opaque;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct stream *stream = priv->stream;
|
||||
if (!stream)
|
||||
return -1;
|
||||
|
||||
MP_TRACE(demuxer, "mp_seek(%p, %"PRId64", %s)\n", stream, pos,
|
||||
whence == SEEK_END ? "end" :
|
||||
@@ -337,7 +349,7 @@ static int64_t mp_read_seek(void *opaque, int stream_idx, int64_t ts, int flags)
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
if (stream_control(stream, STREAM_CTRL_AVSEEK, &cmd) == STREAM_OK) {
|
||||
if (stream && stream_control(stream, STREAM_CTRL_AVSEEK, &cmd) == STREAM_OK) {
|
||||
stream_drop_buffers(stream);
|
||||
return 0;
|
||||
}
|
||||
@@ -357,7 +369,7 @@ static void convert_charset(struct demuxer *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
char *cp = priv->opts->sub_cp;
|
||||
if (!cp || mp_charset_is_utf8(cp))
|
||||
if (!cp || !cp[0] || mp_charset_is_utf8(cp))
|
||||
return;
|
||||
bstr data = stream_read_complete(priv->stream, NULL, 128 * 1024 * 1024);
|
||||
if (!data.start) {
|
||||
@@ -458,11 +470,10 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
} else {
|
||||
int nsize = av_clip(avpd.buf_size * 2, INITIAL_PROBE_SIZE,
|
||||
PROBE_BUF_SIZE);
|
||||
bstr buf = stream_peek(s, nsize);
|
||||
if (buf.len <= avpd.buf_size)
|
||||
nsize = stream_read_peek(s, avpd.buf, nsize);
|
||||
if (nsize <= avpd.buf_size)
|
||||
final_probe = true;
|
||||
memcpy(avpd.buf, buf.start, buf.len);
|
||||
avpd.buf_size = buf.len;
|
||||
avpd.buf_size = nsize;
|
||||
|
||||
priv->avif = av_probe_input_format2(&avpd, avpd.buf_size > 0, &score);
|
||||
}
|
||||
@@ -852,8 +863,27 @@ static int nested_io_open(struct AVFormatContext *s, AVIOContext **pb,
|
||||
struct demuxer *demuxer = s->opaque;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
|
||||
if (priv->opts->propagate_opts) {
|
||||
// Copy av_opts to options, but only entries that are not present in
|
||||
// options. (Hope this will break less by not overwriting important
|
||||
// settings.)
|
||||
AVDictionaryEntry *cur = NULL;
|
||||
while ((cur = av_dict_get(priv->av_opts, "", cur, AV_DICT_IGNORE_SUFFIX)))
|
||||
{
|
||||
if (!*options || !av_dict_get(*options, cur->key, NULL, 0)) {
|
||||
MP_TRACE(demuxer, "Nested option: '%s'='%s'\n",
|
||||
cur->key, cur->value);
|
||||
av_dict_set(options, cur->key, cur->value, 0);
|
||||
} else {
|
||||
MP_TRACE(demuxer, "Skipping nested option: '%s'\n", cur->key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int r = priv->default_io_open(s, pb, url, flags, options);
|
||||
if (r >= 0) {
|
||||
if (options)
|
||||
mp_avdict_print_unset(demuxer->log, MSGL_TRACE, *options);
|
||||
struct nested_stream nest = {
|
||||
.id = *pb,
|
||||
};
|
||||
@@ -926,7 +956,8 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
AVDictionary *dopts = NULL;
|
||||
|
||||
if ((priv->avif_flags & AVFMT_NOFILE) || priv->format_hack.no_stream) {
|
||||
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log);
|
||||
mp_setup_av_network_options(&dopts, priv->avif->name,
|
||||
demuxer->global, demuxer->log);
|
||||
// This might be incorrect.
|
||||
demuxer->seekable = true;
|
||||
} else {
|
||||
@@ -972,16 +1003,19 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
if (demuxer->access_references) {
|
||||
priv->default_io_open = avfc->io_open;
|
||||
priv->default_io_close = avfc->io_close;
|
||||
#if !HAVE_FFMPEG_STRICT_ABI
|
||||
avfc->io_open = nested_io_open;
|
||||
avfc->io_close = nested_io_close;
|
||||
#endif
|
||||
} else {
|
||||
avfc->io_open = block_io_open;
|
||||
}
|
||||
|
||||
mp_set_avdict(&dopts, lavfdopts->avopts);
|
||||
|
||||
if (av_dict_copy(&priv->av_opts, dopts, 0) < 0) {
|
||||
av_dict_free(&dopts);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (avformat_open_input(&avfc, priv->filename, priv->avif, &dopts) < 0) {
|
||||
MP_ERR(demuxer, "avformat_open_input() failed\n");
|
||||
av_dict_free(&dopts);
|
||||
@@ -1076,7 +1110,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
priv->own_stream = false;
|
||||
priv->stream = demuxer->stream;
|
||||
priv->stream = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1096,7 +1130,7 @@ static bool demux_lavf_read_packet(struct demuxer *demux,
|
||||
return true;
|
||||
if (r == AVERROR_EOF)
|
||||
return false;
|
||||
MP_WARN(demux, "error reading packet.\n");
|
||||
MP_WARN(demux, "error reading packet: %s.\n", av_err2str(r));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1197,7 +1231,7 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
|
||||
if (flags & SEEK_FACTOR) {
|
||||
struct stream *s = priv->stream;
|
||||
int64_t end = stream_get_size(s);
|
||||
int64_t end = s ? stream_get_size(s) : -1;
|
||||
if (end > 0 && demuxer->ts_resets_possible &&
|
||||
!(priv->avif_flags & AVFMT_NO_BYTE_SEEK))
|
||||
{
|
||||
|
||||
@@ -43,15 +43,17 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
probe_size *= 100;
|
||||
}
|
||||
|
||||
bstr probe = stream_peek(demuxer->stream, probe_size);
|
||||
if (probe.len == 0)
|
||||
void *probe = ta_alloc_size(NULL, probe_size);
|
||||
if (!probe)
|
||||
return -1;
|
||||
int probe_got = stream_read_peek(demuxer->stream, probe, probe_size);
|
||||
struct stream *probe_stream =
|
||||
stream_memory_open(demuxer->global, probe.start, probe.len);
|
||||
stream_memory_open(demuxer->global, probe, probe_got);
|
||||
struct mp_archive *mpa = mp_archive_new(mp_null_log, probe_stream, flags);
|
||||
bool ok = !!mpa;
|
||||
free_stream(probe_stream);
|
||||
mp_archive_free(mpa);
|
||||
ta_free(probe);
|
||||
if (!ok)
|
||||
return -1;
|
||||
|
||||
@@ -62,9 +64,6 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
struct playlist *pl = talloc_zero(demuxer, struct playlist);
|
||||
demuxer->playlist = pl;
|
||||
|
||||
// make it load archive://
|
||||
pl->disable_safety = true;
|
||||
|
||||
char *prefix = mp_url_escape(mpa, demuxer->stream->url, "~|");
|
||||
|
||||
char **files = NULL;
|
||||
@@ -72,7 +71,7 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
|
||||
while (mp_archive_next_entry(mpa)) {
|
||||
// stream_libarchive.c does the real work
|
||||
char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix,
|
||||
char *f = talloc_asprintf(mpa, "archive://%s|/%s", prefix,
|
||||
mpa->entry_filename);
|
||||
MP_TARRAY_APPEND(mpa, files, num_files, f);
|
||||
}
|
||||
@@ -83,6 +82,9 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
for (int n = 0; n < num_files; n++)
|
||||
playlist_add_file(pl, files[n]);
|
||||
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next)
|
||||
e->stream_flags = demuxer->stream_origin;
|
||||
|
||||
demuxer->filetype = "archive";
|
||||
demuxer->fully_read = true;
|
||||
|
||||
|
||||
+40
-23
@@ -15,6 +15,7 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
@@ -55,8 +56,9 @@ static void mf_add(mf_t *mf, const char *fname)
|
||||
MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry);
|
||||
}
|
||||
|
||||
static mf_t *open_mf_pattern(void *talloc_ctx, struct mp_log *log, char *filename)
|
||||
static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename)
|
||||
{
|
||||
struct mp_log *log = d->log;
|
||||
int error_count = 0;
|
||||
int count = 0;
|
||||
|
||||
@@ -64,21 +66,33 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct mp_log *log, char *filenam
|
||||
mf->log = log;
|
||||
|
||||
if (filename[0] == '@') {
|
||||
FILE *lst_f = fopen(filename + 1, "r");
|
||||
if (lst_f) {
|
||||
char *fname = talloc_size(mf, 512);
|
||||
while (fgets(fname, 512, lst_f)) {
|
||||
/* remove spaces from end of fname */
|
||||
char *t = fname + strlen(fname) - 1;
|
||||
while (t > fname && mp_isspace(*t))
|
||||
*(t--) = 0;
|
||||
if (!mp_path_exists(fname)) {
|
||||
mp_verbose(log, "file not found: '%s'\n", fname);
|
||||
} else {
|
||||
mf_add(mf, fname);
|
||||
struct stream *s = stream_create(filename + 1,
|
||||
d->stream_origin | STREAM_READ, d->cancel, d->global);
|
||||
if (s) {
|
||||
while (1) {
|
||||
char buf[512];
|
||||
int len = stream_read_peek(s, buf, sizeof(buf));
|
||||
if (!len)
|
||||
break;
|
||||
bstr data = (bstr){buf, len};
|
||||
int pos = bstrchr(data, '\n');
|
||||
data = bstr_splice(data, 0, pos < 0 ? data.len : pos + 1);
|
||||
bstr fname = bstr_strip(data);
|
||||
if (fname.len) {
|
||||
if (bstrchr(fname, '\0') >= 0) {
|
||||
mp_err(log, "invalid filename\n");
|
||||
break;
|
||||
}
|
||||
char *entry = bstrto0(mf, fname);
|
||||
if (!mp_path_exists(entry)) {
|
||||
mp_verbose(log, "file not found: '%s'\n", entry);
|
||||
} else {
|
||||
MP_TARRAY_APPEND(mf, mf->names, mf->nr_of_files, entry);
|
||||
}
|
||||
}
|
||||
stream_seek_skip(s, stream_tell(s) + data.len);
|
||||
}
|
||||
fclose(lst_f);
|
||||
free_stream(s);
|
||||
|
||||
mp_info(log, "number of files: %d\n", mf->nr_of_files);
|
||||
goto exit_mf;
|
||||
@@ -163,14 +177,15 @@ static mf_t *open_mf_single(void *talloc_ctx, struct mp_log *log, char *filename
|
||||
static void demux_seek_mf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
{
|
||||
mf_t *mf = demuxer->priv;
|
||||
int newpos = seek_pts * mf->sh->codec->fps;
|
||||
double newpos = seek_pts * mf->sh->codec->fps;
|
||||
if (flags & SEEK_FACTOR)
|
||||
newpos = seek_pts * (mf->nr_of_files - 1);
|
||||
if (newpos < 0)
|
||||
newpos = 0;
|
||||
if (newpos >= mf->nr_of_files)
|
||||
newpos = mf->nr_of_files;
|
||||
mf->curr_frame = newpos;
|
||||
if (flags & SEEK_FORWARD) {
|
||||
newpos = ceil(newpos);
|
||||
} else {
|
||||
newpos = MPMIN(floor(newpos), mf->nr_of_files - 1);
|
||||
}
|
||||
mf->curr_frame = MPCLAMP((int)newpos, 0, mf->nr_of_files);
|
||||
}
|
||||
|
||||
static bool demux_mf_read_packet(struct demuxer *demuxer,
|
||||
@@ -186,8 +201,10 @@ static bool demux_mf_read_packet(struct demuxer *demuxer,
|
||||
struct stream *stream = entry_stream;
|
||||
if (!stream) {
|
||||
char *filename = mf->names[mf->curr_frame];
|
||||
if (filename)
|
||||
stream = stream_open(filename, demuxer->global);
|
||||
if (filename) {
|
||||
stream = stream_create(filename, demuxer->stream_origin | STREAM_READ,
|
||||
demuxer->cancel, demuxer->global);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
@@ -295,7 +312,7 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
if (strncmp(demuxer->stream->url, "mf://", 5) == 0 &&
|
||||
demuxer->stream->info && strcmp(demuxer->stream->info->name, "mf") == 0)
|
||||
{
|
||||
mf = open_mf_pattern(demuxer, demuxer->log, demuxer->stream->url + 5);
|
||||
mf = open_mf_pattern(demuxer, demuxer, demuxer->stream->url + 5);
|
||||
} else {
|
||||
mf = open_mf_single(demuxer, demuxer->log, demuxer->stream->url);
|
||||
int bog = 0;
|
||||
|
||||
+64
-74
@@ -404,7 +404,7 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
|
||||
stream_t *s = demuxer->stream;
|
||||
int res = 0;
|
||||
|
||||
MP_VERBOSE(demuxer, "|+ segment information...\n");
|
||||
MP_DBG(demuxer, "|+ segment information...\n");
|
||||
|
||||
mkv_d->tc_scale = 1000000;
|
||||
mkv_d->duration = 0;
|
||||
@@ -414,12 +414,12 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
|
||||
if (ebml_read_element(s, &parse_ctx, &info, &ebml_info_desc) < 0)
|
||||
return -1;
|
||||
if (info.muxing_app)
|
||||
MP_VERBOSE(demuxer, "| + muxing app: %s\n", info.muxing_app);
|
||||
MP_DBG(demuxer, "| + muxing app: %s\n", info.muxing_app);
|
||||
if (info.writing_app)
|
||||
MP_VERBOSE(demuxer, "| + writing app: %s\n", info.writing_app);
|
||||
MP_DBG(demuxer, "| + writing app: %s\n", info.writing_app);
|
||||
if (info.n_timecode_scale) {
|
||||
mkv_d->tc_scale = info.timecode_scale;
|
||||
MP_VERBOSE(demuxer, "| + timecode scale: %"PRId64"\n", mkv_d->tc_scale);
|
||||
MP_DBG(demuxer, "| + timecode scale: %"PRId64"\n", mkv_d->tc_scale);
|
||||
if (mkv_d->tc_scale < 1 || mkv_d->tc_scale > INT_MAX) {
|
||||
res = -1;
|
||||
goto out;
|
||||
@@ -427,7 +427,7 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
|
||||
}
|
||||
if (info.n_duration) {
|
||||
mkv_d->duration = info.duration * mkv_d->tc_scale / 1e9;
|
||||
MP_VERBOSE(demuxer, "| + duration: %.3fs\n",
|
||||
MP_DBG(demuxer, "| + duration: %.3fs\n",
|
||||
mkv_d->duration);
|
||||
demuxer->duration = mkv_d->duration;
|
||||
}
|
||||
@@ -441,11 +441,11 @@ static int demux_mkv_read_info(demuxer_t *demuxer)
|
||||
} else {
|
||||
memcpy(demuxer->matroska_data.uid.segment, info.segment_uid.start,
|
||||
len);
|
||||
MP_VERBOSE(demuxer, "| + segment uid");
|
||||
MP_DBG(demuxer, "| + segment uid");
|
||||
for (size_t i = 0; i < len; i++)
|
||||
MP_VERBOSE(demuxer, " %02x",
|
||||
MP_DBG(demuxer, " %02x",
|
||||
demuxer->matroska_data.uid.segment[i]);
|
||||
MP_VERBOSE(demuxer, "\n");
|
||||
MP_DBG(demuxer, "\n");
|
||||
}
|
||||
}
|
||||
if (demuxer->params && demuxer->params->matroska_wanted_uids) {
|
||||
@@ -539,23 +539,23 @@ static void parse_trackaudio(struct demuxer *demuxer, struct mkv_track *track,
|
||||
{
|
||||
if (audio->n_sampling_frequency) {
|
||||
track->a_sfreq = audio->sampling_frequency;
|
||||
MP_VERBOSE(demuxer, "| + Sampling frequency: %f\n", track->a_sfreq);
|
||||
MP_DBG(demuxer, "| + Sampling frequency: %f\n", track->a_sfreq);
|
||||
} else {
|
||||
track->a_sfreq = 8000;
|
||||
}
|
||||
if (audio->n_output_sampling_frequency) {
|
||||
track->a_osfreq = audio->output_sampling_frequency;
|
||||
MP_VERBOSE(demuxer, "| + Output sampling frequency: %f\n", track->a_osfreq);
|
||||
MP_DBG(demuxer, "| + Output sampling frequency: %f\n", track->a_osfreq);
|
||||
} else {
|
||||
track->a_osfreq = track->a_sfreq;
|
||||
}
|
||||
if (audio->n_bit_depth) {
|
||||
track->a_bps = audio->bit_depth;
|
||||
MP_VERBOSE(demuxer, "| + Bit depth: %"PRIu32"\n", track->a_bps);
|
||||
MP_DBG(demuxer, "| + Bit depth: %"PRIu32"\n", track->a_bps);
|
||||
}
|
||||
if (audio->n_channels) {
|
||||
track->a_channels = audio->channels;
|
||||
MP_VERBOSE(demuxer, "| + Channels: %"PRIu32"\n", track->a_channels);
|
||||
MP_DBG(demuxer, "| + Channels: %"PRIu32"\n", track->a_channels);
|
||||
} else {
|
||||
track->a_channels = 1;
|
||||
}
|
||||
@@ -569,27 +569,27 @@ static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track,
|
||||
// so we can just re-use our avcol_ conversion functions.
|
||||
if (colour->n_matrix_coefficients) {
|
||||
track->color.space = avcol_spc_to_mp_csp(colour->matrix_coefficients);
|
||||
MP_VERBOSE(demuxer, "| + Matrix: %s\n",
|
||||
MP_DBG(demuxer, "| + Matrix: %s\n",
|
||||
m_opt_choice_str(mp_csp_names, track->color.space));
|
||||
}
|
||||
if (colour->n_primaries) {
|
||||
track->color.primaries = avcol_pri_to_mp_csp_prim(colour->primaries);
|
||||
MP_VERBOSE(demuxer, "| + Primaries: %s\n",
|
||||
MP_DBG(demuxer, "| + Primaries: %s\n",
|
||||
m_opt_choice_str(mp_csp_prim_names, track->color.primaries));
|
||||
}
|
||||
if (colour->n_transfer_characteristics) {
|
||||
track->color.gamma = avcol_trc_to_mp_csp_trc(colour->transfer_characteristics);
|
||||
MP_VERBOSE(demuxer, "| + Gamma: %s\n",
|
||||
MP_DBG(demuxer, "| + Gamma: %s\n",
|
||||
m_opt_choice_str(mp_csp_trc_names, track->color.gamma));
|
||||
}
|
||||
if (colour->n_range) {
|
||||
track->color.levels = avcol_range_to_mp_csp_levels(colour->range);
|
||||
MP_VERBOSE(demuxer, "| + Levels: %s\n",
|
||||
MP_DBG(demuxer, "| + Levels: %s\n",
|
||||
m_opt_choice_str(mp_csp_levels_names, track->color.levels));
|
||||
}
|
||||
if (colour->n_max_cll) {
|
||||
track->color.sig_peak = colour->max_cll / MP_REF_WHITE;
|
||||
MP_VERBOSE(demuxer, "| + MaxCLL: %"PRIu64"\n", colour->max_cll);
|
||||
MP_DBG(demuxer, "| + MaxCLL: %"PRIu64"\n", colour->max_cll);
|
||||
}
|
||||
// if MaxCLL is unavailable, try falling back to the mastering metadata
|
||||
if (!track->color.sig_peak && colour->n_mastering_metadata) {
|
||||
@@ -597,7 +597,7 @@ static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track,
|
||||
|
||||
if (mastering->n_luminance_max) {
|
||||
track->color.sig_peak = mastering->luminance_max / MP_REF_WHITE;
|
||||
MP_VERBOSE(demuxer, "| + HDR peak: %f\n", track->color.sig_peak);
|
||||
MP_DBG(demuxer, "| + HDR peak: %f\n", track->color.sig_peak);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,39 +606,36 @@ static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_video *video)
|
||||
{
|
||||
if (video->n_frame_rate) {
|
||||
MP_VERBOSE(demuxer, "| + Frame rate: %f (ignored)\n", video->frame_rate);
|
||||
MP_DBG(demuxer, "| + Frame rate: %f (ignored)\n", video->frame_rate);
|
||||
}
|
||||
if (video->n_display_width) {
|
||||
track->v_dwidth = video->display_width;
|
||||
track->v_dwidth_set = true;
|
||||
MP_VERBOSE(demuxer, "| + Display width: %"PRIu32"\n",
|
||||
track->v_dwidth);
|
||||
MP_DBG(demuxer, "| + Display width: %"PRIu32"\n", track->v_dwidth);
|
||||
}
|
||||
if (video->n_display_height) {
|
||||
track->v_dheight = video->display_height;
|
||||
track->v_dheight_set = true;
|
||||
MP_VERBOSE(demuxer, "| + Display height: %"PRIu32"\n",
|
||||
track->v_dheight);
|
||||
MP_DBG(demuxer, "| + Display height: %"PRIu32"\n", track->v_dheight);
|
||||
}
|
||||
if (video->n_pixel_width) {
|
||||
track->v_width = video->pixel_width;
|
||||
MP_VERBOSE(demuxer, "| + Pixel width: %"PRIu32"\n", track->v_width);
|
||||
MP_DBG(demuxer, "| + Pixel width: %"PRIu32"\n", track->v_width);
|
||||
}
|
||||
if (video->n_pixel_height) {
|
||||
track->v_height = video->pixel_height;
|
||||
MP_VERBOSE(demuxer, "| + Pixel height: %"PRIu32"\n", track->v_height);
|
||||
MP_DBG(demuxer, "| + Pixel height: %"PRIu32"\n", track->v_height);
|
||||
}
|
||||
if (video->n_colour_space && video->colour_space.len == 4) {
|
||||
uint8_t *d = (uint8_t *)&video->colour_space.start[0];
|
||||
track->colorspace = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
|
||||
MP_VERBOSE(demuxer, "| + Colorspace: %#"PRIx32"\n",
|
||||
track->colorspace);
|
||||
MP_DBG(demuxer, "| + Colorspace: %#"PRIx32"\n", track->colorspace);
|
||||
}
|
||||
if (video->n_stereo_mode) {
|
||||
const char *name = MP_STEREO3D_NAME(video->stereo_mode);
|
||||
if (name) {
|
||||
track->stereo_mode = video->stereo_mode;
|
||||
MP_VERBOSE(demuxer, "| + StereoMode: %s\n", name);
|
||||
MP_DBG(demuxer, "| + StereoMode: %s\n", name);
|
||||
} else {
|
||||
MP_WARN(demuxer, "Unknown StereoMode: %"PRIu64"\n",
|
||||
video->stereo_mode);
|
||||
@@ -668,7 +665,7 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
|
||||
track->tnum = entry->track_number;
|
||||
if (track->tnum) {
|
||||
MP_VERBOSE(demuxer, "| + Track number: %d\n", track->tnum);
|
||||
MP_DBG(demuxer, "| + Track number: %d\n", track->tnum);
|
||||
} else {
|
||||
MP_ERR(demuxer, "Missing track number!\n");
|
||||
}
|
||||
@@ -676,39 +673,39 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
|
||||
if (entry->name) {
|
||||
track->name = talloc_strdup(track, entry->name);
|
||||
MP_VERBOSE(demuxer, "| + Name: %s\n", track->name);
|
||||
MP_DBG(demuxer, "| + Name: %s\n", track->name);
|
||||
}
|
||||
|
||||
track->type = entry->track_type;
|
||||
MP_VERBOSE(demuxer, "| + Track type: ");
|
||||
MP_DBG(demuxer, "| + Track type: ");
|
||||
switch (track->type) {
|
||||
case MATROSKA_TRACK_AUDIO:
|
||||
MP_VERBOSE(demuxer, "Audio\n");
|
||||
MP_DBG(demuxer, "Audio\n");
|
||||
break;
|
||||
case MATROSKA_TRACK_VIDEO:
|
||||
MP_VERBOSE(demuxer, "Video\n");
|
||||
MP_DBG(demuxer, "Video\n");
|
||||
break;
|
||||
case MATROSKA_TRACK_SUBTITLE:
|
||||
MP_VERBOSE(demuxer, "Subtitle\n");
|
||||
MP_DBG(demuxer, "Subtitle\n");
|
||||
break;
|
||||
default:
|
||||
MP_VERBOSE(demuxer, "unknown\n");
|
||||
MP_DBG(demuxer, "unknown\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry->n_audio) {
|
||||
MP_VERBOSE(demuxer, "| + Audio track\n");
|
||||
MP_DBG(demuxer, "| + Audio track\n");
|
||||
parse_trackaudio(demuxer, track, &entry->audio);
|
||||
}
|
||||
|
||||
if (entry->n_video) {
|
||||
MP_VERBOSE(demuxer, "| + Video track\n");
|
||||
MP_DBG(demuxer, "| + Video track\n");
|
||||
parse_trackvideo(demuxer, track, &entry->video);
|
||||
}
|
||||
|
||||
if (entry->codec_id) {
|
||||
track->codec_id = talloc_strdup(track, entry->codec_id);
|
||||
MP_VERBOSE(demuxer, "| + Codec ID: %s\n", track->codec_id);
|
||||
MP_DBG(demuxer, "| + Codec ID: %s\n", track->codec_id);
|
||||
} else {
|
||||
MP_ERR(demuxer, "Missing codec ID!\n");
|
||||
track->codec_id = "";
|
||||
@@ -719,36 +716,36 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
track->private_data = talloc_size(track, len + AV_LZO_INPUT_PADDING);
|
||||
memcpy(track->private_data, entry->codec_private.start, len);
|
||||
track->private_size = len;
|
||||
MP_VERBOSE(demuxer, "| + CodecPrivate, length %u\n", track->private_size);
|
||||
MP_DBG(demuxer, "| + CodecPrivate, length %u\n", track->private_size);
|
||||
}
|
||||
|
||||
if (entry->language) {
|
||||
track->language = talloc_strdup(track, entry->language);
|
||||
MP_VERBOSE(demuxer, "| + Language: %s\n", track->language);
|
||||
MP_DBG(demuxer, "| + Language: %s\n", track->language);
|
||||
} else {
|
||||
track->language = talloc_strdup(track, "eng");
|
||||
}
|
||||
|
||||
if (entry->n_flag_default) {
|
||||
track->default_track = entry->flag_default;
|
||||
MP_VERBOSE(demuxer, "| + Default flag: %d\n", track->default_track);
|
||||
MP_DBG(demuxer, "| + Default flag: %d\n", track->default_track);
|
||||
} else {
|
||||
track->default_track = 1;
|
||||
}
|
||||
|
||||
if (entry->n_flag_forced) {
|
||||
track->forced_track = entry->flag_forced;
|
||||
MP_VERBOSE(demuxer, "| + Forced flag: %d\n", track->forced_track);
|
||||
MP_DBG(demuxer, "| + Forced flag: %d\n", track->forced_track);
|
||||
}
|
||||
|
||||
if (entry->n_default_duration) {
|
||||
track->default_duration = entry->default_duration / 1e9;
|
||||
if (entry->default_duration == 0) {
|
||||
MP_VERBOSE(demuxer, "| + Default duration: 0");
|
||||
MP_DBG(demuxer, "| + Default duration: 0");
|
||||
} else {
|
||||
track->v_frate = 1e9 / entry->default_duration;
|
||||
MP_VERBOSE(demuxer, "| + Default duration: %.3fms ( = %.3f fps)\n",
|
||||
entry->default_duration / 1000000.0, track->v_frate);
|
||||
MP_DBG(demuxer, "| + Default duration: %.3fms ( = %.3f fps)\n",
|
||||
entry->default_duration / 1000000.0, track->v_frate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,7 +763,7 @@ static int demux_mkv_read_tracks(demuxer_t *demuxer)
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
MP_VERBOSE(demuxer, "|+ segment tracks...\n");
|
||||
MP_DBG(demuxer, "|+ segment tracks...\n");
|
||||
|
||||
struct ebml_tracks tracks = {0};
|
||||
struct ebml_parse_ctx parse_ctx = {demuxer->log};
|
||||
@@ -776,7 +773,7 @@ static int demux_mkv_read_tracks(demuxer_t *demuxer)
|
||||
mkv_d->tracks = talloc_zero_array(mkv_d, struct mkv_track*,
|
||||
tracks.n_track_entry);
|
||||
for (int i = 0; i < tracks.n_track_entry; i++) {
|
||||
MP_VERBOSE(demuxer, "| + a track...\n");
|
||||
MP_DBG(demuxer, "| + a track...\n");
|
||||
parse_trackentry(demuxer, &tracks.track_entry[i]);
|
||||
}
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
@@ -895,7 +892,7 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
if (wanted_edition_uid)
|
||||
wanted_edition = -1;
|
||||
|
||||
MP_VERBOSE(demuxer, "Parsing chapters...\n");
|
||||
MP_DBG(demuxer, "Parsing chapters...\n");
|
||||
struct ebml_chapters file_chapters = {0};
|
||||
struct ebml_parse_ctx parse_ctx = {demuxer->log};
|
||||
if (ebml_read_element(s, &parse_ctx, &file_chapters,
|
||||
@@ -1001,15 +998,15 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
chapter.uid.edition = ca->chapter_segment_edition_uid;
|
||||
else
|
||||
chapter.uid.edition = 0;
|
||||
MP_VERBOSE(demuxer, "Chapter segment uid ");
|
||||
MP_DBG(demuxer, "Chapter segment uid ");
|
||||
for (int n = 0; n < len; n++)
|
||||
MP_VERBOSE(demuxer, "%02x ",
|
||||
MP_DBG(demuxer, "%02x ",
|
||||
chapter.uid.segment[n]);
|
||||
MP_VERBOSE(demuxer, "\n");
|
||||
MP_DBG(demuxer, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
MP_VERBOSE(demuxer, "Chapter %u from %02d:%02d:%02d.%03d "
|
||||
MP_DBG(demuxer, "Chapter %u from %02d:%02d:%02d.%03d "
|
||||
"to %02d:%02d:%02d.%03d, %s\n", i,
|
||||
(int) (chapter.start / 60 / 60 / 1000000000),
|
||||
(int) ((chapter.start / 60 / 1000000000) % 60),
|
||||
@@ -1116,7 +1113,7 @@ static int demux_mkv_read_attachments(demuxer_t *demuxer)
|
||||
{
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
MP_VERBOSE(demuxer, "Parsing attachments...\n");
|
||||
MP_DBG(demuxer, "Parsing attachments...\n");
|
||||
|
||||
struct ebml_attachments attachments = {0};
|
||||
struct ebml_parse_ctx parse_ctx = {demuxer->log};
|
||||
@@ -1135,8 +1132,8 @@ static int demux_mkv_read_attachments(demuxer_t *demuxer)
|
||||
char *mime = attachment->file_mime_type;
|
||||
demuxer_add_attachment(demuxer, name, mime, attachment->file_data.start,
|
||||
attachment->file_data.len);
|
||||
MP_VERBOSE(demuxer, "Attachment: %s, %s, %zu bytes\n",
|
||||
name, mime, attachment->file_data.len);
|
||||
MP_DBG(demuxer, "Attachment: %s, %s, %zu bytes\n",
|
||||
name, mime, attachment->file_data.len);
|
||||
}
|
||||
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
@@ -1191,7 +1188,7 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer)
|
||||
struct ebml_seek_head seekhead = {0};
|
||||
struct ebml_parse_ctx parse_ctx = {demuxer->log};
|
||||
|
||||
MP_VERBOSE(demuxer, "Parsing seek head...\n");
|
||||
MP_DBG(demuxer, "Parsing seek head...\n");
|
||||
if (ebml_read_element(s, &parse_ctx, &seekhead, &ebml_seek_head_desc) < 0) {
|
||||
res = -1;
|
||||
goto out;
|
||||
@@ -1289,7 +1286,6 @@ static void add_coverart(struct demuxer *demuxer)
|
||||
if (!codec)
|
||||
continue;
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
sh->demuxer_id = -1 - sh->index; // don't clash with mkv IDs
|
||||
sh->codec->codec = codec;
|
||||
sh->attached_picture = new_demux_packet_from(att->data, att->data_size);
|
||||
if (sh->attached_picture) {
|
||||
@@ -1594,7 +1590,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
// The private_data contains a WAVEFORMATEX struct
|
||||
if (track->private_size < 18)
|
||||
goto error;
|
||||
MP_VERBOSE(demuxer, "track with MS compat audio.\n");
|
||||
MP_DBG(demuxer, "track with MS compat audio.\n");
|
||||
unsigned char *h = track->private_data;
|
||||
sh_a->codec_tag = AV_RL16(h + 0); // wFormatTag
|
||||
if (track->a_channels == 0)
|
||||
@@ -1957,18 +1953,18 @@ static int read_mkv_segment_header(demuxer_t *demuxer, int64_t *segment_end)
|
||||
if (demuxer->params)
|
||||
num_skip = demuxer->params->matroska_wanted_segment;
|
||||
|
||||
while (!s->eof) {
|
||||
while (stream_read_peek(s, &(char){0}, 1)) {
|
||||
if (ebml_read_id(s) != MATROSKA_ID_SEGMENT) {
|
||||
MP_VERBOSE(demuxer, "segment not found\n");
|
||||
return 0;
|
||||
}
|
||||
MP_VERBOSE(demuxer, "+ a segment...\n");
|
||||
MP_DBG(demuxer, "+ a segment...\n");
|
||||
uint64_t len = ebml_read_length(s);
|
||||
*segment_end = (len == EBML_UINT_INVALID) ? 0 : stream_tell(s) + len;
|
||||
if (num_skip <= 0)
|
||||
return 1;
|
||||
num_skip--;
|
||||
MP_VERBOSE(demuxer, " (skipping)\n");
|
||||
MP_DBG(demuxer, " (skipping)\n");
|
||||
if (*segment_end <= 0)
|
||||
break;
|
||||
if (*segment_end >= stream_get_size(s))
|
||||
@@ -2002,16 +1998,12 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
if (demuxer->params)
|
||||
mkv_d->probably_webm_dash_init = demuxer->params->init_fragment.len > 0;
|
||||
|
||||
bstr start = stream_peek(s, 4);
|
||||
uint32_t start_id = 0;
|
||||
for (int n = 0; n < start.len; n++)
|
||||
start_id = (start_id << 8) | start.start[n];
|
||||
if (start_id != EBML_ID_EBML)
|
||||
// Make sure you can seek back after read_ebml_header() if no EBML ID.
|
||||
if (stream_read_peek(s, &(char[4]){0}, 4) != 4)
|
||||
return -1;
|
||||
|
||||
if (!read_ebml_header(demuxer))
|
||||
return -1;
|
||||
MP_VERBOSE(demuxer, "Found the head...\n");
|
||||
MP_DBG(demuxer, "Found the head...\n");
|
||||
|
||||
if (!read_mkv_segment_header(demuxer, &end_pos))
|
||||
return -1;
|
||||
@@ -2030,7 +2022,6 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
while (1) {
|
||||
start_pos = stream_tell(s);
|
||||
stream_peek(s, 4); // make sure we can always seek back
|
||||
uint32_t id = ebml_read_id(s);
|
||||
if (s->eof) {
|
||||
if (!mkv_d->probably_webm_dash_init)
|
||||
@@ -2038,7 +2029,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
break;
|
||||
}
|
||||
if (id == MATROSKA_ID_CLUSTER) {
|
||||
MP_VERBOSE(demuxer, "|+ found cluster\n");
|
||||
MP_DBG(demuxer, "|+ found cluster\n");
|
||||
mkv_d->cluster_start = start_pos;
|
||||
break;
|
||||
}
|
||||
@@ -2145,7 +2136,7 @@ static int demux_mkv_read_block_lacing(struct block_info *block, int type,
|
||||
uint8_t t;
|
||||
do {
|
||||
t = stream_read_char(s);
|
||||
if (stream_eof(s) || stream_tell(s) >= endpos)
|
||||
if (s->eof || stream_tell(s) >= endpos)
|
||||
goto error;
|
||||
lace_size[i] += t;
|
||||
} while (t == 0xFF);
|
||||
@@ -2588,7 +2579,7 @@ static int read_block(demuxer_t *demuxer, int64_t end, struct block_info *block)
|
||||
exit:
|
||||
if (res <= 0)
|
||||
free_block(block);
|
||||
stream_seek(s, endpos);
|
||||
stream_seek_skip(s, endpos);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -2839,7 +2830,6 @@ static int read_next_block_into_queue(demuxer_t *demuxer)
|
||||
find_next_cluster:
|
||||
mkv_d->cluster_end = 0;
|
||||
for (;;) {
|
||||
stream_peek(s, 4); // guarantee we can undo ebml_read_id() below
|
||||
mkv_d->cluster_start = stream_tell(s);
|
||||
uint32_t id = ebml_read_id(s);
|
||||
if (id == MATROSKA_ID_CLUSTER)
|
||||
@@ -3076,7 +3066,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
if (!(flags & SEEK_FACTOR)) { /* time in secs */
|
||||
mkv_index_t *index = NULL;
|
||||
|
||||
seek_pts = FFMAX(seek_pts, 0);
|
||||
seek_pts = MPMAX(seek_pts, 0);
|
||||
int64_t target_timecode = seek_pts * 1e9 + 0.5;
|
||||
|
||||
if (create_index_until(demuxer, target_timecode) >= 0) {
|
||||
|
||||
@@ -172,6 +172,7 @@ static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
|
||||
.matroska_wanted_segment = segment,
|
||||
.matroska_was_valid = &was_valid,
|
||||
.disable_timeline = true,
|
||||
.stream_flags = ctx->tl->stream_origin,
|
||||
};
|
||||
struct mp_cancel *cancel = ctx->tl->cancel;
|
||||
if (mp_cancel_test(cancel))
|
||||
@@ -515,7 +516,7 @@ void build_ordered_chapter_timeline(struct timeline *tl)
|
||||
.global = tl->global,
|
||||
.tl = tl,
|
||||
.demuxer = demuxer,
|
||||
.opts = mp_get_config_group(ctx, tl->global, GLOBAL_CONFIG),
|
||||
.opts = mp_get_config_group(ctx, tl->global, &mp_opt_root),
|
||||
};
|
||||
|
||||
if (!ctx->opts->ordered_chapters || !demuxer->access_references) {
|
||||
|
||||
+80
-4
@@ -20,6 +20,8 @@
|
||||
#include <strings.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/common.h"
|
||||
#include "options/options.h"
|
||||
@@ -60,9 +62,78 @@ struct pl_parser {
|
||||
char *format;
|
||||
};
|
||||
|
||||
|
||||
static uint16_t stream_read_word_endian(stream_t *s, bool big_endian)
|
||||
{
|
||||
unsigned int y = stream_read_char(s);
|
||||
y = (y << 8) | stream_read_char(s);
|
||||
if (!big_endian)
|
||||
y = ((y >> 8) & 0xFF) | (y << 8);
|
||||
return y;
|
||||
}
|
||||
|
||||
// Read characters until the next '\n' (including), or until the buffer in s is
|
||||
// exhausted.
|
||||
static int read_characters(stream_t *s, uint8_t *dst, int dstsize, int utf16)
|
||||
{
|
||||
if (utf16 == 1 || utf16 == 2) {
|
||||
uint8_t *cur = dst;
|
||||
while (1) {
|
||||
if ((cur - dst) + 8 >= dstsize) // PUT_UTF8 writes max. 8 bytes
|
||||
return -1; // line too long
|
||||
uint32_t c;
|
||||
uint8_t tmp;
|
||||
GET_UTF16(c, stream_read_word_endian(s, utf16 == 2), return -1;)
|
||||
if (s->eof)
|
||||
break; // legitimate EOF; ignore the case of partial reads
|
||||
PUT_UTF8(c, tmp, *cur++ = tmp;)
|
||||
if (c == '\n')
|
||||
break;
|
||||
}
|
||||
return cur - dst;
|
||||
} else {
|
||||
uint8_t buf[1024];
|
||||
int buf_len = stream_read_peek(s, buf, sizeof(buf));
|
||||
uint8_t *end = memchr(buf, '\n', buf_len);
|
||||
int len = end ? end - buf + 1 : buf_len;
|
||||
if (len > dstsize)
|
||||
return -1; // line too long
|
||||
memcpy(dst, buf, len);
|
||||
stream_seek_skip(s, stream_tell(s) + len);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
// On error, or if the line is larger than max-1, return NULL and unset s->eof.
|
||||
// On EOF, return NULL, and s->eof will be set.
|
||||
// Otherwise, return the line (including \n or \r\n at the end of the line).
|
||||
// If the return value is non-NULL, it's always the same as mem.
|
||||
// utf16: 0: UTF8 or 8 bit legacy, 1: UTF16-LE, 2: UTF16-BE
|
||||
static char *read_line(stream_t *s, char *mem, int max, int utf16)
|
||||
{
|
||||
if (max < 1)
|
||||
return NULL;
|
||||
int read = 0;
|
||||
while (1) {
|
||||
// Reserve 1 byte of ptr for terminating \0.
|
||||
int l = read_characters(s, &mem[read], max - read - 1, utf16);
|
||||
if (l < 0 || memchr(&mem[read], '\0', l)) {
|
||||
MP_WARN(s, "error reading line\n");
|
||||
return NULL;
|
||||
}
|
||||
read += l;
|
||||
if (l == 0 || (read > 0 && mem[read - 1] == '\n'))
|
||||
break;
|
||||
}
|
||||
mem[read] = '\0';
|
||||
if (!stream_read_peek(s, &(char){0}, 1) && read == 0) // legitimate EOF
|
||||
return NULL;
|
||||
return mem;
|
||||
}
|
||||
|
||||
static char *pl_get_line0(struct pl_parser *p)
|
||||
{
|
||||
char *res = stream_read_line(p->s, p->buffer, sizeof(p->buffer), p->utf16);
|
||||
char *res = read_line(p->s, p->buffer, sizeof(p->buffer), p->utf16);
|
||||
if (res) {
|
||||
int len = strlen(res);
|
||||
if (len > 0 && res[len - 1] == '\n')
|
||||
@@ -107,7 +178,9 @@ static int parse_m3u(struct pl_parser *p)
|
||||
// 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);
|
||||
char probe[PROBE_SIZE];
|
||||
int len = stream_read_peek(p->real_stream, probe, sizeof(probe));
|
||||
bstr data = {probe, len};
|
||||
if (ext && data.len > 10 && maybe_text(data)) {
|
||||
const char *exts[] = {"m3u", "m3u8", NULL};
|
||||
for (int n = 0; exts[n]; n++) {
|
||||
@@ -366,8 +439,9 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
p->real_stream = demuxer->stream;
|
||||
p->add_base = true;
|
||||
|
||||
bstr probe_buf = stream_peek(demuxer->stream, PROBE_SIZE);
|
||||
p->s = stream_memory_open(demuxer->global, probe_buf.start, probe_buf.len);
|
||||
char probe[PROBE_SIZE];
|
||||
int probe_len = stream_read_peek(p->real_stream, probe, sizeof(probe));
|
||||
p->s = stream_memory_open(demuxer->global, probe, probe_len);
|
||||
p->s->mime_type = demuxer->stream->mime_type;
|
||||
p->utf16 = stream_skip_bom(p->s);
|
||||
p->force = force;
|
||||
@@ -388,6 +462,8 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
bool ok = fmt->parse(p) >= 0 && !p->error;
|
||||
if (p->add_base)
|
||||
playlist_add_base_path(p->pl, mp_dirname(demuxer->filename));
|
||||
for (struct playlist_entry *e = p->pl->first; e; e = e->next)
|
||||
e->stream_flags = demuxer->stream_origin;
|
||||
demuxer->playlist = talloc_steal(demuxer, p->pl);
|
||||
demuxer->filetype = p->format ? p->format : fmt->name;
|
||||
demuxer->fully_read = true;
|
||||
|
||||
+4
-5
@@ -137,8 +137,8 @@ static int generic_open(struct demuxer *demuxer)
|
||||
struct stream *s = demuxer->stream;
|
||||
struct priv *p = demuxer->priv;
|
||||
|
||||
int64_t end = 0;
|
||||
if (stream_control(s, STREAM_CTRL_GET_SIZE, &end) == STREAM_OK)
|
||||
int64_t end = stream_get_size(s);
|
||||
if (end >= 0)
|
||||
demuxer->duration = (end / p->frame_size) / p->frame_rate;
|
||||
|
||||
return 0;
|
||||
@@ -299,8 +299,7 @@ static void raw_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
{
|
||||
struct priv *p = demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
int64_t end = 0;
|
||||
stream_control(s, STREAM_CTRL_GET_SIZE, &end);
|
||||
int64_t end = stream_get_size(s);
|
||||
int64_t frame_nr = seek_pts * p->frame_rate;
|
||||
frame_nr = frame_nr - (frame_nr % p->read_frames);
|
||||
int64_t pos = frame_nr * p->frame_size;
|
||||
@@ -308,7 +307,7 @@ static void raw_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
pos = end * seek_pts;
|
||||
if (pos < 0)
|
||||
pos = 0;
|
||||
if (end && pos > end)
|
||||
if (end > 0 && pos > end)
|
||||
pos = end;
|
||||
stream_seek(s, (pos / p->frame_size) * p->frame_size);
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ static void associate_streams(struct demuxer *demuxer,
|
||||
|
||||
// Matching by demuxer ID is supposedly useful and preferable for
|
||||
// ordered chapters.
|
||||
if (sh->demuxer_id == vs->sh->demuxer_id)
|
||||
if (sh->demuxer_id >= 0 && sh->demuxer_id == vs->sh->demuxer_id)
|
||||
other = vs;
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ static void reopen_lazy_segments(struct demuxer *demuxer,
|
||||
struct demuxer_params params = {
|
||||
.init_fragment = src->tl->init_fragment,
|
||||
.skip_lavf_probing = true,
|
||||
.stream_flags = demuxer->stream_origin,
|
||||
};
|
||||
src->current->d = demux_open_url(src->current->url, ¶ms,
|
||||
demuxer->cancel, demuxer->global);
|
||||
|
||||
+3
-2
@@ -180,14 +180,14 @@ int ebml_read_skip(struct mp_log *log, int64_t end, stream_t *s)
|
||||
if (len >= INT64_MAX - pos2 || (end > 0 && pos2 + len > end))
|
||||
goto invalid;
|
||||
|
||||
if (!stream_skip(s, len))
|
||||
if (!stream_seek_skip(s, pos2 + len))
|
||||
goto invalid;
|
||||
|
||||
return 0;
|
||||
|
||||
invalid:
|
||||
mp_err(log, "Invalid EBML length at position %"PRId64"\n", pos);
|
||||
stream_seek(s, pos);
|
||||
stream_seek_skip(s, pos);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ int ebml_resync_cluster(struct mp_log *log, stream_t *s)
|
||||
{
|
||||
int64_t pos = stream_tell(s);
|
||||
uint32_t last_4_bytes = 0;
|
||||
stream_read_peek(s, &(char){0}, 1);
|
||||
if (!s->eof) {
|
||||
mp_err(log, "Corrupt file detected. "
|
||||
"Trying to resync starting from position %"PRId64"...\n", pos);
|
||||
|
||||
@@ -17,6 +17,7 @@ struct timeline *timeline_load(struct mpv_global *global, struct mp_log *log,
|
||||
.cancel = demuxer->cancel,
|
||||
.demuxer = demuxer,
|
||||
.format = "unknown",
|
||||
.stream_origin = demuxer->stream_origin,
|
||||
};
|
||||
|
||||
demuxer->desc->load_timeline(tl);
|
||||
|
||||
@@ -36,6 +36,7 @@ struct timeline {
|
||||
struct mp_cancel *cancel;
|
||||
|
||||
bool is_network, is_streaming;
|
||||
int stream_origin;
|
||||
const char *format;
|
||||
|
||||
// main source, and all other sources (this usually only has special meaning
|
||||
|
||||
+12
-13
@@ -33,13 +33,6 @@ local -a tag_order
|
||||
zstyle -a ":completion:*:*:$service:*" tag-order tag_order ||
|
||||
zstyle ":completion:*:*:$service:*" tag-order '!urls'
|
||||
|
||||
# Use PCRE for regular expression matching if possible. This approximately
|
||||
# halves the execution time of generate_arguments compared to the default POSIX
|
||||
# regex, which translates to a more responsive first tab press. However, we
|
||||
# can't rely on PCRE being available, so we keep all our patterns
|
||||
# POSIX-compatible.
|
||||
setopt re_match_pcre &>/dev/null
|
||||
|
||||
typeset -ga _mpv_completion_arguments _mpv_completion_protocols
|
||||
|
||||
function generate_arguments {
|
||||
@@ -51,7 +44,7 @@ function generate_arguments {
|
||||
local list_options_line
|
||||
for list_options_line in "${(@f)$($words[1] --list-options)}"; do
|
||||
|
||||
[[ $list_options_line =~ '^\s+--(\S+)\s*(.*)' ]] || continue
|
||||
[[ $list_options_line =~ $'^[ \t]+--([^ \t]+)[ \t]*(.*)' ]] || continue
|
||||
|
||||
local name=$match[1] desc=$match[2]
|
||||
|
||||
@@ -79,10 +72,10 @@ function generate_arguments {
|
||||
|
||||
_mpv_completion_arguments+="$name"
|
||||
|
||||
elif [[ $desc =~ '^alias for --(\S+)' ]]; then
|
||||
elif [[ $desc =~ $'^alias for (--)?([^ \t]+)' ]]; then
|
||||
|
||||
# Save this for later; we might not have parsed the target option yet
|
||||
option_aliases+="$name $match[1]"
|
||||
option_aliases+="$name $match[2]"
|
||||
|
||||
else
|
||||
|
||||
@@ -154,7 +147,7 @@ function generate_protocols {
|
||||
_mpv_completion_protocols=()
|
||||
local list_protos_line
|
||||
for list_protos_line in "${(@f)$($words[1] --list-protocols)}"; do
|
||||
if [[ $list_protos_line =~ '^\s+(.*)' ]]; then
|
||||
if [[ $list_protos_line =~ $'^[ \t]+(.*)' ]]; then
|
||||
_mpv_completion_protocols+="$match[1]"
|
||||
fi
|
||||
done
|
||||
@@ -169,6 +162,12 @@ function generate_if_changed {
|
||||
zmodload -F zsh/stat b:zstat
|
||||
current_binary+=T$(zstat +mtime $current_binary)
|
||||
if [[ $_mpv_completion_binary[$1] != $current_binary ]]; then
|
||||
# Use PCRE for regular expression matching if possible. This approximately
|
||||
# halves the execution time of generate_arguments compared to the default
|
||||
# POSIX regex, which translates to a more responsive first tab press.
|
||||
# However, we can't rely on PCRE being available, so we keep all our
|
||||
# patterns POSIX-compatible.
|
||||
zmodload -s -F zsh/pcre C:pcre-match && setopt re_match_pcre
|
||||
generate_$1
|
||||
_mpv_completion_binary[$1]=$current_binary
|
||||
fi
|
||||
@@ -193,7 +192,7 @@ case $state in
|
||||
local pattern name_group=1 desc_group=2
|
||||
case $option_name in
|
||||
audio-device|vulkan-device)
|
||||
pattern='^\s+'\''([^'\'']*)'\''\s+\((.*)\)'
|
||||
pattern=$'^[ \t]+'\''([^'\'']*)'\'$'[ \t]+''\((.*)\)'
|
||||
;;
|
||||
profile)
|
||||
# The generic pattern would actually work in most cases for --profile,
|
||||
@@ -202,7 +201,7 @@ case $state in
|
||||
pattern=$'^\t([^\t]*)\t(.*)'
|
||||
;;
|
||||
*)
|
||||
pattern='^\s+(--'${option_name}'=)?(\S+)\s*[-:]?\s*(.*)'
|
||||
pattern=$'^[ \t]+(--'${option_name}$'=)?([^ \t]+)[ \t]*[-:]?[ \t]*(.*)'
|
||||
name_group=2 desc_group=3
|
||||
;;
|
||||
esac
|
||||
|
||||
+9
-2
@@ -26,9 +26,7 @@ input-terminal=no
|
||||
osc=no
|
||||
input-default-bindings=no
|
||||
input-vo-keyboard=no
|
||||
input-gamepad=no
|
||||
# OSX/Cocoa global input hooks
|
||||
input-appleremote=no
|
||||
input-media-keys=no
|
||||
|
||||
[encoding]
|
||||
@@ -62,6 +60,15 @@ demuxer-lavf-analyzeduration=0.1 # if it probes, reduce it
|
||||
video-sync=audio # DS currently requires reading ahead a frame
|
||||
interpolation=no # requires reference frames (more buffering)
|
||||
video-latency-hacks=yes # typically 1 or 2 video frame less latency
|
||||
stream-buffer-size=4k # minimal buffer size; normally not needed
|
||||
|
||||
[sw-fast]
|
||||
# For VOs which use software scalers, also affects screenshots and others.
|
||||
sws-scaler=bilinear
|
||||
sws-fast=yes
|
||||
sws-allow-zimg=yes
|
||||
zimg-scaler=bilinear
|
||||
zimg-dither=no
|
||||
|
||||
# Compatibility alias (deprecated)
|
||||
[opengl-hq]
|
||||
|
||||
+3
-16
@@ -31,6 +31,8 @@
|
||||
#MBTN_LEFT ignore # don't do anything
|
||||
#MBTN_LEFT_DBL cycle fullscreen # toggle fullscreen on/off
|
||||
#MBTN_RIGHT cycle pause # toggle pause on/off
|
||||
#MBTN_BACK playlist-prev
|
||||
#MBTN_FORWARD playlist-next
|
||||
|
||||
# Mouse wheels, touchpad or other input devices that have axes
|
||||
# if the input devices supports precise scrolling it will also scale the
|
||||
@@ -95,6 +97,7 @@
|
||||
#P show-progress
|
||||
#i script-binding stats/display-stats
|
||||
#I script-binding stats/display-stats-toggle
|
||||
#` script-binding console/enable
|
||||
#z add sub-delay -0.1 # subtract 100 ms delay from subs
|
||||
#Z add sub-delay +0.1 # add
|
||||
#x add sub-delay +0.1 # same as previous binding (discouraged)
|
||||
@@ -165,22 +168,6 @@
|
||||
#F8 show_text ${playlist} # show playlist
|
||||
#F9 show_text ${track-list} # show list of audio/sub streams
|
||||
|
||||
# Apple Remote section
|
||||
#AR_PLAY cycle pause
|
||||
#AR_PLAY_HOLD quit
|
||||
#AR_CENTER cycle pause
|
||||
#AR_CENTER_HOLD quit
|
||||
#AR_NEXT seek 10
|
||||
#AR_NEXT_HOLD seek 120
|
||||
#AR_PREV seek -10
|
||||
#AR_PREV_HOLD seek -120
|
||||
#AR_MENU show-progress
|
||||
#AR_MENU_HOLD cycle mute
|
||||
#AR_VUP add volume 2
|
||||
#AR_VUP_HOLD add chapter 1
|
||||
#AR_VDOWN add volume -2
|
||||
#AR_VDOWN_HOLD add chapter -1
|
||||
|
||||
#
|
||||
# Legacy bindings (may or may not be removed in the future)
|
||||
#
|
||||
|
||||
@@ -74,25 +74,6 @@ l cycle tv-channel -1
|
||||
n cycle tv-norm
|
||||
#b tv_step_chanlist
|
||||
|
||||
##
|
||||
## Apple Remote section
|
||||
##
|
||||
## To use OSD menu with Apple Remote, set key AR_MENU to any OSD menu command,
|
||||
## or just comment out the 'AR_MENU osd' line and uncomment the line after it.
|
||||
##
|
||||
|
||||
AR_PLAY cycle pause
|
||||
AR_PLAY_HOLD quit
|
||||
AR_NEXT seek 30
|
||||
AR_NEXT_HOLD seek 120
|
||||
AR_PREV seek -10
|
||||
AR_PREV_HOLD seek -120
|
||||
#AR_MENU menu up
|
||||
#AR_MENU menu cancel
|
||||
AR_MENU_HOLD cycle mute
|
||||
AR_VUP add volume 1
|
||||
AR_VDOWN add volume -1
|
||||
|
||||
#? add chapter -1 # skip to previous dvd chapter
|
||||
#? add chapter +1 # next
|
||||
|
||||
|
||||
@@ -309,7 +309,7 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log,
|
||||
goto fail;
|
||||
}
|
||||
|
||||
int out = mp_sws_find_best_out_format(src_fmt, fmts, num_fmts);
|
||||
int out = mp_sws_find_best_out_format(sws, src_fmt, fmts, num_fmts);
|
||||
if (!out) {
|
||||
mp_err(log, "can't find video conversion for %s\n",
|
||||
mp_imgfmt_to_name(src_fmt));
|
||||
|
||||
+23
-15
@@ -22,6 +22,7 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/buffer.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/rational.h>
|
||||
|
||||
#include "config.h"
|
||||
@@ -161,7 +162,7 @@ static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
if (p->decoder) {
|
||||
MP_VERBOSE(f, "Uninit decoder.\n");
|
||||
MP_DBG(f, "Uninit decoder.\n");
|
||||
talloc_free(p->decoder->f);
|
||||
p->decoder = NULL;
|
||||
}
|
||||
@@ -660,11 +661,13 @@ static void read_frame(struct priv *p)
|
||||
|
||||
if (p->decoded_coverart.type) {
|
||||
if (p->coverart_returned == 0) {
|
||||
mp_pin_in_write(pin, mp_frame_ref(p->decoded_coverart));
|
||||
frame = mp_frame_ref(p->decoded_coverart);
|
||||
p->coverart_returned = 1;
|
||||
goto output_frame;
|
||||
} else if (p->coverart_returned == 1) {
|
||||
mp_pin_in_write(pin, MP_EOF_FRAME);
|
||||
frame = MP_EOF_FRAME;
|
||||
p->coverart_returned = 2;
|
||||
goto output_frame;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -772,7 +775,7 @@ struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent,
|
||||
|
||||
struct priv *p = f->priv;
|
||||
struct mp_decoder_wrapper *w = &p->public;
|
||||
p->opt_cache = m_config_cache_alloc(p, f->global, GLOBAL_CONFIG);
|
||||
p->opt_cache = m_config_cache_alloc(p, f->global, &mp_opt_root);
|
||||
p->log = f->log;
|
||||
p->f = f;
|
||||
p->header = src;
|
||||
@@ -814,22 +817,23 @@ error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void lavc_process(struct mp_filter *f, bool *eof_flag,
|
||||
bool (*send)(struct mp_filter *f, struct demux_packet *pkt),
|
||||
bool (*receive)(struct mp_filter *f, struct mp_frame *res))
|
||||
void lavc_process(struct mp_filter *f, struct lavc_state *state,
|
||||
int (*send)(struct mp_filter *f, struct demux_packet *pkt),
|
||||
int (*receive)(struct mp_filter *f, struct mp_frame *res))
|
||||
{
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
struct mp_frame frame = {0};
|
||||
if (!receive(f, &frame)) {
|
||||
if (!*eof_flag)
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
*eof_flag = true;
|
||||
} else if (frame.type) {
|
||||
*eof_flag = false;
|
||||
int ret_recv = receive(f, &frame);
|
||||
if (frame.type) {
|
||||
state->eof_returned = false;
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
} else {
|
||||
} else if (ret_recv == AVERROR_EOF) {
|
||||
if (!state->eof_returned)
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
state->eof_returned = true;
|
||||
} else if (ret_recv == AVERROR(EAGAIN)) {
|
||||
// Need to feed a packet.
|
||||
frame = mp_pin_out_read(f->ppins[0]);
|
||||
struct demux_packet *pkt = NULL;
|
||||
@@ -843,7 +847,8 @@ void lavc_process(struct mp_filter *f, bool *eof_flag,
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!send(f, pkt)) {
|
||||
int ret_send = send(f, pkt);
|
||||
if (ret_send == AVERROR(EAGAIN)) {
|
||||
// Should never happen, but can happen with broken decoders.
|
||||
MP_WARN(f, "could not consume packet\n");
|
||||
mp_pin_out_unread(f->ppins[0], frame);
|
||||
@@ -852,5 +857,8 @@ void lavc_process(struct mp_filter *f, bool *eof_flag,
|
||||
}
|
||||
talloc_free(pkt);
|
||||
mp_filter_internal_mark_progress(f);
|
||||
} else {
|
||||
// Decoding error, or hwdec fallback recovery. Just try again.
|
||||
mp_filter_internal_mark_progress(f);
|
||||
}
|
||||
}
|
||||
@@ -109,11 +109,14 @@ extern const struct mp_decoder_fns vd_lavc;
|
||||
extern const struct mp_decoder_fns ad_lavc;
|
||||
extern const struct mp_decoder_fns ad_spdif;
|
||||
|
||||
// Convenience wrapper for lavc based decoders. eof_flag must be set to false
|
||||
// on init and resets.
|
||||
void lavc_process(struct mp_filter *f, bool *eof_flag,
|
||||
bool (*send)(struct mp_filter *f, struct demux_packet *pkt),
|
||||
bool (*receive)(struct mp_filter *f, struct mp_frame *res));
|
||||
// Convenience wrapper for lavc based decoders. Treat lavc_state as private;
|
||||
// init to all-0 on init and resets.
|
||||
struct lavc_state {
|
||||
bool eof_returned;
|
||||
};
|
||||
void lavc_process(struct mp_filter *f, struct lavc_state *state,
|
||||
int (*send)(struct mp_filter *f, struct demux_packet *pkt),
|
||||
int (*receive)(struct mp_filter *f, struct mp_frame *res));
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
@@ -117,6 +117,7 @@ struct lavfi_pad {
|
||||
AVFilterContext *buffer;
|
||||
AVRational timebase;
|
||||
bool buffer_is_eof; // received/sent EOF to the buffer
|
||||
bool got_eagain;
|
||||
|
||||
struct mp_tags *metadata;
|
||||
|
||||
@@ -140,6 +141,7 @@ static void free_graph(struct lavfi *c)
|
||||
pad->buffer = NULL;
|
||||
mp_frame_unref(&pad->in_fmt);
|
||||
pad->buffer_is_eof = false;
|
||||
pad->got_eagain = false;
|
||||
}
|
||||
c->initialized = false;
|
||||
c->draining_recover = false;
|
||||
@@ -643,6 +645,9 @@ static bool feed_input_pads(struct lavfi *c)
|
||||
MP_FATAL(c, "could not pass frame to filter\n");
|
||||
av_frame_free(&frame);
|
||||
|
||||
for (int i = 0; i < c->num_out_pads; i++)
|
||||
c->out_pads[i]->got_eagain = false;
|
||||
|
||||
progress = true;
|
||||
}
|
||||
|
||||
@@ -652,6 +657,26 @@ static bool feed_input_pads(struct lavfi *c)
|
||||
return progress;
|
||||
}
|
||||
|
||||
// Some filters get stuck and return EAGAIN forever if they did not get any
|
||||
// input (i.e. we send only EOF as input). "dynaudnorm" is known to be affected.
|
||||
static bool check_stuck_eagain_on_eof_bug(struct lavfi *c)
|
||||
{
|
||||
for (int n = 0; n < c->num_in_pads; n++) {
|
||||
if (!c->in_pads[n]->buffer_is_eof)
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int n = 0; n < c->num_out_pads; n++) {
|
||||
struct lavfi_pad *pad = c->out_pads[n];
|
||||
|
||||
if (!pad->buffer_is_eof && !pad->got_eagain)
|
||||
return false;
|
||||
}
|
||||
|
||||
MP_WARN(c, "Filter is stuck. This is a FFmpeg bug. Treating as EOF.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_output_pads(struct lavfi *c)
|
||||
{
|
||||
bool progress = false;
|
||||
@@ -669,6 +694,16 @@ static bool read_output_pads(struct lavfi *c)
|
||||
int r = AVERROR_EOF;
|
||||
if (!pad->buffer_is_eof)
|
||||
r = av_buffersink_get_frame_flags(pad->buffer, c->tmp_frame, 0);
|
||||
|
||||
pad->got_eagain = r == AVERROR(EAGAIN);
|
||||
if (pad->got_eagain) {
|
||||
if (check_stuck_eagain_on_eof_bug(c))
|
||||
r = AVERROR_EOF;
|
||||
} else {
|
||||
for (int i = 0; i < c->num_out_pads; i++)
|
||||
c->out_pads[i]->got_eagain = false;
|
||||
}
|
||||
|
||||
if (r >= 0) {
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
mp_tags_copy_from_av_dictionary(pad->metadata, c->tmp_frame->metadata);
|
||||
@@ -941,6 +976,12 @@ static bool is_usable(const AVFilter *filter, int media_type)
|
||||
is_single_media_only(filter->outputs, media_type);
|
||||
}
|
||||
|
||||
bool mp_lavfi_is_usable(const char *name, int media_type)
|
||||
{
|
||||
const AVFilter *f = avfilter_get_by_name(name);
|
||||
return f && is_usable(f, media_type);
|
||||
}
|
||||
|
||||
static void dump_list(struct mp_log *log, int media_type)
|
||||
{
|
||||
mp_info(log, "Available libavfilter filters:\n");
|
||||
|
||||
@@ -34,3 +34,6 @@ void print_lavfi_help_list(struct mp_log *log, int media_type);
|
||||
|
||||
// Print libavfilter help for the given filter
|
||||
void print_lavfi_help(struct mp_log *log, const char *name, int media_type);
|
||||
|
||||
// Return whether the given filter exists and has the required media_type in/outs.
|
||||
bool mp_lavfi_is_usable(const char *name, int media_type);
|
||||
@@ -504,33 +504,6 @@ double mp_output_get_measured_total_delay(struct mp_output_chain *c)
|
||||
return delay;
|
||||
}
|
||||
|
||||
static bool compare_filter(struct m_obj_settings *a, struct m_obj_settings *b)
|
||||
{
|
||||
if (a == b || !a || !b)
|
||||
return a == b;
|
||||
|
||||
if (!a->name || !b->name)
|
||||
return a->name == b->name;
|
||||
|
||||
if (!!a->label != !!b->label || (a->label && strcmp(a->label, b->label) != 0))
|
||||
return false;
|
||||
|
||||
if (a->enabled != b->enabled)
|
||||
return false;
|
||||
|
||||
if (!a->attribs || !a->attribs[0])
|
||||
return !b->attribs || !b->attribs[0];
|
||||
|
||||
for (int n = 0; a->attribs[n] || b->attribs[n]; n++) {
|
||||
if (!a->attribs[n] || !b->attribs[n])
|
||||
return false;
|
||||
if (strcmp(a->attribs[n], b->attribs[n]) != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_output_chain_update_filters(struct mp_output_chain *c,
|
||||
struct m_obj_settings *list)
|
||||
{
|
||||
@@ -551,7 +524,8 @@ bool mp_output_chain_update_filters(struct mp_output_chain *c,
|
||||
struct mp_user_filter *u = NULL;
|
||||
|
||||
for (int i = 0; i < p->num_user_filters; i++) {
|
||||
if (!used[i] && compare_filter(entry, p->user_filters[i]->args)) {
|
||||
if (!used[i] && m_obj_settings_equal(entry, p->user_filters[i]->args))
|
||||
{
|
||||
u = p->user_filters[i];
|
||||
used[i] = true;
|
||||
break;
|
||||
|
||||
+4
-9
@@ -40,17 +40,14 @@
|
||||
#include "filter.h"
|
||||
#include "filter_internal.h"
|
||||
|
||||
int mp_sws_find_best_out_format(int in_format, int *out_formats,
|
||||
int num_out_formats)
|
||||
int mp_sws_find_best_out_format(struct mp_sws_filter *sws, int in_format,
|
||||
int *out_formats, int num_out_formats)
|
||||
{
|
||||
if (sws_isSupportedInput(imgfmt2pixfmt(in_format)) < 1)
|
||||
return 0;
|
||||
|
||||
int best = 0;
|
||||
for (int n = 0; n < num_out_formats; n++) {
|
||||
int out_format = out_formats[n];
|
||||
|
||||
if (sws_isSupportedOutput(imgfmt2pixfmt(out_format)) < 1)
|
||||
if (!mp_sws_supports_formats(sws->sws, out_format, in_format))
|
||||
continue;
|
||||
|
||||
if (best) {
|
||||
@@ -151,10 +148,8 @@ struct mp_sws_filter *mp_sws_filter_create(struct mp_filter *parent)
|
||||
s->f = f;
|
||||
s->sws = mp_sws_alloc(s);
|
||||
s->sws->log = f->log;
|
||||
mp_sws_enable_cmdline_opts(s->sws, f->global);
|
||||
s->pool = mp_image_pool_new(s);
|
||||
|
||||
s->sws->allow_zimg = true;
|
||||
mp_sws_set_from_cmdline(s->sws, f->global);
|
||||
|
||||
return s;
|
||||
}
|
||||
+2
-1
@@ -23,7 +23,8 @@ struct mp_sws_filter *mp_sws_filter_create(struct mp_filter *parent);
|
||||
// formats. This tries to set the output format to the one that will result in
|
||||
// the least loss. Returns a format from out_formats[], or 0 if no format could
|
||||
// be chosen (or it's not supported by libswscale).
|
||||
int mp_sws_find_best_out_format(int in_format, int *out_formats,
|
||||
int mp_sws_find_best_out_format(struct mp_sws_filter *sws,
|
||||
int in_format, int *out_formats,
|
||||
int num_out_formats);
|
||||
|
||||
// Whether the given format is supported as input format.
|
||||
|
||||
@@ -20,6 +20,14 @@ static bool get_desc_from(const struct mp_user_filter_entry **list, int num,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool check_unknown_entry(const char *name, int media_type)
|
||||
{
|
||||
// Generic lavfi bridge: skip the lavfi- prefix, if present.
|
||||
if (strncmp(name, "lavfi-", 6) == 0)
|
||||
name += 6;
|
||||
return mp_lavfi_is_usable(name, media_type);
|
||||
}
|
||||
|
||||
// --af option
|
||||
|
||||
const struct mp_user_filter_entry *af_list[] = {
|
||||
@@ -48,11 +56,17 @@ static void print_af_lavfi_help(struct mp_log *log, const char *name)
|
||||
print_lavfi_help(log, name, AVMEDIA_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
static bool check_af_lavfi(const char *name)
|
||||
{
|
||||
return check_unknown_entry(name, AVMEDIA_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
const struct m_obj_list af_obj_list = {
|
||||
.get_desc = get_af_desc,
|
||||
.description = "audio filters",
|
||||
.allow_disable_entries = true,
|
||||
.allow_unknown_entries = true,
|
||||
.check_unknown_entry = check_af_lavfi,
|
||||
.print_help_list = print_af_help_list,
|
||||
.print_unknown_entry_help = print_af_lavfi_help,
|
||||
};
|
||||
@@ -79,6 +93,9 @@ const struct mp_user_filter_entry *vf_list[] = {
|
||||
#if HAVE_D3D_HWACCEL
|
||||
&vf_d3d11vpp,
|
||||
#endif
|
||||
#if HAVE_EGL_HELPERS && HAVE_GL && HAVE_EGL
|
||||
&vf_gpu,
|
||||
#endif
|
||||
};
|
||||
|
||||
static bool get_vf_desc(struct m_obj_desc *dst, int index)
|
||||
@@ -96,11 +113,17 @@ static void print_vf_lavfi_help(struct mp_log *log, const char *name)
|
||||
print_lavfi_help(log, name, AVMEDIA_TYPE_VIDEO);
|
||||
}
|
||||
|
||||
static bool check_vf_lavfi(const char *name)
|
||||
{
|
||||
return check_unknown_entry(name, AVMEDIA_TYPE_VIDEO);
|
||||
}
|
||||
|
||||
const struct m_obj_list vf_obj_list = {
|
||||
.get_desc = get_vf_desc,
|
||||
.description = "video filters",
|
||||
.allow_disable_entries = true,
|
||||
.allow_unknown_entries = true,
|
||||
.check_unknown_entry = check_vf_lavfi,
|
||||
.print_help_list = print_vf_help_list,
|
||||
.print_unknown_entry_help = print_vf_lavfi_help,
|
||||
};
|
||||
|
||||
@@ -34,3 +34,4 @@ extern const struct mp_user_filter_entry vf_vdpaupp;
|
||||
extern const struct mp_user_filter_entry vf_vavpp;
|
||||
extern const struct mp_user_filter_entry vf_d3d11vpp;
|
||||
extern const struct mp_user_filter_entry vf_fingerprint;
|
||||
extern const struct mp_user_filter_entry vf_gpu;
|
||||
+15
-3
@@ -413,7 +413,7 @@ static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp,
|
||||
}
|
||||
|
||||
bstr orig = {ctx->start.start, ctx->str.start - ctx->start.start};
|
||||
cmd->original = bstrdup(cmd, bstr_strip(orig));
|
||||
cmd->original = bstrto0(cmd, bstr_strip(orig));
|
||||
|
||||
*str = ctx->str;
|
||||
return cmd;
|
||||
@@ -449,7 +449,6 @@ mp_cmd_t *mp_input_parse_cmd_str(struct mp_log *log, bstr str, const char *loc)
|
||||
*list = (struct mp_cmd) {
|
||||
.name = (char *)mp_cmd_list.name,
|
||||
.def = &mp_cmd_list,
|
||||
.original = bstrdup(list, original),
|
||||
};
|
||||
talloc_steal(list, cmd);
|
||||
struct mp_cmd_arg arg = {0};
|
||||
@@ -469,6 +468,16 @@ mp_cmd_t *mp_input_parse_cmd_str(struct mp_log *log, bstr str, const char *loc)
|
||||
p_prev = &sub->queue_next;
|
||||
}
|
||||
|
||||
cmd->original = bstrto0(cmd, bstr_strip(
|
||||
bstr_splice(original, 0, str.start - original.start)));
|
||||
|
||||
str = bstr_strip(str);
|
||||
if (bstr_eatstart0(&str, "#") && !bstr_startswith0(str, "#")) {
|
||||
str = bstr_strip(str);
|
||||
if (str.len)
|
||||
cmd->desc = bstrto0(cmd, str);
|
||||
}
|
||||
|
||||
done:
|
||||
talloc_free(tmp);
|
||||
return cmd;
|
||||
@@ -509,8 +518,11 @@ mp_cmd_t *mp_cmd_clone(mp_cmd_t *cmd)
|
||||
ret->args[i].type = cmd->args[i].type;
|
||||
m_option_copy(ret->args[i].type, &ret->args[i].v, &cmd->args[i].v);
|
||||
}
|
||||
ret->original = bstrdup(ret, cmd->original);
|
||||
ret->original = talloc_strdup(ret, cmd->original);
|
||||
ret->desc = talloc_strdup(ret, cmd->desc);
|
||||
ret->sender = NULL;
|
||||
ret->key_name = talloc_strdup(ret, ret->key_name);
|
||||
ret->key_text = talloc_strdup(ret, ret->key_text);
|
||||
|
||||
if (cmd->def == &mp_cmd_list) {
|
||||
struct mp_cmd *prev = NULL;
|
||||
|
||||
+4
-1
@@ -40,6 +40,7 @@ struct mp_cmd_def {
|
||||
bool vararg; // last argument can be given 0 to multiple times
|
||||
bool scalable;
|
||||
bool is_ignore;
|
||||
bool is_noisy; // reduce log level
|
||||
bool default_async; // default to MP_ASYNC flag if none set by user
|
||||
// If you set this, handler() must ensure mp_cmd_ctx_complete() is called
|
||||
// at some point (can be after handler() returns). If you don't set it, the
|
||||
@@ -99,7 +100,8 @@ typedef struct mp_cmd {
|
||||
struct mp_cmd_arg *args;
|
||||
int nargs;
|
||||
int flags; // mp_cmd_flags bitfield
|
||||
bstr original;
|
||||
char *original;
|
||||
char *desc; // (usually NULL since stripped away later)
|
||||
char *input_section;
|
||||
bool is_up_down : 1;
|
||||
bool is_up : 1;
|
||||
@@ -114,6 +116,7 @@ typedef struct mp_cmd {
|
||||
const struct mp_cmd_def *def;
|
||||
char *sender; // name of the client API user which sent this
|
||||
char *key_name; // string representation of the key binding
|
||||
char *key_text; // text if key is a text key
|
||||
} mp_cmd_t;
|
||||
|
||||
extern const struct mp_cmd_def mp_cmds[];
|
||||
|
||||
+111
-59
@@ -31,9 +31,6 @@
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "osdep/io.h"
|
||||
#include "misc/rendezvous.h"
|
||||
|
||||
@@ -49,6 +46,7 @@
|
||||
#include "mpv_talloc.h"
|
||||
#include "options/options.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "misc/node.h"
|
||||
#include "stream/stream.h"
|
||||
#include "common/common.h"
|
||||
|
||||
@@ -66,6 +64,7 @@ struct cmd_bind {
|
||||
int num_keys;
|
||||
char *cmd;
|
||||
char *location; // filename/line number of definition
|
||||
char *desc; // human readable description
|
||||
bool is_builtin;
|
||||
struct cmd_bind_section *owner;
|
||||
};
|
||||
@@ -77,7 +76,6 @@ struct cmd_bind_section {
|
||||
char *section;
|
||||
struct mp_rect mouse_area; // set at runtime, if at all
|
||||
bool mouse_area_set; // mouse_area is valid and should be tested
|
||||
struct cmd_bind_section *next;
|
||||
};
|
||||
|
||||
#define MP_MAX_SOURCES 10
|
||||
@@ -140,7 +138,8 @@ struct input_ctx {
|
||||
double last_wheel_time; // mp_time_sec() of the last wheel event
|
||||
|
||||
// List of command binding sections
|
||||
struct cmd_bind_section *cmd_bind_sections;
|
||||
struct cmd_bind_section **sections;
|
||||
int num_sections;
|
||||
|
||||
// List currently active command sections
|
||||
struct active_section active_sections[MAX_ACTIVE_SECTIONS];
|
||||
@@ -184,7 +183,7 @@ struct input_opts {
|
||||
|
||||
const struct m_sub_options input_config = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_STRING("input-conf", config_file, M_OPT_FIXED | M_OPT_FILE),
|
||||
OPT_STRING("input-conf", config_file, M_OPT_FILE),
|
||||
OPT_INT("input-ar-delay", ar_delay, 0),
|
||||
OPT_INT("input-ar-rate", ar_rate, 0),
|
||||
OPT_PRINT("input-keylist", mp_print_key_list),
|
||||
@@ -197,14 +196,14 @@ const struct m_sub_options input_config = {
|
||||
OPT_FLAG("input-cursor", enable_mouse_movements, 0),
|
||||
OPT_FLAG("input-vo-keyboard", vo_key_input, 0),
|
||||
OPT_FLAG("input-media-keys", use_media_keys, 0),
|
||||
#if HAVE_COCOA
|
||||
OPT_FLAG("input-appleremote", use_appleremote, 0),
|
||||
#endif
|
||||
#if HAVE_SDL2_GAMEPAD
|
||||
OPT_FLAG("input-gamepad", use_gamepad, 0),
|
||||
#endif
|
||||
OPT_FLAG("window-dragging", allow_win_drag, 0),
|
||||
OPT_REPLACED("input-x11-keyboard", "input-vo-keyboard"),
|
||||
#if HAVE_COCOA
|
||||
OPT_REMOVED("input-appleremote", "replaced by MediaPlayer support"),
|
||||
#endif
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct input_opts),
|
||||
@@ -216,12 +215,6 @@ const struct m_sub_options input_config = {
|
||||
.use_alt_gr = 1,
|
||||
.enable_mouse_movements = 1,
|
||||
.use_media_keys = 1,
|
||||
#if HAVE_COCOA
|
||||
.use_appleremote = 1,
|
||||
#endif
|
||||
#if HAVE_SDL2_GAMEPAD
|
||||
.use_gamepad = 1,
|
||||
#endif
|
||||
.default_bindings = 1,
|
||||
.vo_key_input = 1,
|
||||
.allow_win_drag = 1,
|
||||
@@ -288,8 +281,8 @@ static void append_bind_info(struct input_ctx *ictx, char **pmsg,
|
||||
char *msg = *pmsg;
|
||||
struct mp_cmd *cmd = mp_input_parse_cmd(ictx, bstr0(bind->cmd),
|
||||
bind->location);
|
||||
bstr stripped = cmd ? cmd->original : bstr0(bind->cmd);
|
||||
msg = talloc_asprintf_append(msg, " '%.*s'", BSTR_P(stripped));
|
||||
char *stripped = cmd ? cmd->original : bind->cmd;
|
||||
msg = talloc_asprintf_append(msg, " '%s'", stripped);
|
||||
if (!cmd)
|
||||
msg = talloc_asprintf_append(msg, " (invalid)");
|
||||
if (strcmp(bind->owner->section, "default") != 0)
|
||||
@@ -318,9 +311,9 @@ static mp_cmd_t *handle_test(struct input_ctx *ictx, int code)
|
||||
talloc_free(key_buf);
|
||||
|
||||
int count = 0;
|
||||
for (struct cmd_bind_section *bs = ictx->cmd_bind_sections;
|
||||
bs; bs = bs->next)
|
||||
{
|
||||
for (int n = 0; n < ictx->num_sections; n++) {
|
||||
struct cmd_bind_section *bs = ictx->sections[n];
|
||||
|
||||
for (int i = 0; i < bs->num_binds; i++) {
|
||||
if (bs->binds[i].num_keys && bs->binds[i].keys[0] == code) {
|
||||
count++;
|
||||
@@ -342,32 +335,32 @@ static mp_cmd_t *handle_test(struct input_ctx *ictx, int code)
|
||||
return res;
|
||||
}
|
||||
|
||||
static struct cmd_bind_section *find_section(struct input_ctx *ictx,
|
||||
bstr section)
|
||||
{
|
||||
for (int n = 0; n < ictx->num_sections; n++) {
|
||||
struct cmd_bind_section *bs = ictx->sections[n];
|
||||
if (bstr_equals0(section, bs->section))
|
||||
return bs;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct cmd_bind_section *get_bind_section(struct input_ctx *ictx,
|
||||
bstr section)
|
||||
{
|
||||
struct cmd_bind_section *bind_section = ictx->cmd_bind_sections;
|
||||
|
||||
if (section.len == 0)
|
||||
section = bstr0("default");
|
||||
while (bind_section) {
|
||||
if (bstrcmp0(section, bind_section->section) == 0)
|
||||
return bind_section;
|
||||
if (bind_section->next == NULL)
|
||||
break;
|
||||
bind_section = bind_section->next;
|
||||
}
|
||||
if (bind_section) {
|
||||
bind_section->next = talloc_ptrtype(ictx, bind_section->next);
|
||||
bind_section = bind_section->next;
|
||||
} else {
|
||||
ictx->cmd_bind_sections = talloc_ptrtype(ictx, ictx->cmd_bind_sections);
|
||||
bind_section = ictx->cmd_bind_sections;
|
||||
}
|
||||
struct cmd_bind_section *bind_section = find_section(ictx, section);
|
||||
if (bind_section)
|
||||
return bind_section;
|
||||
bind_section = talloc_ptrtype(ictx, bind_section);
|
||||
*bind_section = (struct cmd_bind_section) {
|
||||
.section = bstrdup0(bind_section, section),
|
||||
.mouse_area = {INT_MIN, INT_MIN, INT_MAX, INT_MAX},
|
||||
.mouse_area_set = true,
|
||||
};
|
||||
MP_TARRAY_APPEND(ictx, ictx->sections, ictx->num_sections, bind_section);
|
||||
return bind_section;
|
||||
}
|
||||
|
||||
@@ -462,7 +455,12 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
|
||||
if (ictx->opts->test)
|
||||
return handle_test(ictx, code);
|
||||
|
||||
struct cmd_bind *cmd = find_any_bind_for_key(ictx, force_section, code);
|
||||
struct cmd_bind *cmd = NULL;
|
||||
|
||||
if (MP_KEY_IS_UNICODE(code))
|
||||
cmd = find_any_bind_for_key(ictx, force_section, MP_KEY_ANY_UNICODE);
|
||||
if (!cmd)
|
||||
cmd = find_any_bind_for_key(ictx, force_section, code);
|
||||
if (!cmd)
|
||||
cmd = find_any_bind_for_key(ictx, force_section, MP_KEY_UNMAPPED);
|
||||
if (!cmd) {
|
||||
@@ -482,6 +480,11 @@ static mp_cmd_t *get_cmd_from_keys(struct input_ctx *ictx, char *force_section,
|
||||
ret->key_name = talloc_steal(ret, mp_input_get_key_combo_name(&code, 1));
|
||||
MP_TRACE(ictx, "key '%s' -> '%s' in '%s'\n",
|
||||
ret->key_name, cmd->cmd, ret->input_section);
|
||||
if (MP_KEY_IS_UNICODE(code)) {
|
||||
bstr text = {0};
|
||||
mp_append_utf8_bstr(ret, &text, code);
|
||||
ret->key_text = text.start;
|
||||
}
|
||||
ret->is_mouse_button = code & MP_KEY_EMIT_ON_UP;
|
||||
} else {
|
||||
char *key_buf = mp_input_get_key_combo_name(&code, 1);
|
||||
@@ -559,7 +562,7 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale,
|
||||
int state = code & (MP_KEY_STATE_DOWN | MP_KEY_STATE_UP);
|
||||
code = code & ~(unsigned)state;
|
||||
|
||||
if (mp_msg_test(ictx->log, MSGL_DEBUG)) {
|
||||
if (mp_msg_test(ictx->log, MSGL_TRACE)) {
|
||||
char *key = mp_input_get_key_name(code);
|
||||
MP_TRACE(ictx, "key code=%#x '%s'%s%s\n",
|
||||
code, key, (state & MP_KEY_STATE_DOWN) ? " down" : "",
|
||||
@@ -626,7 +629,7 @@ static void interpret_key(struct input_ctx *ictx, int code, double scale,
|
||||
cmd->scale = 1;
|
||||
cmd->scale_units = 1;
|
||||
// Avoid spamming the player with too many commands
|
||||
scale_units = FFMIN(scale_units, 20);
|
||||
scale_units = MPMIN(scale_units, 20);
|
||||
for (int i = 0; i < scale_units - 1; i++)
|
||||
mp_input_queue_cmd(ictx, mp_cmd_clone(cmd));
|
||||
if (scale_units)
|
||||
@@ -880,8 +883,8 @@ static void adjust_max_wait_time(struct input_ctx *ictx, double *time)
|
||||
{
|
||||
struct input_opts *opts = ictx->opts;
|
||||
if (ictx->last_key_down && opts->ar_rate > 0 && ictx->ar_state >= 0) {
|
||||
*time = FFMIN(*time, 1.0 / opts->ar_rate);
|
||||
*time = FFMIN(*time, opts->ar_delay / 1000.0);
|
||||
*time = MPMIN(*time, 1.0 / opts->ar_rate);
|
||||
*time = MPMIN(*time, opts->ar_delay / 1000.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1076,6 +1079,7 @@ static void bind_dealloc(struct cmd_bind *bind)
|
||||
{
|
||||
talloc_free(bind->cmd);
|
||||
talloc_free(bind->location);
|
||||
talloc_free(bind->desc);
|
||||
}
|
||||
|
||||
// builtin: if true, remove all builtin binds, else remove all user binds
|
||||
@@ -1119,14 +1123,13 @@ void mp_input_define_section(struct input_ctx *ictx, char *name, char *location,
|
||||
void mp_input_remove_sections_by_owner(struct input_ctx *ictx, char *owner)
|
||||
{
|
||||
input_lock(ictx);
|
||||
struct cmd_bind_section *bs = ictx->cmd_bind_sections;
|
||||
while (bs) {
|
||||
for (int n = 0; n < ictx->num_sections; n++) {
|
||||
struct cmd_bind_section *bs = ictx->sections[n];
|
||||
if (bs->owner && owner && strcmp(bs->owner, owner) == 0) {
|
||||
mp_input_disable_section(ictx, bs->section);
|
||||
remove_binds(bs, false);
|
||||
remove_binds(bs, true);
|
||||
}
|
||||
bs = bs->next;
|
||||
}
|
||||
input_unlock(ictx);
|
||||
}
|
||||
@@ -1144,7 +1147,7 @@ static bool bind_matches_key(struct cmd_bind *bind, int num_keys, const int *key
|
||||
|
||||
static void bind_keys(struct input_ctx *ictx, bool builtin, bstr section,
|
||||
const int *keys, int num_keys, bstr command,
|
||||
const char *loc)
|
||||
const char *loc, const char *desc)
|
||||
{
|
||||
struct cmd_bind_section *bs = get_bind_section(ictx, section);
|
||||
struct cmd_bind *bind = NULL;
|
||||
@@ -1170,6 +1173,7 @@ static void bind_keys(struct input_ctx *ictx, bool builtin, bstr section,
|
||||
*bind = (struct cmd_bind) {
|
||||
.cmd = bstrdup0(bs->binds, command),
|
||||
.location = talloc_strdup(bs->binds, loc),
|
||||
.desc = talloc_strdup(bs->binds, desc),
|
||||
.owner = bs,
|
||||
.is_builtin = builtin,
|
||||
.num_keys = num_keys,
|
||||
@@ -1245,11 +1249,18 @@ static int parse_config(struct input_ctx *ictx, bool builtin, bstr data,
|
||||
}
|
||||
}
|
||||
|
||||
bind_keys(ictx, builtin, section, keys, num_keys, command, cur_loc);
|
||||
// Print warnings if invalid commands are encountered.
|
||||
struct mp_cmd *cmd = mp_input_parse_cmd(ictx, command, cur_loc);
|
||||
const char *desc = NULL;
|
||||
if (cmd) {
|
||||
desc = cmd->desc;
|
||||
command = bstr0(cmd->original);
|
||||
}
|
||||
|
||||
bind_keys(ictx, builtin, section, keys, num_keys, command, cur_loc, desc);
|
||||
n_binds++;
|
||||
|
||||
// Print warnings if invalid commands are encountered.
|
||||
talloc_free(mp_input_parse_cmd(ictx, command, cur_loc));
|
||||
talloc_free(cmd);
|
||||
}
|
||||
|
||||
talloc_free(cur_loc);
|
||||
@@ -1265,7 +1276,7 @@ static int parse_config_file(struct input_ctx *ictx, char *file, bool warn)
|
||||
|
||||
file = mp_get_user_path(tmp, ictx->global, file);
|
||||
|
||||
s = stream_open(file, ictx->global);
|
||||
s = stream_create(file, STREAM_ORIGIN_DIRECT | STREAM_READ, NULL, ictx->global);
|
||||
if (!s) {
|
||||
MP_ERR(ictx, "Can't open input config file %s.\n", file);
|
||||
goto done;
|
||||
@@ -1321,15 +1332,6 @@ static void reload_opts(struct input_ctx *ictx, bool shutdown)
|
||||
#if HAVE_COCOA
|
||||
struct input_opts *opts = ictx->opts;
|
||||
|
||||
if (ictx->using_ar != (opts->use_appleremote && !shutdown)) {
|
||||
ictx->using_ar = !ictx->using_ar;
|
||||
if (ictx->using_ar) {
|
||||
cocoa_init_apple_remote();
|
||||
} else {
|
||||
cocoa_uninit_apple_remote();
|
||||
}
|
||||
}
|
||||
|
||||
if (ictx->using_cocoa_media_keys != (opts->use_media_keys && !shutdown)) {
|
||||
ictx->using_cocoa_media_keys = !ictx->using_cocoa_media_keys;
|
||||
if (ictx->using_cocoa_media_keys) {
|
||||
@@ -1447,7 +1449,7 @@ void mp_input_run_cmd(struct input_ctx *ictx, const char **cmd)
|
||||
|
||||
void mp_input_bind_key(struct input_ctx *ictx, int key, bstr command)
|
||||
{
|
||||
struct cmd_bind_section *bs = ictx->cmd_bind_sections;
|
||||
struct cmd_bind_section *bs = get_bind_section(ictx, (bstr){0});
|
||||
struct cmd_bind *bind = NULL;
|
||||
|
||||
for (int n = 0; n < bs->num_binds; n++) {
|
||||
@@ -1483,6 +1485,56 @@ void mp_input_bind_key(struct input_ctx *ictx, int key, bstr command)
|
||||
}
|
||||
}
|
||||
|
||||
struct mpv_node mp_input_get_bindings(struct input_ctx *ictx)
|
||||
{
|
||||
input_lock(ictx);
|
||||
struct mpv_node root;
|
||||
node_init(&root, MPV_FORMAT_NODE_ARRAY, NULL);
|
||||
|
||||
for (int x = 0; x < ictx->num_sections; x++) {
|
||||
struct cmd_bind_section *s = ictx->sections[x];
|
||||
int priority = -1;
|
||||
|
||||
for (int i = 0; i < ictx->num_active_sections; i++) {
|
||||
struct active_section *as = &ictx->active_sections[i];
|
||||
if (strcmp(as->name, s->section) == 0) {
|
||||
priority = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int n = 0; n < s->num_binds; n++) {
|
||||
struct cmd_bind *b = &s->binds[n];
|
||||
struct mpv_node *entry = node_array_add(&root, MPV_FORMAT_NODE_MAP);
|
||||
|
||||
int b_priority = priority;
|
||||
if (b->is_builtin && !ictx->opts->default_bindings)
|
||||
b_priority = -1;
|
||||
|
||||
// Try to fixup the weird logic so consumer of this bindings list
|
||||
// does not get too confused.
|
||||
if (b_priority >= 0 && !b->is_builtin)
|
||||
b_priority += ictx->num_active_sections;
|
||||
|
||||
node_map_add_string(entry, "section", s->section);
|
||||
if (s->owner)
|
||||
node_map_add_string(entry, "owner", s->owner);
|
||||
node_map_add_string(entry, "cmd", b->cmd);
|
||||
node_map_add_flag(entry, "is_weak", b->is_builtin);
|
||||
node_map_add_int64(entry, "priority", b_priority);
|
||||
if (b->desc)
|
||||
node_map_add_string(entry, "comment", b->desc);
|
||||
|
||||
char *key = mp_input_get_key_combo_name(b->keys, b->num_keys);
|
||||
node_map_add_string(entry, "key", key);
|
||||
talloc_free(key);
|
||||
}
|
||||
}
|
||||
|
||||
input_unlock(ictx);
|
||||
return root;
|
||||
}
|
||||
|
||||
struct mp_input_src_internal {
|
||||
pthread_t thread;
|
||||
bool thread_running;
|
||||
|
||||
+2
-3
@@ -23,9 +23,6 @@
|
||||
|
||||
#include "cmd.h"
|
||||
|
||||
// For mp_input_put_key(): release all keys that are down.
|
||||
#define MP_INPUT_RELEASE_ALL -1
|
||||
|
||||
struct input_ctx;
|
||||
struct mp_log;
|
||||
|
||||
@@ -208,6 +205,8 @@ void mp_input_bind_key(struct input_ctx *ictx, int key, bstr command);
|
||||
|
||||
void mp_input_set_repeat_info(struct input_ctx *ictx, int rate, int delay);
|
||||
|
||||
struct mpv_node mp_input_get_bindings(struct input_ctx *ictx);
|
||||
|
||||
void mp_input_pipe_add(struct input_ctx *ictx, const char *filename);
|
||||
|
||||
void mp_input_sdl_gamepad_add(struct input_ctx *ictx);
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ done:
|
||||
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||
struct mpv_global *global)
|
||||
{
|
||||
struct MPOpts *opts = mp_get_config_group(NULL, global, GLOBAL_CONFIG);
|
||||
struct MPOpts *opts = mp_get_config_group(NULL, global, &mp_opt_root);
|
||||
|
||||
struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg);
|
||||
*arg = (struct mp_ipc_ctx){
|
||||
|
||||
+1
-1
@@ -450,7 +450,7 @@ done:
|
||||
struct mp_ipc_ctx *mp_init_ipc(struct mp_client_api *client_api,
|
||||
struct mpv_global *global)
|
||||
{
|
||||
struct MPOpts *opts = mp_get_config_group(NULL, global, GLOBAL_CONFIG);
|
||||
struct MPOpts *opts = mp_get_config_group(NULL, global, &mp_opt_root);
|
||||
|
||||
struct mp_ipc_ctx *arg = talloc_ptrtype(NULL, arg);
|
||||
*arg = (struct mp_ipc_ctx){
|
||||
|
||||
+2
-17
@@ -102,21 +102,6 @@ static const struct key_name key_names[] = {
|
||||
{ MP_MBTN_MID_DBL, "MBTN_MID_DBL" },
|
||||
{ MP_MBTN_RIGHT_DBL, "MBTN_RIGHT_DBL" },
|
||||
|
||||
{ MP_AR_PLAY, "AR_PLAY" },
|
||||
{ MP_AR_PLAY_HOLD, "AR_PLAY_HOLD" },
|
||||
{ MP_AR_CENTER, "AR_CENTER" },
|
||||
{ MP_AR_CENTER_HOLD, "AR_CENTER_HOLD" },
|
||||
{ MP_AR_NEXT, "AR_NEXT" },
|
||||
{ MP_AR_NEXT_HOLD, "AR_NEXT_HOLD" },
|
||||
{ MP_AR_PREV, "AR_PREV" },
|
||||
{ MP_AR_PREV_HOLD, "AR_PREV_HOLD" },
|
||||
{ MP_AR_MENU, "AR_MENU" },
|
||||
{ MP_AR_MENU_HOLD, "AR_MENU_HOLD" },
|
||||
{ MP_AR_VUP, "AR_VUP" },
|
||||
{ MP_AR_VUP_HOLD, "AR_VUP_HOLD" },
|
||||
{ MP_AR_VDOWN, "AR_VDOWN" },
|
||||
{ MP_AR_VDOWN_HOLD, "AR_VDOWN_HOLD" },
|
||||
|
||||
{ MP_KEY_GAMEPAD_ACTION_DOWN, "GAMEPAD_ACTION_DOWN" },
|
||||
{ MP_KEY_GAMEPAD_ACTION_RIGHT, "GAMEPAD_ACTION_RIGHT" },
|
||||
{ MP_KEY_GAMEPAD_ACTION_LEFT, "GAMEPAD_ACTION_LEFT" },
|
||||
@@ -208,6 +193,7 @@ static const struct key_name key_names[] = {
|
||||
{ MP_KEY_MOUSE_ENTER, "MOUSE_ENTER" },
|
||||
|
||||
{ MP_KEY_UNMAPPED, "UNMAPPED" },
|
||||
{ MP_KEY_ANY_UNICODE, "ANY_UNICODE" },
|
||||
|
||||
{ 0, NULL }
|
||||
};
|
||||
@@ -271,8 +257,7 @@ static void mp_input_append_key_name(bstr *buf, int key)
|
||||
}
|
||||
}
|
||||
|
||||
// printable, and valid unicode range
|
||||
if (key >= 32 && key <= 0x10FFFF) {
|
||||
if (MP_KEY_IS_UNICODE(key)) {
|
||||
mp_append_utf8_bstr(NULL, buf, key);
|
||||
return;
|
||||
}
|
||||
|
||||
+8
-18
@@ -22,6 +22,10 @@
|
||||
// Special keys come after this.
|
||||
#define MP_KEY_BASE (1<<21)
|
||||
|
||||
// printable, and valid unicode range (we don't care too much about whether
|
||||
// certain sub-ranges are reserved and disallowed, like surrogate pairs)
|
||||
#define MP_KEY_IS_UNICODE(key) ((key) >= 32 && (key) <= 0x10FFFF)
|
||||
|
||||
#define MP_KEY_ENTER 13
|
||||
#define MP_KEY_TAB 9
|
||||
|
||||
@@ -137,23 +141,6 @@
|
||||
|
||||
#define MP_KEY_MOUSE_BTN_COUNT (MP_MBTN_END - MP_MBTN_BASE)
|
||||
|
||||
// Apple Remote input module
|
||||
#define MP_AR_BASE (MP_KEY_BASE+0xE0)
|
||||
#define MP_AR_PLAY (MP_AR_BASE + 0)
|
||||
#define MP_AR_PLAY_HOLD (MP_AR_BASE + 1)
|
||||
#define MP_AR_CENTER (MP_AR_BASE + 2)
|
||||
#define MP_AR_CENTER_HOLD (MP_AR_BASE + 3)
|
||||
#define MP_AR_NEXT (MP_AR_BASE + 4)
|
||||
#define MP_AR_NEXT_HOLD (MP_AR_BASE + 5)
|
||||
#define MP_AR_PREV (MP_AR_BASE + 6)
|
||||
#define MP_AR_PREV_HOLD (MP_AR_BASE + 7)
|
||||
#define MP_AR_MENU (MP_AR_BASE + 8)
|
||||
#define MP_AR_MENU_HOLD (MP_AR_BASE + 9)
|
||||
#define MP_AR_VUP (MP_AR_BASE + 10)
|
||||
#define MP_AR_VUP_HOLD (MP_AR_BASE + 11)
|
||||
#define MP_AR_VDOWN (MP_AR_BASE + 12)
|
||||
#define MP_AR_VDOWN_HOLD (MP_AR_BASE + 13)
|
||||
|
||||
/* game controller keys */
|
||||
#define MP_KEY_GAMEPAD (MP_KEY_BASE+0xF0)
|
||||
#define MP_KEY_GAMEPAD_ACTION_DOWN (MP_KEY_GAMEPAD+0)
|
||||
@@ -210,7 +197,10 @@
|
||||
(MP_KEY_IS_MOUSE_CLICK(code) || MP_KEY_IS_MOUSE_MOVE(code))
|
||||
|
||||
// No input source should generate this.
|
||||
#define MP_KEY_UNMAPPED (MP_KEY_INTERN+4)
|
||||
#define MP_KEY_UNMAPPED (MP_KEY_INTERN+4)
|
||||
#define MP_KEY_ANY_UNICODE (MP_KEY_INTERN+5)
|
||||
// For mp_input_put_key(): release all keys that are down.
|
||||
#define MP_INPUT_RELEASE_ALL (MP_KEY_INTERN+6)
|
||||
|
||||
// Emit a command even on key-up (normally key-up is ignored). This means by
|
||||
// default they binding will be triggered on key-up instead of key-down.
|
||||
|
||||
+9
-3
@@ -29,7 +29,7 @@ struct gamepad_priv {
|
||||
|
||||
static Uint32 gamepad_cancel_wakeup;
|
||||
|
||||
static void initalize_events()
|
||||
static void initialize_events(void)
|
||||
{
|
||||
gamepad_cancel_wakeup = SDL_RegisterEvents(1);
|
||||
}
|
||||
@@ -198,13 +198,19 @@ static void remove_gamepad(struct mp_input_src *src, int id)
|
||||
|
||||
static void read_gamepad_thread(struct mp_input_src *src, void *param)
|
||||
{
|
||||
if (SDL_WasInit(SDL_INIT_EVENTS)) {
|
||||
MP_ERR(src, "Another component is using SDL already.\n");
|
||||
mp_input_src_init_done(src);
|
||||
return;
|
||||
}
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER)) {
|
||||
MP_ERR(src, "SDL_Init failed\n");
|
||||
mp_input_src_init_done(src);
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
pthread_once(&events_initialized, initalize_events);
|
||||
pthread_once(&events_initialized, initialize_events);
|
||||
|
||||
if (gamepad_cancel_wakeup == (Uint32)-1) {
|
||||
MP_ERR(src, "Can't register SDL custom events\n");
|
||||
|
||||
+4
-4
@@ -227,7 +227,7 @@ extern "C" {
|
||||
* relational operators (<, >, <=, >=).
|
||||
*/
|
||||
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 106)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 107)
|
||||
|
||||
/**
|
||||
* The API user is allowed to "#define MPV_ENABLE_DEPRECATED 0" before
|
||||
@@ -1356,15 +1356,16 @@ typedef enum mpv_event_id {
|
||||
* removed in the far future.
|
||||
*/
|
||||
MPV_EVENT_UNPAUSE = 13,
|
||||
#endif
|
||||
/**
|
||||
* Sent every time after a video frame is displayed. Note that currently,
|
||||
* this will be sent in lower frequency if there is no video, or playback
|
||||
* is paused - but that will be removed in the future, and it will be
|
||||
* restricted to video frames only.
|
||||
*
|
||||
* @deprecated Use mpv_observe_property() with relevant properties instead
|
||||
* (such as "playback-time").
|
||||
*/
|
||||
MPV_EVENT_TICK = 14,
|
||||
#if MPV_ENABLE_DEPRECATED
|
||||
/**
|
||||
* @deprecated This was used internally with the internal "script_dispatch"
|
||||
* command to dispatch keyboard and mouse input for the OSC.
|
||||
@@ -1496,7 +1497,6 @@ typedef struct mpv_event_property {
|
||||
*
|
||||
* Note that this is set to NULL if retrieving the property failed (the
|
||||
* format will be MPV_FORMAT_NONE).
|
||||
* See mpv_event.error for the status.
|
||||
*/
|
||||
void *data;
|
||||
} mpv_event_property;
|
||||
|
||||
+8
-32
@@ -22,8 +22,6 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "common/common.h"
|
||||
@@ -34,7 +32,7 @@ int bstrcmp(struct bstr str1, struct bstr str2)
|
||||
{
|
||||
int ret = 0;
|
||||
if (str1.len && str2.len)
|
||||
ret = memcmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
|
||||
ret = memcmp(str1.start, str2.start, MPMIN(str1.len, str2.len));
|
||||
|
||||
if (!ret) {
|
||||
if (str1.len == str2.len)
|
||||
@@ -51,7 +49,7 @@ int bstrcasecmp(struct bstr str1, struct bstr str2)
|
||||
{
|
||||
int ret = 0;
|
||||
if (str1.len && str2.len)
|
||||
ret = strncasecmp(str1.start, str2.start, FFMIN(str1.len, str2.len));
|
||||
ret = strncasecmp(str1.start, str2.start, MPMIN(str1.len, str2.len));
|
||||
|
||||
if (!ret) {
|
||||
if (str1.len == str2.len)
|
||||
@@ -157,9 +155,9 @@ struct bstr bstr_splice(struct bstr str, int start, int end)
|
||||
start += str.len;
|
||||
if (end < 0)
|
||||
end += str.len;
|
||||
end = FFMIN(end, str.len);
|
||||
start = FFMAX(start, 0);
|
||||
end = FFMAX(end, start);
|
||||
end = MPMIN(end, str.len);
|
||||
start = MPMAX(start, 0);
|
||||
end = MPMAX(end, start);
|
||||
str.start += start;
|
||||
str.len = end - start;
|
||||
return str;
|
||||
@@ -169,7 +167,7 @@ long long bstrtoll(struct bstr str, struct bstr *rest, int base)
|
||||
{
|
||||
str = bstr_lstrip(str);
|
||||
char buf[51];
|
||||
int len = FFMIN(str.len, 50);
|
||||
int len = MPMIN(str.len, 50);
|
||||
memcpy(buf, str.start, len);
|
||||
buf[len] = 0;
|
||||
char *endptr;
|
||||
@@ -183,7 +181,7 @@ double bstrtod(struct bstr str, struct bstr *rest)
|
||||
{
|
||||
str = bstr_lstrip(str);
|
||||
char buf[101];
|
||||
int len = FFMIN(str.len, 100);
|
||||
int len = MPMIN(str.len, 100);
|
||||
memcpy(buf, str.start, len);
|
||||
buf[len] = 0;
|
||||
char *endptr;
|
||||
@@ -193,28 +191,6 @@ double bstrtod(struct bstr str, struct bstr *rest)
|
||||
return r;
|
||||
}
|
||||
|
||||
struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str)
|
||||
{
|
||||
if (str.len == 0)
|
||||
return NULL;
|
||||
int count = 0;
|
||||
for (int i = 0; i < str.len; i++)
|
||||
if (str.start[i] == '\n')
|
||||
count++;
|
||||
if (str.start[str.len - 1] != '\n')
|
||||
count++;
|
||||
struct bstr *r = talloc_array_ptrtype(talloc_ctx, r, count);
|
||||
unsigned char *p = str.start;
|
||||
for (int i = 0; i < count - 1; i++) {
|
||||
r[i].start = p;
|
||||
while (*p++ != '\n');
|
||||
r[i].len = p - r[i].start;
|
||||
}
|
||||
r[count - 1].start = p;
|
||||
r[count - 1].len = str.start + str.len - p;
|
||||
return r;
|
||||
}
|
||||
|
||||
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c)
|
||||
{
|
||||
int pos = bstrchr(str, c);
|
||||
@@ -272,7 +248,7 @@ int bstr_parse_utf8_code_length(unsigned char b)
|
||||
{
|
||||
if (b < 128)
|
||||
return 1;
|
||||
int bytes = 7 - av_log2(b ^ 255);
|
||||
int bytes = 7 - mp_log2(b ^ 255);
|
||||
return (bytes >= 2 && bytes <= 4) ? bytes : -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ int bstrspn(struct bstr str, const char *accept);
|
||||
int bstrcspn(struct bstr str, const char *reject);
|
||||
|
||||
int bstr_find(struct bstr haystack, struct bstr needle);
|
||||
struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str);
|
||||
struct bstr bstr_lstrip(struct bstr str);
|
||||
struct bstr bstr_strip(struct bstr str);
|
||||
struct bstr bstr_split(struct bstr str, const char *sep, struct bstr *rest);
|
||||
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
* JNI utility functions
|
||||
*
|
||||
* Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <libavcodec/jni.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/bprint.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
static JavaVM *java_vm;
|
||||
static pthread_key_t current_env;
|
||||
static pthread_once_t once = PTHREAD_ONCE_INIT;
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static void jni_detach_env(void *data)
|
||||
{
|
||||
if (java_vm) {
|
||||
(*java_vm)->DetachCurrentThread(java_vm);
|
||||
}
|
||||
}
|
||||
|
||||
static void jni_create_pthread_key(void)
|
||||
{
|
||||
pthread_key_create(¤t_env, jni_detach_env);
|
||||
}
|
||||
|
||||
JNIEnv *mp_jni_get_env(struct mp_log *log)
|
||||
{
|
||||
int ret = 0;
|
||||
JNIEnv *env = NULL;
|
||||
|
||||
pthread_mutex_lock(&lock);
|
||||
if (java_vm == NULL) {
|
||||
java_vm = av_jni_get_java_vm(NULL);
|
||||
}
|
||||
|
||||
if (!java_vm) {
|
||||
mp_err(log, "No Java virtual machine has been registered\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
pthread_once(&once, jni_create_pthread_key);
|
||||
|
||||
if ((env = pthread_getspecific(current_env)) != NULL) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = (*java_vm)->GetEnv(java_vm, (void **)&env, JNI_VERSION_1_6);
|
||||
switch(ret) {
|
||||
case JNI_EDETACHED:
|
||||
if ((*java_vm)->AttachCurrentThread(java_vm, &env, NULL) != 0) {
|
||||
mp_err(log, "Failed to attach the JNI environment to the current thread\n");
|
||||
env = NULL;
|
||||
} else {
|
||||
pthread_setspecific(current_env, env);
|
||||
}
|
||||
break;
|
||||
case JNI_OK:
|
||||
break;
|
||||
case JNI_EVERSION:
|
||||
mp_err(log, "The specified JNI version is not supported\n");
|
||||
break;
|
||||
default:
|
||||
mp_err(log, "Failed to get the JNI environment attached to this thread\n");
|
||||
break;
|
||||
}
|
||||
|
||||
done:
|
||||
pthread_mutex_unlock(&lock);
|
||||
return env;
|
||||
}
|
||||
|
||||
char *mp_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, struct mp_log *log)
|
||||
{
|
||||
char *ret = NULL;
|
||||
const char *utf_chars = NULL;
|
||||
|
||||
jboolean copy = 0;
|
||||
|
||||
if (!string) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
utf_chars = (*env)->GetStringUTFChars(env, string, ©);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "String.getStringUTFChars() threw an exception\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = av_strdup(utf_chars);
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, string, utf_chars);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "String.releaseStringUTFChars() threw an exception\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, struct mp_log *log)
|
||||
{
|
||||
jstring ret;
|
||||
|
||||
ret = (*env)->NewStringUTF(env, utf_chars);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "NewStringUTF() threw an exception\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, struct mp_log *log)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
AVBPrint bp;
|
||||
|
||||
char *name = NULL;
|
||||
char *message = NULL;
|
||||
|
||||
jclass class_class = NULL;
|
||||
jmethodID get_name_id = NULL;
|
||||
|
||||
jclass exception_class = NULL;
|
||||
jmethodID get_message_id = NULL;
|
||||
|
||||
jstring string = NULL;
|
||||
|
||||
av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
|
||||
|
||||
exception_class = (*env)->GetObjectClass(env, exception);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "Could not find Throwable class\n");
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
class_class = (*env)->GetObjectClass(env, exception_class);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "Could not find Throwable class's class\n");
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
get_name_id = (*env)->GetMethodID(env, class_class, "getName", "()Ljava/lang/String;");
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "Could not find method Class.getName()\n");
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
string = (*env)->CallObjectMethod(env, exception_class, get_name_id);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "Class.getName() threw an exception\n");
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (string) {
|
||||
name = mp_jni_jstring_to_utf_chars(env, string, log);
|
||||
(*env)->DeleteLocalRef(env, string);
|
||||
string = NULL;
|
||||
}
|
||||
|
||||
get_message_id = (*env)->GetMethodID(env, exception_class, "getMessage", "()Ljava/lang/String;");
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "Could not find method java/lang/Throwable.getMessage()\n");
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
string = (*env)->CallObjectMethod(env, exception, get_message_id);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env);
|
||||
mp_err(log, "Throwable.getMessage() threw an exception\n");
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (string) {
|
||||
message = mp_jni_jstring_to_utf_chars(env, string, log);
|
||||
(*env)->DeleteLocalRef(env, string);
|
||||
string = NULL;
|
||||
}
|
||||
|
||||
if (name && message) {
|
||||
av_bprintf(&bp, "%s: %s", name, message);
|
||||
} else if (name && !message) {
|
||||
av_bprintf(&bp, "%s occurred", name);
|
||||
} else if (!name && message) {
|
||||
av_bprintf(&bp, "Exception: %s", message);
|
||||
} else {
|
||||
mp_warn(log, "Could not retrieve exception name and message\n");
|
||||
av_bprintf(&bp, "Exception occurred");
|
||||
}
|
||||
|
||||
ret = av_bprint_finalize(&bp, error);
|
||||
done:
|
||||
|
||||
av_free(name);
|
||||
av_free(message);
|
||||
|
||||
if (class_class) {
|
||||
(*env)->DeleteLocalRef(env, class_class);
|
||||
}
|
||||
|
||||
if (exception_class) {
|
||||
(*env)->DeleteLocalRef(env, exception_class);
|
||||
}
|
||||
|
||||
if (string) {
|
||||
(*env)->DeleteLocalRef(env, string);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mp_jni_exception_check(JNIEnv *env, int logging, struct mp_log *log)
|
||||
{
|
||||
int ret;
|
||||
|
||||
jthrowable exception;
|
||||
|
||||
char *message = NULL;
|
||||
|
||||
if (!(*(env))->ExceptionCheck((env))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!logging) {
|
||||
(*(env))->ExceptionClear((env));
|
||||
return -1;
|
||||
}
|
||||
|
||||
exception = (*env)->ExceptionOccurred(env);
|
||||
(*(env))->ExceptionClear((env));
|
||||
|
||||
if ((ret = mp_jni_exception_get_summary(env, exception, &message, log)) < 0) {
|
||||
(*env)->DeleteLocalRef(env, exception);
|
||||
return ret;
|
||||
}
|
||||
|
||||
(*env)->DeleteLocalRef(env, exception);
|
||||
|
||||
mp_err(log, "%s\n", message);
|
||||
av_free(message);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int mp_jni_init_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log)
|
||||
{
|
||||
int i, ret = 0;
|
||||
jclass last_clazz = NULL;
|
||||
|
||||
for (i = 0; jfields_mapping[i].name; i++) {
|
||||
int mandatory = jfields_mapping[i].mandatory;
|
||||
enum MPJniFieldType type = jfields_mapping[i].type;
|
||||
|
||||
if (type == MP_JNI_CLASS) {
|
||||
jclass clazz;
|
||||
|
||||
last_clazz = NULL;
|
||||
|
||||
clazz = (*env)->FindClass(env, jfields_mapping[i].name);
|
||||
if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
last_clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) =
|
||||
global ? (*env)->NewGlobalRef(env, clazz) : clazz;
|
||||
|
||||
if (global) {
|
||||
(*env)->DeleteLocalRef(env, clazz);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (!last_clazz) {
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
switch(type) {
|
||||
case MP_JNI_FIELD: {
|
||||
jfieldID field_id = (*env)->GetFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
|
||||
if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
|
||||
break;
|
||||
}
|
||||
case MP_JNI_STATIC_FIELD_AS_INT:
|
||||
case MP_JNI_STATIC_FIELD: {
|
||||
jfieldID field_id = (*env)->GetStaticFieldID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
|
||||
if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (type == MP_JNI_STATIC_FIELD_AS_INT) {
|
||||
if (field_id) {
|
||||
jint value = (*env)->GetStaticIntField(env, last_clazz, field_id);
|
||||
if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
|
||||
goto done;
|
||||
}
|
||||
*(jint*)((uint8_t*)jfields + jfields_mapping[i].offset) = value;
|
||||
}
|
||||
} else {
|
||||
*(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = field_id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MP_JNI_METHOD: {
|
||||
jmethodID method_id = (*env)->GetMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
|
||||
if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
|
||||
break;
|
||||
}
|
||||
case MP_JNI_STATIC_METHOD: {
|
||||
jmethodID method_id = (*env)->GetStaticMethodID(env, last_clazz, jfields_mapping[i].method, jfields_mapping[i].signature);
|
||||
if ((ret = mp_jni_exception_check(env, mandatory, log)) < 0 && mandatory) {
|
||||
goto done;
|
||||
}
|
||||
|
||||
*(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = method_id;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
mp_err(log, "Unknown JNI field type\n");
|
||||
ret = -1;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
|
||||
done:
|
||||
if (ret < 0) {
|
||||
/* reset jfields in case of failure so it does not leak references */
|
||||
mp_jni_reset_jfields(env, jfields, jfields_mapping, global, log);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mp_jni_reset_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; jfields_mapping[i].name; i++) {
|
||||
enum MPJniFieldType type = jfields_mapping[i].type;
|
||||
|
||||
switch(type) {
|
||||
case MP_JNI_CLASS: {
|
||||
jclass clazz = *(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset);
|
||||
if (!clazz)
|
||||
continue;
|
||||
|
||||
if (global) {
|
||||
(*env)->DeleteGlobalRef(env, clazz);
|
||||
} else {
|
||||
(*env)->DeleteLocalRef(env, clazz);
|
||||
}
|
||||
|
||||
*(jclass*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
|
||||
break;
|
||||
}
|
||||
case MP_JNI_FIELD: {
|
||||
*(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
|
||||
break;
|
||||
}
|
||||
case MP_JNI_STATIC_FIELD: {
|
||||
*(jfieldID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
|
||||
break;
|
||||
}
|
||||
case MP_JNI_STATIC_FIELD_AS_INT: {
|
||||
*(jint*)((uint8_t*)jfields + jfields_mapping[i].offset) = 0;
|
||||
break;
|
||||
}
|
||||
case MP_JNI_METHOD: {
|
||||
*(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
|
||||
break;
|
||||
}
|
||||
case MP_JNI_STATIC_METHOD: {
|
||||
*(jmethodID*)((uint8_t*)jfields + jfields_mapping[i].offset) = NULL;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
mp_err(log, "Unknown JNI field type\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* JNI utility functions
|
||||
*
|
||||
* Copyright (c) 2015-2016 Matthieu Bouron <matthieu.bouron stupeflix.com>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_JNI_H
|
||||
#define MP_JNI_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "common/msg.h"
|
||||
|
||||
/* Convenience macros */
|
||||
#define MP_JNI_GET_ENV(obj) mp_jni_get_env((obj)->log)
|
||||
#define MP_JNI_EXCEPTION_CHECK() mp_jni_exception_check(env, 0, NULL)
|
||||
#define MP_JNI_EXCEPTION_LOG(obj) mp_jni_exception_check(env, 1, (obj)->log)
|
||||
#define MP_JNI_DO(what, obj, method, ...) (*env)->what(env, obj, method, ##__VA_ARGS__)
|
||||
#define MP_JNI_NEW(clazz, method, ...) MP_JNI_DO(NewObject, clazz, method, ##__VA_ARGS__)
|
||||
#define MP_JNI_CALL_INT(obj, method, ...) MP_JNI_DO(CallIntMethod, obj, method, ##__VA_ARGS__)
|
||||
#define MP_JNI_CALL_BOOL(obj, method, ...) MP_JNI_DO(CallBooleanMethod, obj, method, ##__VA_ARGS__)
|
||||
#define MP_JNI_CALL_VOID(obj, method, ...) MP_JNI_DO(CallVoidMethod, obj, method, ##__VA_ARGS__)
|
||||
#define MP_JNI_CALL_STATIC_INT(clazz, method, ...) MP_JNI_DO(CallStaticIntMethod, clazz, method, ##__VA_ARGS__)
|
||||
#define MP_JNI_CALL_OBJECT(obj, method, ...) MP_JNI_DO(CallObjectMethod, obj, method, ##__VA_ARGS__)
|
||||
#define MP_JNI_GET_INT(obj, field) MP_JNI_DO(GetIntField, obj, field)
|
||||
#define MP_JNI_GET_LONG(obj, field) MP_JNI_DO(GetLongField, obj, field)
|
||||
#define MP_JNI_GET_BOOL(obj, field) MP_JNI_DO(GetBoolField, obj, field)
|
||||
|
||||
/*
|
||||
* Attach permanently a JNI environment to the current thread and retrieve it.
|
||||
*
|
||||
* If successfully attached, the JNI environment will automatically be detached
|
||||
* at thread destruction.
|
||||
*
|
||||
* @param attached pointer to an integer that will be set to 1 if the
|
||||
* environment has been attached to the current thread or 0 if it is
|
||||
* already attached.
|
||||
* @param log context used for logging
|
||||
* @return the JNI environment on success, NULL otherwise
|
||||
*/
|
||||
JNIEnv *mp_jni_get_env(struct mp_log *log);
|
||||
|
||||
/*
|
||||
* Convert a jstring to its utf characters equivalent.
|
||||
*
|
||||
* @param env JNI environment
|
||||
* @param string Java string to convert
|
||||
* @param log context used for logging
|
||||
* @return a pointer to an array of unicode characters on success, NULL
|
||||
* otherwise
|
||||
*/
|
||||
char *mp_jni_jstring_to_utf_chars(JNIEnv *env, jstring string, struct mp_log *log);
|
||||
|
||||
/*
|
||||
* Convert utf chars to its jstring equivalent.
|
||||
*
|
||||
* @param env JNI environment
|
||||
* @param utf_chars a pointer to an array of unicode characters
|
||||
* @param log context used for logging
|
||||
* @return a Java string object on success, NULL otherwise
|
||||
*/
|
||||
jstring mp_jni_utf_chars_to_jstring(JNIEnv *env, const char *utf_chars, struct mp_log *log);
|
||||
|
||||
/*
|
||||
* Extract the error summary from a jthrowable in the form of "className: errorMessage"
|
||||
*
|
||||
* @param env JNI environment
|
||||
* @param exception exception to get the summary from
|
||||
* @param error address pointing to the error, the value is updated if a
|
||||
* summary can be extracted
|
||||
* @param log context used for logging
|
||||
* @return 0 on success, < 0 otherwise
|
||||
*/
|
||||
int mp_jni_exception_get_summary(JNIEnv *env, jthrowable exception, char **error, struct mp_log *log);
|
||||
|
||||
/*
|
||||
* Check if an exception has occurred,log it using av_log and clear it.
|
||||
*
|
||||
* @param env JNI environment
|
||||
* @param value used to enable logging if an exception has occurred,
|
||||
* 0 disables logging, != 0 enables logging
|
||||
* @param log context used for logging
|
||||
*/
|
||||
int mp_jni_exception_check(JNIEnv *env, int logging, struct mp_log *log);
|
||||
|
||||
/*
|
||||
* Jni field type.
|
||||
*/
|
||||
enum MPJniFieldType {
|
||||
|
||||
MP_JNI_CLASS,
|
||||
MP_JNI_FIELD,
|
||||
MP_JNI_STATIC_FIELD,
|
||||
MP_JNI_STATIC_FIELD_AS_INT,
|
||||
MP_JNI_METHOD,
|
||||
MP_JNI_STATIC_METHOD
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* Jni field describing a class, a field or a method to be retrieved using
|
||||
* the mp_jni_init_jfields method.
|
||||
*/
|
||||
struct MPJniField {
|
||||
|
||||
const char *name;
|
||||
const char *method;
|
||||
const char *signature;
|
||||
enum MPJniFieldType type;
|
||||
int offset;
|
||||
int mandatory;
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
* Retrieve class references, field ids and method ids to an arbitrary structure.
|
||||
*
|
||||
* @param env JNI environment
|
||||
* @param jfields a pointer to an arbitrary structure where the different
|
||||
* fields are declared and where the MPJNIField mapping table offsets are
|
||||
* pointing to
|
||||
* @param jfields_mapping null terminated array of MPJNIFields describing
|
||||
* the class/field/method to be retrieved
|
||||
* @param global make the classes references global. It is the caller
|
||||
* responsibility to properly release global references.
|
||||
* @param log_ctx context used for logging, can be NULL
|
||||
* @return 0 on success, < 0 otherwise
|
||||
*/
|
||||
int mp_jni_init_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log);
|
||||
|
||||
/*
|
||||
* Delete class references, field ids and method ids of an arbitrary structure.
|
||||
*
|
||||
* @param env JNI environment
|
||||
* @param jfields a pointer to an arbitrary structure where the different
|
||||
* fields are declared and where the MPJNIField mapping table offsets are
|
||||
* pointing to
|
||||
* @param jfields_mapping null terminated array of MPJNIFields describing
|
||||
* the class/field/method to be deleted
|
||||
* @param global treat the classes references as global and delete them
|
||||
* accordingly
|
||||
* @param log_ctx context used for logging, can be NULL
|
||||
* @return 0 on success, < 0 otherwise
|
||||
*/
|
||||
int mp_jni_reset_jfields(JNIEnv *env, void *jfields, const struct MPJniField *jfields_mapping, int global, struct mp_log *log);
|
||||
|
||||
#endif
|
||||
+6
-5
@@ -18,8 +18,9 @@
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "ring.h"
|
||||
@@ -59,10 +60,10 @@ int mp_ring_read(struct mp_ring *buffer, unsigned char *dest, int len)
|
||||
{
|
||||
int size = mp_ring_size(buffer);
|
||||
int buffered = mp_ring_buffered(buffer);
|
||||
int read_len = FFMIN(len, buffered);
|
||||
int read_len = MPMIN(len, buffered);
|
||||
int read_ptr = mp_ring_get_rpos(buffer) % size;
|
||||
|
||||
int len1 = FFMIN(size - read_ptr, read_len);
|
||||
int len1 = MPMIN(size - read_ptr, read_len);
|
||||
int len2 = read_len - len1;
|
||||
|
||||
if (dest) {
|
||||
@@ -84,10 +85,10 @@ int mp_ring_write(struct mp_ring *buffer, unsigned char *src, int len)
|
||||
{
|
||||
int size = mp_ring_size(buffer);
|
||||
int free = mp_ring_available(buffer);
|
||||
int write_len = FFMIN(len, free);
|
||||
int write_len = MPMIN(len, free);
|
||||
int write_ptr = mp_ring_get_wpos(buffer) % size;
|
||||
|
||||
int len1 = FFMIN(size - write_ptr, write_len);
|
||||
int len1 = MPMIN(size - write_ptr, write_len);
|
||||
int len2 = write_len - len1;
|
||||
|
||||
memcpy(buffer->buffer + write_ptr, src, len1);
|
||||
|
||||
+511
-232
File diff suppressed because it is too large.
Load diff
+64
-49
@@ -35,6 +35,7 @@ struct m_sub_options;
|
||||
struct m_obj_desc;
|
||||
struct m_obj_settings;
|
||||
struct mp_log;
|
||||
struct mp_dispatch_queue;
|
||||
|
||||
// Config option
|
||||
struct m_config_option {
|
||||
@@ -71,15 +72,13 @@ typedef struct m_config {
|
||||
int (*includefunc)(void *ctx, char *filename, int flags);
|
||||
void *includefunc_ctx;
|
||||
|
||||
// Can intercept option write accesses.
|
||||
int (*option_set_callback)(void *ctx, struct m_config_option *co,
|
||||
void *data, int flags);
|
||||
void *option_set_callback_cb;
|
||||
|
||||
// Notification after an option was successfully written to.
|
||||
// Uses flags as set in UPDATE_OPTS_MASK.
|
||||
// self_update==true means the update was caused by a call to
|
||||
// m_config_notify_change_opt_ptr(). If false, it's caused either by
|
||||
// m_config_set_option_*() (and similar) calls or external updates.
|
||||
void (*option_change_callback)(void *ctx, struct m_config_option *co,
|
||||
int flags);
|
||||
int flags, bool self_update);
|
||||
void *option_change_callback_ctx;
|
||||
|
||||
// For the command line parser
|
||||
@@ -88,7 +87,8 @@ typedef struct m_config {
|
||||
void *optstruct; // struct mpopts or other
|
||||
|
||||
// Private. Non-NULL if data was allocated. m_config_option.data uses it.
|
||||
struct m_config_data *data;
|
||||
// API users call m_config_set_update_dispatch_queue() to get async updates.
|
||||
struct m_config_cache *cache;
|
||||
|
||||
// Private. Thread-safe shadow memory; only set for the main m_config.
|
||||
struct m_config_shadow *shadow;
|
||||
@@ -96,16 +96,10 @@ typedef struct m_config {
|
||||
|
||||
// Create a new config object.
|
||||
// talloc_ctx: talloc parent context for the m_config allocation
|
||||
// size: size of the optstruct (where option values are stored)
|
||||
// size==0 creates a config object with no option struct allocated
|
||||
// defaults: if not NULL, points to a struct of same type as optstruct, which
|
||||
// contains default values for all options
|
||||
// options: list of options. Each option defines a member of the optstruct
|
||||
// and a corresponding option switch or sub-option field.
|
||||
// Note that the m_config object will keep pointers to defaults and options.
|
||||
// root: description of all options
|
||||
// Note that the m_config object will keep pointers to root and log.
|
||||
struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log,
|
||||
size_t size, const void *defaults,
|
||||
const struct m_option *options);
|
||||
const struct m_sub_options *root);
|
||||
|
||||
// Create a m_config for the given desc. This is for --af/--vf, which have
|
||||
// different sub-options for every filter (represented by separate desc
|
||||
@@ -150,14 +144,10 @@ enum {
|
||||
M_SETOPT_FROM_CMDLINE = 8, // Mark as set by command line
|
||||
M_SETOPT_BACKUP = 16, // Call m_config_backup_opt() before
|
||||
M_SETOPT_PRESERVE_CMDLINE = 32, // Don't set if already marked as FROM_CMDLINE
|
||||
M_SETOPT_NO_FIXED = 64, // Reject M_OPT_FIXED options
|
||||
M_SETOPT_NO_PRE_PARSE = 128, // Reject M_OPT_PREPARSE options
|
||||
M_SETOPT_NO_OVERWRITE = 256, // Skip options marked with FROM_*
|
||||
};
|
||||
|
||||
// Flags for safe option setting during runtime.
|
||||
#define M_SETOPT_RUNTIME M_SETOPT_NO_FIXED
|
||||
|
||||
// Set the named option to the given string. This is for command line and config
|
||||
// file use only.
|
||||
// flags: combination of M_SETOPT_* flags (0 for normal operation)
|
||||
@@ -173,12 +163,6 @@ int m_config_set_option_raw(struct m_config *config, struct m_config_option *co,
|
||||
|
||||
void m_config_mark_co_flags(struct m_config_option *co, int flags);
|
||||
|
||||
// Unlike m_config_set_option_raw() this does not go through the property layer
|
||||
// via config.option_set_callback.
|
||||
int m_config_set_option_raw_direct(struct m_config *config,
|
||||
struct m_config_option *co,
|
||||
void *data, int flags);
|
||||
|
||||
// Convert the mpv_node to raw option data, then call m_config_set_option_raw().
|
||||
struct mpv_node;
|
||||
int m_config_set_option_node(struct m_config *config, bstr name,
|
||||
@@ -209,11 +193,7 @@ const char *m_config_get_positional_option(const struct m_config *config, int n)
|
||||
int m_config_option_requires_param(struct m_config *config, bstr name);
|
||||
|
||||
// Notify m_config_cache users that the option has (probably) changed its value.
|
||||
void m_config_notify_change_co(struct m_config *config,
|
||||
struct m_config_option *co);
|
||||
// Like m_config_notify_change_co(), but automatically find the option by its
|
||||
// pointer within the global option struct (config->optstruct). In practice,
|
||||
// it means it works only on fields in MPContext.opts.
|
||||
// This will force a self-notification back to config->option_change_callback.
|
||||
void m_config_notify_change_opt_ptr(struct m_config *config, void *ptr);
|
||||
|
||||
// Return all (visible) option names as NULL terminated string list.
|
||||
@@ -273,38 +253,40 @@ int m_config_set_profile(struct m_config *config, char *name, int flags);
|
||||
|
||||
struct mpv_node m_config_get_profiles(struct m_config *config);
|
||||
|
||||
// Run async option updates here. This will call option_change_callback() on it.
|
||||
void m_config_set_update_dispatch_queue(struct m_config *config,
|
||||
struct mp_dispatch_queue *dispatch);
|
||||
|
||||
// This can be used to create and synchronize per-thread option structs,
|
||||
// which then can be read without synchronization. No concurrent access to
|
||||
// the cache itself is allowed.
|
||||
struct m_config_cache {
|
||||
// The struct as indicated by m_config_cache_alloc's group parameter.
|
||||
// (Internally the same as data->gdata[0]->udata.)
|
||||
// (Internally the same as internal->gdata[0]->udata.)
|
||||
void *opts;
|
||||
// Accumulated change flags. The user can set this to 0 to unset all flags.
|
||||
// They are set when calling any of the update functions. A flag is only set
|
||||
// once the new value is visible in ->opts.
|
||||
uint64_t change_flags;
|
||||
|
||||
// Internal.
|
||||
struct m_config_shadow *shadow; // real data
|
||||
struct m_config_data *data; // copy for the cache user
|
||||
bool in_list; // registered as listener with root config
|
||||
// --- Implicitly synchronized by setting/unsetting wakeup_cb.
|
||||
struct mp_dispatch_queue *wakeup_dispatch_queue;
|
||||
void (*wakeup_dispatch_cb)(void *ctx);
|
||||
void *wakeup_dispatch_cb_ctx;
|
||||
// --- Protected by shadow->lock
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
void *wakeup_cb_ctx;
|
||||
// Set to non-NULL for logging all option changes as they are retrieved
|
||||
// with one of the update functions (like m_config_cache_update()).
|
||||
struct mp_log *debug;
|
||||
|
||||
// Do not access.
|
||||
struct config_cache *internal;
|
||||
};
|
||||
|
||||
#define GLOBAL_CONFIG NULL
|
||||
|
||||
// Create a mirror copy from the global options.
|
||||
// Keep in mind that a m_config_cache object is not thread-safe; it merely
|
||||
// provides thread-safe access to the global options. All API functions for
|
||||
// the same m_config_cache object must synchronized, unless otherwise noted.
|
||||
// This does not create an initial change event (m_config_cache_update() will
|
||||
// return false), but note that a change might be asynchronously signaled at any
|
||||
// time.
|
||||
// ta_parent: parent for the returned allocation
|
||||
// global: option data source
|
||||
// group: the option group to return. This can be GLOBAL_CONFIG for the global
|
||||
// option struct (MPOpts), or m_sub_options used in a certain
|
||||
// OPT_SUBSTRUCT() item.
|
||||
// group: the option group to return
|
||||
struct m_config_cache *m_config_cache_alloc(void *ta_parent,
|
||||
struct mpv_global *global,
|
||||
const struct m_sub_options *group);
|
||||
@@ -330,18 +312,51 @@ void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache,
|
||||
// some options have changed).
|
||||
// Keep in mind that while the cache->opts pointer does not change, the option
|
||||
// data itself will (e.g. string options might be reallocated).
|
||||
// New change flags are or-ed into cache->change_flags with this call (if you
|
||||
// use them, you should probably do cache->change_flags=0 before this call).
|
||||
bool m_config_cache_update(struct m_config_cache *cache);
|
||||
|
||||
// Check for changes and return fine grained change information.
|
||||
// Warning: this conflicts with m_config_cache_update(). If you call
|
||||
// m_config_cache_update(), all options will be marked as "not changed",
|
||||
// and this function will return false. Also, calling this function and
|
||||
// then m_config_cache_update() is not supported, and may skip updating
|
||||
// some fields.
|
||||
// This returns true as long as there is a changed option, and false if all
|
||||
// changed options have been returned.
|
||||
// If multiple options have changed, the new option value is visible only once
|
||||
// this function has returned the change for it.
|
||||
// out_ptr: pointer to a void*, which is set to the cache->opts field associated
|
||||
// with the changed option if the function returns true; set to NULL
|
||||
// if no option changed.
|
||||
// returns: *out_ptr!=NULL (true if there was a changed option)
|
||||
bool m_config_cache_get_next_changed(struct m_config_cache *cache, void **out_ptr);
|
||||
|
||||
// Copy the option field pointed to by ptr to the global option storage. This
|
||||
// is sort of similar to m_config_set_option_raw(), except doesn't require
|
||||
// access to the main thread. (And you can't pass any flags.)
|
||||
// You write the new value to the option struct, and then call this function
|
||||
// with the pointer to it. You will not get a change notification for it (though
|
||||
// you might still get a redundant wakeup callback).
|
||||
// Changing the option struct and not calling this function before any update
|
||||
// function (like m_config_cache_update()) will leave the value inconsistent,
|
||||
// and will possibly (but not necessarily) overwrite it with the next update
|
||||
// call.
|
||||
// ptr: points to any field in cache->opts that is managed by an option. If
|
||||
// this is not the case, the function crashes for your own good.
|
||||
// returns: if true, this was an update; if false, shadow had same value
|
||||
bool m_config_cache_write_opt(struct m_config_cache *cache, void *ptr);
|
||||
|
||||
// Like m_config_cache_alloc(), but return the struct (m_config_cache->opts)
|
||||
// directly, with no way to update the config. Basically this returns a copy
|
||||
// with a snapshot of the current option values.
|
||||
// group==GLOBAL_CONFIG is a special case, and always returns the root group.
|
||||
void *mp_get_config_group(void *ta_parent, struct mpv_global *global,
|
||||
const struct m_sub_options *group);
|
||||
|
||||
// Read a single global option in a thread-safe way. For multiple options,
|
||||
// use m_config_cache. The option must exist and match the provided type (the
|
||||
// type is used as a sanity check only). Performs semi-expensive lookup.
|
||||
// Warning: new code must not use this.
|
||||
void mp_read_option_raw(struct mpv_global *global, const char *name,
|
||||
const struct m_option_type *type, void *dst);
|
||||
|
||||
|
||||
+261
-44
@@ -31,7 +31,6 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/avstring.h>
|
||||
|
||||
#include "libmpv/client.h"
|
||||
#include "player/client.h"
|
||||
@@ -41,6 +40,7 @@
|
||||
#include "common/msg.h"
|
||||
#include "common/msg_control.h"
|
||||
#include "misc/json.h"
|
||||
#include "misc/node.h"
|
||||
#include "m_option.h"
|
||||
#include "m_config.h"
|
||||
|
||||
@@ -86,15 +86,6 @@ int m_option_required_params(const m_option_t *opt)
|
||||
return 1;
|
||||
}
|
||||
|
||||
const m_option_t *m_option_list_find(const m_option_t *list, const char *name)
|
||||
{
|
||||
for (int i = 0; list[i].name; i++) {
|
||||
if (strcmp(list[i].name, name) == 0)
|
||||
return &list[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int m_option_set_node_or_string(struct mp_log *log, const m_option_t *opt,
|
||||
const char *name, void *dst, struct mpv_node *src)
|
||||
{
|
||||
@@ -179,6 +170,11 @@ static int flag_get(const m_option_t *opt, void *ta_parent,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool flag_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
return VAL(a) == VAL(b);
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_flag = {
|
||||
// need yes or no in config files
|
||||
.name = "Flag",
|
||||
@@ -190,6 +186,7 @@ const m_option_type_t m_option_type_flag = {
|
||||
.add = add_flag,
|
||||
.set = flag_set,
|
||||
.get = flag_get,
|
||||
.equal = flag_equal,
|
||||
};
|
||||
|
||||
// Integer
|
||||
@@ -285,6 +282,8 @@ static void add_int64(const m_option_t *opt, void *val, double add, bool wrap)
|
||||
{
|
||||
int64_t v = *(int64_t *)val;
|
||||
|
||||
clamp_int64(opt, &v);
|
||||
|
||||
v = v + add;
|
||||
|
||||
bool is64 = opt->type->size == sizeof(int64_t);
|
||||
@@ -369,6 +368,16 @@ static int int_get(const m_option_t *opt, void *ta_parent,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool int_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
return *(int *)a == *(int *)b;
|
||||
}
|
||||
|
||||
static bool int64_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
return *(int64_t *)a == *(int64_t *)b;
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_int = {
|
||||
.name = "Integer",
|
||||
.size = sizeof(int),
|
||||
@@ -379,6 +388,7 @@ const m_option_type_t m_option_type_int = {
|
||||
.multiply = multiply_int,
|
||||
.set = int_set,
|
||||
.get = int_get,
|
||||
.equal = int_equal,
|
||||
};
|
||||
|
||||
const m_option_type_t m_option_type_int64 = {
|
||||
@@ -391,6 +401,7 @@ const m_option_type_t m_option_type_int64 = {
|
||||
.multiply = multiply_int64,
|
||||
.set = int64_set,
|
||||
.get = int64_get,
|
||||
.equal = int64_equal,
|
||||
};
|
||||
|
||||
static int parse_byte_size(struct mp_log *log, const m_option_t *opt,
|
||||
@@ -488,6 +499,7 @@ const m_option_type_t m_option_type_byte_size = {
|
||||
.multiply = multiply_int64,
|
||||
.set = int64_set,
|
||||
.get = int64_get,
|
||||
.equal = int64_equal,
|
||||
};
|
||||
|
||||
static int parse_intpair(struct mp_log *log, const struct m_option *opt,
|
||||
@@ -611,12 +623,12 @@ static void choice_get_min_max(const struct m_option *opt, int *min, int *max)
|
||||
*min = INT_MAX;
|
||||
*max = INT_MIN;
|
||||
for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++) {
|
||||
*min = FFMIN(*min, alt->value);
|
||||
*max = FFMAX(*max, alt->value);
|
||||
*min = MPMIN(*min, alt->value);
|
||||
*max = MPMAX(*max, alt->value);
|
||||
}
|
||||
if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
|
||||
*min = FFMIN(*min, opt->min);
|
||||
*max = FFMAX(*max, opt->max);
|
||||
*min = MPMIN(*min, opt->min);
|
||||
*max = MPMAX(*max, opt->max);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -756,6 +768,7 @@ const struct m_option_type m_option_type_choice = {
|
||||
.add = add_choice,
|
||||
.set = choice_set,
|
||||
.get = choice_get,
|
||||
.equal = int_equal,
|
||||
};
|
||||
|
||||
static int apply_flag(const struct m_option *opt, int *val, bstr flag)
|
||||
@@ -878,6 +891,7 @@ const struct m_option_type m_option_type_flags = {
|
||||
.copy = copy_opt,
|
||||
.set = flags_set,
|
||||
.get = flags_get,
|
||||
.equal = int_equal,
|
||||
};
|
||||
|
||||
// Float
|
||||
@@ -1012,6 +1026,14 @@ static int double_get(const m_option_t *opt, void *ta_parent,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool double_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
double fa = VAL(a), fb = VAL(b);
|
||||
if (isnan(fa) || isnan(fb))
|
||||
return isnan(fa) == isnan(fb);
|
||||
return fa == fb;
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_double = {
|
||||
// double precision float or ratio (numerator[:/]denominator)
|
||||
.name = "Double",
|
||||
@@ -1024,6 +1046,7 @@ const m_option_type_t m_option_type_double = {
|
||||
.multiply = multiply_double,
|
||||
.set = double_set,
|
||||
.get = double_get,
|
||||
.equal = double_equal,
|
||||
};
|
||||
|
||||
#undef VAL
|
||||
@@ -1081,6 +1104,11 @@ static int float_get(const m_option_t *opt, void *ta_parent,
|
||||
return double_get(opt, ta_parent, dst, &tmp);
|
||||
}
|
||||
|
||||
static bool float_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
return double_equal(opt, &(double){VAL(a)}, &(double){VAL(b)});
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_float = {
|
||||
// floating point number or ratio (numerator[:/]denominator)
|
||||
.name = "Float",
|
||||
@@ -1093,6 +1121,7 @@ const m_option_type_t m_option_type_float = {
|
||||
.multiply = multiply_float,
|
||||
.set = float_set,
|
||||
.get = float_get,
|
||||
.equal = float_equal,
|
||||
};
|
||||
|
||||
static int parse_float_aspect(struct mp_log *log, const m_option_t *opt,
|
||||
@@ -1118,6 +1147,7 @@ const m_option_type_t m_option_type_aspect = {
|
||||
.multiply = multiply_float,
|
||||
.set = float_set,
|
||||
.get = float_get,
|
||||
.equal = float_equal,
|
||||
};
|
||||
|
||||
///////////// String
|
||||
@@ -1198,6 +1228,11 @@ static int str_get(const m_option_t *opt, void *ta_parent,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool str_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
return bstr_equals(bstr0(VAL(a)), bstr0(VAL(b)));
|
||||
}
|
||||
|
||||
static void free_str(void *src)
|
||||
{
|
||||
if (src && VAL(src)) {
|
||||
@@ -1215,6 +1250,7 @@ const m_option_type_t m_option_type_string = {
|
||||
.free = free_str,
|
||||
.set = str_set,
|
||||
.get = str_get,
|
||||
.equal = str_equal,
|
||||
};
|
||||
|
||||
//////////// String list
|
||||
@@ -1228,7 +1264,8 @@ const m_option_type_t m_option_type_string = {
|
||||
#define OP_DEL 3
|
||||
#define OP_CLR 4
|
||||
#define OP_TOGGLE 5
|
||||
#define OP_ADD_STR 6
|
||||
#define OP_APPEND 6
|
||||
#define OP_REMOVE 7
|
||||
|
||||
static void free_str_list(void *dst)
|
||||
{
|
||||
@@ -1367,11 +1404,20 @@ static int parse_str_list_impl(struct mp_log *log, const m_option_t *opt,
|
||||
op = OP_PRE;
|
||||
} else if (bstr_endswith0(name, "-del")) {
|
||||
op = OP_DEL;
|
||||
mp_warn(log, "Option %.*s: -del is deprecated! "
|
||||
"Use -remove (removes by content instead of by index).\n",
|
||||
BSTR_P(name));
|
||||
} else if (bstr_endswith0(name, "-clr")) {
|
||||
op = OP_CLR;
|
||||
} else if (bstr_endswith0(name, "-set")) {
|
||||
op = OP_NONE;
|
||||
} else if (bstr_endswith0(name, "-toggle")) {
|
||||
op = OP_TOGGLE;
|
||||
} else if (bstr_endswith0(name, "-remove")) {
|
||||
op = OP_REMOVE;
|
||||
}
|
||||
|
||||
if (op == OP_TOGGLE || op == OP_REMOVE) {
|
||||
if (dst) {
|
||||
char **list = VAL(dst);
|
||||
int index = find_list_bstr(list, param);
|
||||
@@ -1383,6 +1429,8 @@ static int parse_str_list_impl(struct mp_log *log, const m_option_t *opt,
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
if (op == OP_REMOVE)
|
||||
return 1; // ignore if not found
|
||||
op = OP_ADD;
|
||||
multi = false;
|
||||
}
|
||||
@@ -1540,6 +1588,26 @@ static int parse_str_list(struct mp_log *log, const m_option_t *opt,
|
||||
return parse_str_list_impl(log, opt, name, param, dst, OP_NONE);
|
||||
}
|
||||
|
||||
static bool str_list_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
char **la = VAL(a);
|
||||
char **lb = VAL(b);
|
||||
|
||||
bool a_empty = !la || !la[0];
|
||||
bool b_empty = !lb || !lb[0];
|
||||
if (a_empty || b_empty)
|
||||
return a_empty == b_empty;
|
||||
|
||||
for (int n = 0; la[n] || lb[n]; n++) {
|
||||
if (!la[n] || !lb[n])
|
||||
return false;
|
||||
if (strcmp(la[n], lb[n]) != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_string_list = {
|
||||
.name = "String list",
|
||||
.size = sizeof(char **),
|
||||
@@ -1549,6 +1617,7 @@ const m_option_type_t m_option_type_string_list = {
|
||||
.free = free_str_list,
|
||||
.get = str_list_get,
|
||||
.set = str_list_set,
|
||||
.equal = str_list_equal,
|
||||
.actions = (const struct m_option_action[]){
|
||||
{"add"},
|
||||
{"append"},
|
||||
@@ -1557,6 +1626,7 @@ const m_option_type_t m_option_type_string_list = {
|
||||
{"pre"},
|
||||
{"set"},
|
||||
{"toggle"},
|
||||
{"remove"},
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1564,6 +1634,28 @@ const m_option_type_t m_option_type_string_list = {
|
||||
static int read_subparam(struct mp_log *log, bstr optname, char *termset,
|
||||
bstr *str, bstr *out_subparam);
|
||||
|
||||
static int keyvalue_list_find_key(char **lst, bstr str)
|
||||
{
|
||||
for (int n = 0; lst && lst[n] && lst[n + 1]; n += 2) {
|
||||
if (bstr_equals0(str, lst[n]))
|
||||
return n / 2;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void keyvalue_list_del_key(char **lst, int index)
|
||||
{
|
||||
int count = 0;
|
||||
for (int n = 0; lst && lst[n]; n++)
|
||||
count++;
|
||||
assert(index * 2 + 1 < count);
|
||||
count += 1; // terminating item
|
||||
talloc_free(lst[index * 2 + 0]);
|
||||
talloc_free(lst[index * 2 + 1]);
|
||||
MP_TARRAY_REMOVE_AT(lst, count, index * 2 + 1);
|
||||
MP_TARRAY_REMOVE_AT(lst, count, index * 2 + 0);
|
||||
}
|
||||
|
||||
static int parse_keyvalue_list(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param, void *dst)
|
||||
{
|
||||
@@ -1580,6 +1672,14 @@ static int parse_keyvalue_list(struct mp_log *log, const m_option_t *opt,
|
||||
append = true;
|
||||
} else if (bstr_endswith0(name, "-append")) {
|
||||
append = full_value = true;
|
||||
} else if (bstr_endswith0(name, "-remove")) {
|
||||
lst = dst ? VAL(dst) : NULL;
|
||||
int index = dst ? keyvalue_list_find_key(lst, param) : -1;
|
||||
if (index >= 0) {
|
||||
keyvalue_list_del_key(lst, index);
|
||||
VAL(dst) = lst;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (append && dst) {
|
||||
@@ -1607,15 +1707,25 @@ static int parse_keyvalue_list(struct mp_log *log, const m_option_t *opt,
|
||||
break;
|
||||
}
|
||||
if (dst) {
|
||||
int index = keyvalue_list_find_key(lst, key);
|
||||
if (index >= 0) {
|
||||
keyvalue_list_del_key(lst, index);
|
||||
num -= 2;
|
||||
}
|
||||
MP_TARRAY_APPEND(NULL, lst, num, bstrto0(NULL, key));
|
||||
MP_TARRAY_APPEND(NULL, lst, num, bstrto0(NULL, val));
|
||||
MP_TARRAY_APPEND(NULL, lst, num, NULL);
|
||||
num -= 1;
|
||||
}
|
||||
|
||||
if (!bstr_eatstart0(¶m, ",") && !bstr_eatstart0(¶m, ":"))
|
||||
break;
|
||||
|
||||
if (append) {
|
||||
mp_warn(log, "Passing more than 1 argument to %.*s is deprecated!\n",
|
||||
BSTR_P(name));
|
||||
}
|
||||
}
|
||||
if (dst)
|
||||
MP_TARRAY_APPEND(NULL, lst, num, NULL);
|
||||
|
||||
if (param.len) {
|
||||
mp_err(log, "Unparseable garbage at end of option value: '%.*s'\n",
|
||||
@@ -1698,10 +1808,12 @@ const m_option_type_t m_option_type_keyvalue_list = {
|
||||
.free = free_str_list,
|
||||
.get = keyvalue_list_get,
|
||||
.set = keyvalue_list_set,
|
||||
.equal = str_list_equal,
|
||||
.actions = (const struct m_option_action[]){
|
||||
{"add"},
|
||||
{"append"},
|
||||
{"set"},
|
||||
{"remove"},
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1770,6 +1882,7 @@ const m_option_type_t m_option_type_msglevels = {
|
||||
.free = free_str_list,
|
||||
.get = keyvalue_list_get,
|
||||
.set = set_msglevels,
|
||||
.equal = str_list_equal,
|
||||
};
|
||||
|
||||
static int parse_print(struct mp_log *log, const m_option_t *opt,
|
||||
@@ -1982,12 +2095,20 @@ static char *print_color(const m_option_t *opt, const void *val)
|
||||
return talloc_asprintf(NULL, "#%02X%02X%02X%02X", c->a, c->r, c->g, c->b);
|
||||
}
|
||||
|
||||
static bool color_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
struct m_color *ca = a;
|
||||
struct m_color *cb = b;
|
||||
return ca->a == cb->a && ca->r == cb->r && ca->g == cb->g && ca->b == cb->b;
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_color = {
|
||||
.name = "Color",
|
||||
.size = sizeof(struct m_color),
|
||||
.parse = parse_color,
|
||||
.print = print_color,
|
||||
.copy = copy_opt,
|
||||
.equal = color_equal,
|
||||
};
|
||||
|
||||
|
||||
@@ -2164,12 +2285,24 @@ exit:
|
||||
return is_help ? M_OPT_EXIT : M_OPT_INVALID;
|
||||
}
|
||||
|
||||
static bool geometry_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
struct m_geometry *ga = a;
|
||||
struct m_geometry *gb = b;
|
||||
return ga->x == gb->x && ga->y == gb->y && ga->w == gb->w && ga->h == gb->h &&
|
||||
ga->xy_valid == gb->xy_valid && ga->wh_valid == gb->wh_valid &&
|
||||
ga->w_per == gb->w_per && ga->h_per == gb->h_per &&
|
||||
ga->x_per == gb->x_per && ga->y_per == gb->y_per &&
|
||||
ga->x_sign == gb->x_sign && ga->y_sign == gb->y_sign;
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_geometry = {
|
||||
.name = "Window geometry",
|
||||
.size = sizeof(struct m_geometry),
|
||||
.parse = parse_geometry,
|
||||
.print = print_geometry,
|
||||
.copy = copy_opt,
|
||||
.equal = geometry_equal,
|
||||
};
|
||||
|
||||
static int parse_size_box(struct mp_log *log, const m_option_t *opt,
|
||||
@@ -2206,6 +2339,7 @@ const m_option_type_t m_option_type_size_box = {
|
||||
.parse = parse_size_box,
|
||||
.print = print_geometry,
|
||||
.copy = copy_opt,
|
||||
.equal = geometry_equal,
|
||||
};
|
||||
|
||||
|
||||
@@ -2256,6 +2390,7 @@ const m_option_type_t m_option_type_imgfmt = {
|
||||
.parse = parse_imgfmt,
|
||||
.print = print_imgfmt,
|
||||
.copy = copy_opt,
|
||||
.equal = int_equal,
|
||||
};
|
||||
|
||||
static int parse_fourcc(struct mp_log *log, const m_option_t *opt,
|
||||
@@ -2297,6 +2432,7 @@ const m_option_type_t m_option_type_fourcc = {
|
||||
.parse = parse_fourcc,
|
||||
.print = print_fourcc,
|
||||
.copy = copy_opt,
|
||||
.equal = int_equal,
|
||||
};
|
||||
|
||||
#include "audio/format.h"
|
||||
@@ -2344,6 +2480,7 @@ const m_option_type_t m_option_type_afmt = {
|
||||
.parse = parse_afmt,
|
||||
.print = print_afmt,
|
||||
.copy = copy_opt,
|
||||
.equal = int_equal,
|
||||
};
|
||||
|
||||
#include "audio/chmap.h"
|
||||
@@ -2446,13 +2583,32 @@ static void copy_channels(const m_option_t *opt, void *dst, const void *src)
|
||||
talloc_memdup(NULL, ch->chmaps, sizeof(ch->chmaps[0]) * ch->num_chmaps);
|
||||
}
|
||||
|
||||
static bool channels_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
struct m_channels *ca = a;
|
||||
struct m_channels *cb = b;
|
||||
|
||||
if (ca->set != cb->set ||
|
||||
ca->auto_safe != cb->auto_safe ||
|
||||
ca->num_chmaps != cb->num_chmaps)
|
||||
return false;
|
||||
|
||||
for (int n = 0; n < ca->num_chmaps; n++) {
|
||||
if (!mp_chmap_equals(&ca->chmaps[n], &cb->chmaps[n]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_channels = {
|
||||
.name = "Audio channels or channel map",
|
||||
.size = sizeof(struct m_channels),
|
||||
.parse = parse_channels,
|
||||
.print = print_channels,
|
||||
.copy = copy_channels,
|
||||
.free = free_channels,
|
||||
.free = free_channels,
|
||||
.equal = channels_equal,
|
||||
};
|
||||
|
||||
static int parse_timestring(struct bstr str, double *time, char endchar)
|
||||
@@ -2545,6 +2701,7 @@ const m_option_type_t m_option_type_time = {
|
||||
.add = add_double,
|
||||
.set = time_set,
|
||||
.get = time_get,
|
||||
.equal = double_equal,
|
||||
};
|
||||
|
||||
|
||||
@@ -2621,12 +2778,20 @@ static char *print_rel_time(const m_option_t *opt, const void *val)
|
||||
return talloc_strdup(NULL, "none");
|
||||
}
|
||||
|
||||
static bool rel_time_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
struct m_rel_time *ta = a;
|
||||
struct m_rel_time *tb = b;
|
||||
return ta->type == tb->type && ta->pos == tb->pos;
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_rel_time = {
|
||||
.name = "Relative time or percent position",
|
||||
.size = sizeof(struct m_rel_time),
|
||||
.parse = parse_rel_time,
|
||||
.print = print_rel_time,
|
||||
.copy = copy_opt,
|
||||
.equal = rel_time_equal,
|
||||
};
|
||||
|
||||
|
||||
@@ -2664,27 +2829,13 @@ static void obj_setting_free(m_obj_settings_t *item)
|
||||
}
|
||||
|
||||
// If at least one item has a label, compare labels only - otherwise ignore them.
|
||||
static bool obj_setting_equals(m_obj_settings_t *a, m_obj_settings_t *b)
|
||||
static bool obj_setting_match(m_obj_settings_t *a, m_obj_settings_t *b)
|
||||
{
|
||||
bstr la = bstr0(a->label), lb = bstr0(b->label);
|
||||
if (la.len || lb.len)
|
||||
return bstr_equals(la, lb);
|
||||
if (strcmp(a->name, b->name) != 0)
|
||||
return false;
|
||||
|
||||
int a_attr_count = 0;
|
||||
while (a->attribs && a->attribs[a_attr_count])
|
||||
a_attr_count++;
|
||||
int b_attr_count = 0;
|
||||
while (b->attribs && b->attribs[b_attr_count])
|
||||
b_attr_count++;
|
||||
if (a_attr_count != b_attr_count)
|
||||
return false;
|
||||
for (int n = 0; n < a_attr_count; n++) {
|
||||
if (strcmp(a->attribs[n], b->attribs[n]) != 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return m_obj_settings_equal(a, b);
|
||||
}
|
||||
|
||||
static int obj_settings_list_num_items(m_obj_settings_t *obj_list)
|
||||
@@ -2749,7 +2900,7 @@ static int obj_settings_find_by_content(m_obj_settings_t *obj_list,
|
||||
m_obj_settings_t *item)
|
||||
{
|
||||
for (int n = 0; obj_list && obj_list[n].name; n++) {
|
||||
if (obj_setting_equals(&obj_list[n], item))
|
||||
if (obj_setting_match(&obj_list[n], item))
|
||||
return n;
|
||||
}
|
||||
return -1;
|
||||
@@ -3031,7 +3182,11 @@ static int parse_obj_settings(struct mp_log *log, struct bstr opt, int op,
|
||||
mp_warn(log, "Driver '%s' has been replaced with '%s'!\n",
|
||||
desc.replaced_name, desc.name);
|
||||
} else {
|
||||
if (!list->allow_unknown_entries) {
|
||||
char name[80];
|
||||
snprintf(name, sizeof(name), "%.*s", BSTR_P(str));
|
||||
if (!list->allow_unknown_entries ||
|
||||
(list->check_unknown_entry && !list->check_unknown_entry(name)))
|
||||
{
|
||||
mp_err(log, "Option %.*s: %.*s doesn't exist.\n",
|
||||
BSTR_P(opt), BSTR_P(str));
|
||||
return M_OPT_INVALID;
|
||||
@@ -3068,7 +3223,8 @@ done: ;
|
||||
// Parse a single entry for -vf-del (return 0 if not applicable)
|
||||
// mark_del is bounded by the number of items in dst
|
||||
static int parse_obj_settings_del(struct mp_log *log, struct bstr opt_name,
|
||||
struct bstr *param, void *dst, bool *mark_del)
|
||||
struct bstr *param, int op,
|
||||
void *dst, bool *mark_del)
|
||||
{
|
||||
bstr s = *param;
|
||||
if (bstr_eatstart0(&s, "@")) {
|
||||
@@ -3092,6 +3248,9 @@ static int parse_obj_settings_del(struct mp_log *log, struct bstr opt_name,
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (op == OP_REMOVE)
|
||||
return 0;
|
||||
|
||||
bstr rest;
|
||||
long long id = bstrtoll(s, &rest, 0);
|
||||
if (rest.len == s.len)
|
||||
@@ -3127,12 +3286,19 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
|
||||
if (bstr_endswith0(name, "-add")) {
|
||||
op = OP_ADD;
|
||||
} else if (bstr_endswith0(name, "-append")) {
|
||||
op = OP_APPEND;
|
||||
} else if (bstr_endswith0(name, "-set")) {
|
||||
op = OP_NONE;
|
||||
} else if (bstr_endswith0(name, "-pre")) {
|
||||
op = OP_PRE;
|
||||
} else if (bstr_endswith0(name, "-del")) {
|
||||
op = OP_DEL;
|
||||
mp_warn(log, "Option %.*s: -del is deprecated! "
|
||||
"Use -remove (removes by content instead of by index).\n",
|
||||
BSTR_P(name));
|
||||
} else if (bstr_endswith0(name, "-remove")) {
|
||||
op = OP_REMOVE;
|
||||
} else if (bstr_endswith0(name, "-clr")) {
|
||||
op = OP_CLR;
|
||||
} else if (bstr_endswith0(name, "-toggle")) {
|
||||
@@ -3142,10 +3308,14 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
"Supported operations are:\n"
|
||||
" %s-set\n"
|
||||
" Overwrite the old list with the given list\n\n"
|
||||
" %s-append\n"
|
||||
" Append the given filter to the current list\n\n"
|
||||
" %s-add\n"
|
||||
" Append the given list to the current list\n\n"
|
||||
" %s-pre\n"
|
||||
" Prepend the given list to the current list\n\n"
|
||||
" %s-remove\n"
|
||||
" Remove the given filter from the current list\n\n"
|
||||
" %s-del x,y,...\n"
|
||||
" Remove the given elements. Take the list element index (starting from 0).\n"
|
||||
" Negative index can be used (i.e. -1 is the last element).\n"
|
||||
@@ -3155,7 +3325,7 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
" %s-clr\n"
|
||||
" Clear the current list.\n\n",
|
||||
opt->name, opt->name, opt->name, opt->name, opt->name,
|
||||
opt->name, opt->name);
|
||||
opt->name, opt->name, opt->name, opt->name);
|
||||
|
||||
return M_OPT_EXIT;
|
||||
}
|
||||
@@ -3190,7 +3360,7 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
if (dst)
|
||||
free_obj_settings_list(dst);
|
||||
return 0;
|
||||
} else if (op == OP_DEL) {
|
||||
} else if (op == OP_DEL || op == OP_REMOVE) {
|
||||
mark_del = talloc_zero_array(NULL, bool, num_items + 1);
|
||||
}
|
||||
|
||||
@@ -3199,8 +3369,8 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
|
||||
while (param.len > 0) {
|
||||
int r = 0;
|
||||
if (op == OP_DEL)
|
||||
r = parse_obj_settings_del(log, name, ¶m, dst, mark_del);
|
||||
if (op == OP_DEL || op == OP_REMOVE)
|
||||
r = parse_obj_settings_del(log, name, ¶m, op, dst, mark_del);
|
||||
if (r == 0) {
|
||||
r = parse_obj_settings(log, name, op, ¶m, ol, dst ? &res : NULL);
|
||||
}
|
||||
@@ -3224,6 +3394,11 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
}
|
||||
|
||||
if (op != OP_NONE && res && res[0].name && res[1].name) {
|
||||
if (op == OP_APPEND) {
|
||||
mp_err(log, "Option %.*s: -append takes only 1 filter (no ',').\n",
|
||||
BSTR_P(name));
|
||||
return M_OPT_INVALID;
|
||||
}
|
||||
mp_warn(log, "Passing more than 1 argument to %.*s is deprecated!\n",
|
||||
BSTR_P(name));
|
||||
}
|
||||
@@ -3245,7 +3420,7 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
}
|
||||
}
|
||||
talloc_free(res);
|
||||
} else if (op == OP_ADD) {
|
||||
} else if (op == OP_ADD || op == OP_APPEND) {
|
||||
for (int n = 0; res && res[n].name; n++) {
|
||||
int label = obj_settings_list_find_by_label0(list, res[n].label);
|
||||
if (label < 0) {
|
||||
@@ -3282,7 +3457,7 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
}
|
||||
}
|
||||
talloc_free(res);
|
||||
} else if (op == OP_DEL) {
|
||||
} else if (op == OP_DEL || op == OP_REMOVE) {
|
||||
for (int n = num_items - 1; n >= 0; n--) {
|
||||
if (mark_del[n])
|
||||
obj_settings_list_del_at(&list, n);
|
||||
@@ -3290,7 +3465,8 @@ static int parse_obj_settings_list(struct mp_log *log, const m_option_t *opt,
|
||||
for (int n = 0; res && res[n].name; n++) {
|
||||
int found = obj_settings_find_by_content(list, &res[n]);
|
||||
if (found < 0) {
|
||||
mp_warn(log, "Option %.*s: Item not found\n", BSTR_P(name));
|
||||
if (op == OP_DEL)
|
||||
mp_warn(log, "Option %.*s: Item not found\n", BSTR_P(name));
|
||||
} else {
|
||||
obj_settings_list_del_at(&list, found);
|
||||
}
|
||||
@@ -3453,6 +3629,38 @@ static int get_obj_settings_list(const m_option_t *opt, void *ta_parent,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool obj_settings_list_equal(const m_option_t *opt, void *pa, void *pb)
|
||||
{
|
||||
struct m_obj_settings *a = VAL(pa);
|
||||
struct m_obj_settings *b = VAL(pb);
|
||||
|
||||
if (a == b || !a || !b)
|
||||
return a == b;
|
||||
|
||||
for (int n = 0; a[n].name || b[n].name; n++) {
|
||||
if (!a[n].name || !b[n].name)
|
||||
return false;
|
||||
if (!m_obj_settings_equal(&a[n], &b[n]))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool m_obj_settings_equal(struct m_obj_settings *a, struct m_obj_settings *b)
|
||||
{
|
||||
if (!str_equal(NULL, &a->name, &b->name))
|
||||
return false;
|
||||
|
||||
if (!str_equal(NULL, &a->label, &b->label))
|
||||
return false;
|
||||
|
||||
if (a->enabled != b->enabled)
|
||||
return false;
|
||||
|
||||
return str_list_equal(NULL, &a->attribs, &b->attribs);
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_obj_settings_list = {
|
||||
.name = "Object settings list",
|
||||
.size = sizeof(m_obj_settings_t *),
|
||||
@@ -3462,14 +3670,17 @@ const m_option_type_t m_option_type_obj_settings_list = {
|
||||
.free = free_obj_settings_list,
|
||||
.set = set_obj_settings_list,
|
||||
.get = get_obj_settings_list,
|
||||
.equal = obj_settings_list_equal,
|
||||
.actions = (const struct m_option_action[]){
|
||||
{"add"},
|
||||
{"append"},
|
||||
{"clr", M_OPT_TYPE_OPTIONAL_PARAM},
|
||||
{"del"},
|
||||
{"help", M_OPT_TYPE_OPTIONAL_PARAM},
|
||||
{"pre"},
|
||||
{"set"},
|
||||
{"toggle"},
|
||||
{"remove"},
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -3593,6 +3804,11 @@ static int node_get(const m_option_t *opt, void *ta_parent,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool node_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
return equal_mpv_node(&VAL(a), &VAL(b));
|
||||
}
|
||||
|
||||
const m_option_type_t m_option_type_node = {
|
||||
.name = "Complex",
|
||||
.size = sizeof(struct mpv_node),
|
||||
@@ -3603,6 +3819,7 @@ const m_option_type_t m_option_type_node = {
|
||||
.free = free_node,
|
||||
.set = node_set,
|
||||
.get = node_get,
|
||||
.equal = node_equal,
|
||||
};
|
||||
|
||||
// Special-cased by m_config.c.
|
||||
|
||||
+29
-17
@@ -63,6 +63,7 @@ extern const m_option_type_t m_option_type_geometry;
|
||||
extern const m_option_type_t m_option_type_size_box;
|
||||
extern const m_option_type_t m_option_type_channels;
|
||||
extern const m_option_type_t m_option_type_aspect;
|
||||
extern const m_option_type_t m_option_type_obj_settings_list;
|
||||
extern const m_option_type_t m_option_type_node;
|
||||
|
||||
// Used internally by m_config.c
|
||||
@@ -149,6 +150,9 @@ struct m_obj_list {
|
||||
// Allow unknown entries, for which a dummy entry is inserted, and whose
|
||||
// options are skipped and ignored.
|
||||
bool allow_unknown_entries;
|
||||
// Callback to test whether an unknown entry should be allowed. (This can
|
||||
// be useful if adding them as explicit entries is too much work.)
|
||||
bool (*check_unknown_entry)(const char *name);
|
||||
// Allow syntax for disabling entries.
|
||||
bool allow_disable_entries;
|
||||
// This helps with confusing error messages if unknown flag options are used.
|
||||
@@ -177,12 +181,7 @@ typedef struct m_obj_settings {
|
||||
char **attribs;
|
||||
} m_obj_settings_t;
|
||||
|
||||
// A parser to set up a list of objects.
|
||||
/** It creates a NULL terminated array \ref m_obj_settings. The option priv
|
||||
* field (\ref m_option::priv) must point to a \ref m_obj_list_t describing
|
||||
* the available object types.
|
||||
*/
|
||||
extern const m_option_type_t m_option_type_obj_settings_list;
|
||||
bool m_obj_settings_equal(struct m_obj_settings *a, struct m_obj_settings *b);
|
||||
|
||||
struct m_opt_choice_alternatives {
|
||||
char *name;
|
||||
@@ -335,6 +334,17 @@ struct m_option_type {
|
||||
int (*get)(const m_option_t *opt, void *ta_parent, struct mpv_node *dst,
|
||||
void *src);
|
||||
|
||||
// Return whether the values are the same. (There are no "unordered"
|
||||
// results; for example, two floats with the value NaN compare equal. Other
|
||||
// ambiguous floats, such as +0 and -0 compare equal. Some option types may
|
||||
// incorrectly report unequal for values that are equal, such as sets (if
|
||||
// the element order is different, which incorrectly matters), but values
|
||||
// duplicated with m_option_copy() always return as equal. Empty strings
|
||||
// and NULL strings are equal. Ambiguous unicode representations compare
|
||||
// unequal.)
|
||||
// If not set, values are always considered equal (=> not really optional).
|
||||
bool (*equal)(const m_option_t *opt, void *a, void *b);
|
||||
|
||||
// Optional: list of suffixes, terminated with a {0} entry. An empty list
|
||||
// behaves like the list being NULL.
|
||||
const struct m_option_action *actions;
|
||||
@@ -386,12 +396,6 @@ char *format_file_size(int64_t size);
|
||||
// The option is forbidden in config files.
|
||||
#define M_OPT_NOCFG (1 << 2)
|
||||
|
||||
// Can not be freely changed at runtime (normally, all options can be changed,
|
||||
// even if the settings don't get effective immediately). Note that an option
|
||||
// might still change even if this is set, e.g. via properties or per-file
|
||||
// options.
|
||||
#define M_OPT_FIXED (1 << 3)
|
||||
|
||||
// The option should be set during command line pre-parsing
|
||||
#define M_OPT_PRE_PARSE (1 << 4)
|
||||
|
||||
@@ -418,7 +422,9 @@ char *format_file_size(int64_t size);
|
||||
#define UPDATE_VOL (1 << 17) // softvol related options
|
||||
#define UPDATE_LAVFI_COMPLEX (1 << 18) // --lavfi-complex
|
||||
#define UPDATE_VO_RESIZE (1 << 19) // --android-surface-size
|
||||
#define UPDATE_OPT_LAST (1 << 19)
|
||||
#define UPDATE_HWDEC (1 << 20) // --hwdec
|
||||
#define UPDATE_DVB_PROG (1 << 21) // some --dvbin-...
|
||||
#define UPDATE_OPT_LAST (1 << 21)
|
||||
|
||||
// All bits between _FIRST and _LAST (inclusive)
|
||||
#define UPDATE_OPTS_MASK \
|
||||
@@ -482,9 +488,6 @@ char *format_file_size(int64_t size);
|
||||
|
||||
char *m_option_strerror(int code);
|
||||
|
||||
// Find the option matching the given name in the list.
|
||||
const m_option_t *m_option_list_find(const m_option_t *list, const char *name);
|
||||
|
||||
// Helper to parse options, see \ref m_option_type::parse.
|
||||
static inline int m_option_parse(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param, void *dst)
|
||||
@@ -547,6 +550,15 @@ static inline int m_option_get_node(const m_option_t *opt, void *ta_parent,
|
||||
return M_OPT_UNKNOWN;
|
||||
}
|
||||
|
||||
static inline bool m_option_equal(const m_option_t *opt, void *a, void *b)
|
||||
{
|
||||
// Handle trivial equivalence.
|
||||
// If not implemented, assume this type has no actual values => always equal.
|
||||
if (a == b || !opt->type->equal)
|
||||
return true;
|
||||
return opt->type->equal(opt, a, b);
|
||||
}
|
||||
|
||||
int m_option_required_params(const m_option_t *opt);
|
||||
|
||||
extern const char m_option_path_separator;
|
||||
@@ -710,7 +722,7 @@ extern const char m_option_path_separator;
|
||||
|
||||
#define OPT_PRINT(optname, fn) \
|
||||
{.name = optname, \
|
||||
.flags = M_OPT_FIXED | M_OPT_NOCFG | M_OPT_PRE_PARSE | M_OPT_NOPROP, \
|
||||
.flags = M_OPT_NOCFG | M_OPT_PRE_PARSE | M_OPT_NOPROP, \
|
||||
.type = &m_option_type_print_fn, \
|
||||
.priv = MP_EXPECT_TYPE(m_opt_print_fn, fn), \
|
||||
.offset = -1}
|
||||
|
||||
+50
-33
@@ -106,10 +106,6 @@ static const struct m_sub_options screenshot_conf = {
|
||||
|
||||
static const m_option_t mp_vo_opt_list[] = {
|
||||
OPT_SETTINGSLIST("vo", video_driver_list, 0, &vo_obj_list, ),
|
||||
OPT_SUBSTRUCT("sws", sws_opts, sws_conf, 0),
|
||||
#if HAVE_ZIMG
|
||||
OPT_SUBSTRUCT("zimg", zimg_opts, zimg_conf, 0),
|
||||
#endif
|
||||
OPT_FLAG("taskbar-progress", taskbar_progress, 0),
|
||||
OPT_FLAG("snap-window", snap_window, 0),
|
||||
OPT_FLAG("ontop", ontop, 0),
|
||||
@@ -123,6 +119,8 @@ static const m_option_t mp_vo_opt_list[] = {
|
||||
OPT_SIZE_BOX("autofit-larger", autofit_larger, 0),
|
||||
OPT_SIZE_BOX("autofit-smaller", autofit_smaller, 0),
|
||||
OPT_DOUBLE("window-scale", window_scale, CONF_RANGE, .min = 0.001, .max = 100),
|
||||
OPT_FLAG("window-minimized", window_minimized, 0),
|
||||
OPT_FLAG("window-maximized", window_maximized, 0),
|
||||
OPT_FLAG("force-window-position", force_window_position, 0),
|
||||
OPT_STRING("x11-name", winname, 0),
|
||||
OPT_FLOATRANGE("monitoraspect", force_monitor_aspect, 0, 0.0, 9.0),
|
||||
@@ -147,11 +145,14 @@ static const m_option_t mp_vo_opt_list[] = {
|
||||
({"default", -1})),
|
||||
OPT_CHOICE_OR_INT("fs-screen", fsscreen_id, 0, 0, 32,
|
||||
({"all", -2}, {"current", -1})),
|
||||
OPT_CHOICE_OR_INT("cursor-autohide", cursor_autohide_delay, 0,
|
||||
0, 30000, ({"no", -1}, {"always", -2})),
|
||||
OPT_FLAG("cursor-autohide-fs-only", cursor_autohide_fs, 0),
|
||||
OPT_FLAG("keepaspect", keepaspect, 0),
|
||||
OPT_FLAG("keepaspect-window", keepaspect_window, 0),
|
||||
OPT_FLAG("hidpi-window-scale", hidpi_window_scale, 0),
|
||||
OPT_FLAG("native-fs", native_fs, 0),
|
||||
OPT_DOUBLE("display-fps", override_display_fps, M_OPT_MIN, .min = 0),
|
||||
OPT_DOUBLE("override-display-fps", override_display_fps, M_OPT_MIN, .min = 0),
|
||||
OPT_DOUBLERANGE("video-timing-offset", timing_offset, 0, 0.0, 1.0),
|
||||
#if HAVE_X11
|
||||
OPT_CHOICE("x11-netwm", x11_netwm, 0,
|
||||
@@ -178,6 +179,7 @@ const struct m_sub_options vo_sub_opts = {
|
||||
.screen_id = -1,
|
||||
.fsscreen_id = -1,
|
||||
.panscan = 0.0f,
|
||||
.cursor_autohide_delay = 1000,
|
||||
.keepaspect = 1,
|
||||
.keepaspect_window = 1,
|
||||
.hidpi_window_scale = 1,
|
||||
@@ -322,37 +324,40 @@ const struct m_sub_options filter_conf = {
|
||||
#undef OPT_BASE_STRUCT
|
||||
#define OPT_BASE_STRUCT struct MPOpts
|
||||
|
||||
const m_option_t mp_opts[] = {
|
||||
static const m_option_t mp_opts[] = {
|
||||
// handled in command line pre-parser (parse_commandline.c)
|
||||
{"v", &m_option_type_dummy_flag, M_OPT_FIXED | CONF_NOCFG | M_OPT_NOPROP,
|
||||
{"v", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_NOPROP,
|
||||
.offset = -1},
|
||||
{"playlist", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_MIN | M_OPT_FIXED | M_OPT_FILE,
|
||||
{"playlist", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_MIN | M_OPT_FILE,
|
||||
.min = 1, .offset = -1},
|
||||
{"{", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_FIXED | M_OPT_NOPROP,
|
||||
{"{", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_NOPROP,
|
||||
.offset = -1},
|
||||
{"}", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_FIXED | M_OPT_NOPROP,
|
||||
{"}", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_NOPROP,
|
||||
.offset = -1},
|
||||
|
||||
// handled in m_config.c
|
||||
{ "include", CONF_TYPE_STRING, M_OPT_FILE, .offset = -1},
|
||||
{ "profile", CONF_TYPE_STRING_LIST, 0, .offset = -1},
|
||||
{ "show-profile", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_FIXED | M_OPT_NOPROP,
|
||||
.offset = -1},
|
||||
{ "list-options", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_FIXED |
|
||||
M_OPT_NOPROP, .offset = -1},
|
||||
OPT_FLAG("list-properties", property_print_help,
|
||||
CONF_NOCFG | M_OPT_FIXED | M_OPT_NOPROP),
|
||||
{ "help", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_FIXED | M_OPT_NOPROP |
|
||||
M_OPT_OPTIONAL_PARAM, .offset = -1},
|
||||
{ "h", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_FIXED | M_OPT_NOPROP |
|
||||
M_OPT_OPTIONAL_PARAM, .offset = -1},
|
||||
{ "show-profile", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_NOPROP |
|
||||
M_OPT_OPTIONAL_PARAM, .offset = -1},
|
||||
{ "list-options", &m_option_type_dummy_flag, CONF_NOCFG | M_OPT_NOPROP,
|
||||
.offset = -1},
|
||||
OPT_FLAG("list-properties", property_print_help, CONF_NOCFG | M_OPT_NOPROP),
|
||||
{ "help", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_NOPROP | M_OPT_OPTIONAL_PARAM,
|
||||
.offset = -1},
|
||||
{ "h", CONF_TYPE_STRING, CONF_NOCFG | M_OPT_NOPROP | M_OPT_OPTIONAL_PARAM,
|
||||
.offset = -1},
|
||||
|
||||
OPT_PRINT("list-protocols", stream_print_proto_list),
|
||||
OPT_PRINT("version", print_version),
|
||||
OPT_PRINT("V", print_version),
|
||||
|
||||
#if HAVE_TESTS
|
||||
OPT_STRING("unittest", test_mode, CONF_NOCFG | M_OPT_NOPROP),
|
||||
#endif
|
||||
|
||||
OPT_CHOICE("player-operation-mode", operation_mode,
|
||||
M_OPT_FIXED | M_OPT_PRE_PARSE | M_OPT_NOPROP,
|
||||
M_OPT_PRE_PARSE | M_OPT_NOPROP,
|
||||
({"cplayer", 0}, {"pseudo-gui", 1})),
|
||||
|
||||
OPT_FLAG("shuffle", shuffle, 0),
|
||||
@@ -378,13 +383,13 @@ const m_option_t mp_opts[] = {
|
||||
{"belownormal", BELOW_NORMAL_PRIORITY_CLASS},
|
||||
{"idle", IDLE_PRIORITY_CLASS})),
|
||||
#endif
|
||||
OPT_FLAG("config", load_config, M_OPT_FIXED | CONF_PRE_PARSE),
|
||||
OPT_FLAG("config", load_config, CONF_PRE_PARSE),
|
||||
OPT_STRING("config-dir", force_configdir,
|
||||
M_OPT_FIXED | CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE),
|
||||
CONF_NOCFG | CONF_PRE_PARSE | M_OPT_FILE),
|
||||
OPT_STRINGLIST("reset-on-next-file", reset_options, 0),
|
||||
|
||||
#if HAVE_LUA || HAVE_JAVASCRIPT
|
||||
OPT_PATHLIST("scripts", script_files, M_OPT_FIXED | M_OPT_FILE),
|
||||
OPT_PATHLIST("scripts", script_files, M_OPT_FILE),
|
||||
OPT_CLI_ALIAS("script", "scripts-append"),
|
||||
OPT_KEYVALUELIST("script-opts", script_opts, 0),
|
||||
OPT_FLAG("load-scripts", auto_load_scripts, 0),
|
||||
@@ -395,6 +400,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_STRING("ytdl-format", lua_ytdl_format, 0),
|
||||
OPT_KEYVALUELIST("ytdl-raw-options", lua_ytdl_raw_options, 0),
|
||||
OPT_FLAG("load-stats-overlay", lua_load_stats, UPDATE_BUILTIN_SCRIPTS),
|
||||
OPT_FLAG("load-osd-console", lua_load_console, UPDATE_BUILTIN_SCRIPTS),
|
||||
#endif
|
||||
|
||||
// ------------------------- stream options --------------------
|
||||
@@ -588,9 +594,6 @@ const m_option_t mp_opts[] = {
|
||||
OPT_CHOICE_OR_INT("video-rotate", video_rotate, UPDATE_IMGPAR, 0, 359,
|
||||
({"no", -1})),
|
||||
|
||||
OPT_CHOICE_OR_INT("cursor-autohide", cursor_autohide_delay, 0,
|
||||
0, 30000, ({"no", -1}, {"always", -2})),
|
||||
OPT_FLAG("cursor-autohide-fs-only", cursor_autohide_fs, 0),
|
||||
OPT_FLAG("stop-screensaver", stop_screensaver, UPDATE_SCREENSAVER),
|
||||
|
||||
OPT_SUBSTRUCT("", video_equalizer, mp_csp_equalizer_conf, 0),
|
||||
@@ -632,6 +635,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_ALIAS("loop", "loop-file"),
|
||||
|
||||
OPT_FLAG("resume-playback", position_resume, 0),
|
||||
OPT_FLAG("resume-playback-check-mtime", position_check_mtime, 0),
|
||||
OPT_FLAG("save-position-on-quit", position_save_on_quit, 0),
|
||||
OPT_FLAG("write-filename-in-watch-later-config", write_filename_in_watch_later_config, 0),
|
||||
OPT_FLAG("ignore-path-in-watch-later-config", ignore_path_in_watch_later_config, 0),
|
||||
@@ -645,7 +649,6 @@ const m_option_t mp_opts[] = {
|
||||
|
||||
OPT_STRING("chapters-file", chapter_file, M_OPT_FILE),
|
||||
|
||||
OPT_FLAG("load-unsafe-playlists", load_unsafe_playlists, 0),
|
||||
OPT_FLAG("merge-files", merge_files, 0),
|
||||
|
||||
// a-v sync stuff:
|
||||
@@ -698,8 +701,9 @@ const m_option_t mp_opts[] = {
|
||||
|
||||
OPT_FLAG("input-terminal", consolecontrols, UPDATE_TERM),
|
||||
|
||||
OPT_STRING("input-file", input_file, M_OPT_FILE | UPDATE_INPUT),
|
||||
OPT_STRING("input-ipc-server", ipc_path, M_OPT_FILE | UPDATE_INPUT),
|
||||
OPT_STRING("input-file", input_file, M_OPT_FILE,
|
||||
.deprecation_message = "use --input-ipc-server"),
|
||||
OPT_STRING("input-ipc-server", ipc_path, M_OPT_FILE),
|
||||
|
||||
OPT_SUBSTRUCT("screenshot", screenshot_image_opts, screenshot_conf, 0),
|
||||
OPT_STRING("screenshot-template", screenshot_template, 0),
|
||||
@@ -715,6 +719,7 @@ const m_option_t mp_opts[] = {
|
||||
OPT_SUBSTRUCT("", vo, vo_sub_opts, 0),
|
||||
OPT_SUBSTRUCT("", demux_opts, demux_conf, 0),
|
||||
OPT_SUBSTRUCT("", demux_cache_opts, demux_cache_conf, 0),
|
||||
OPT_SUBSTRUCT("", stream_opts, stream_conf, 0),
|
||||
|
||||
OPT_SUBSTRUCT("", gl_video_opts, gl_video_conf, 0),
|
||||
OPT_SUBSTRUCT("", spirv_opts, spirv_conf, 0),
|
||||
@@ -768,6 +773,12 @@ const m_option_t mp_opts[] = {
|
||||
OPT_SUBSTRUCT("vaapi", vaapi_opts, vaapi_conf, 0),
|
||||
#endif
|
||||
|
||||
OPT_SUBSTRUCT("sws", sws_opts, sws_conf, 0),
|
||||
|
||||
#if HAVE_ZIMG
|
||||
OPT_SUBSTRUCT("zimg", zimg_opts, zimg_conf, 0),
|
||||
#endif
|
||||
|
||||
OPT_SUBSTRUCT("", encode_opts, encode_config, 0),
|
||||
|
||||
OPT_REMOVED("a52drc", "use --ad-lavc-ac3drc=level"),
|
||||
@@ -848,7 +859,6 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REPLACED("input-unix-socket", "input-ipc-server"),
|
||||
OPT_REPLACED("softvol-max", "volume-max"),
|
||||
OPT_REMOVED("bluray-angle", "this didn't do anything for a few releases"),
|
||||
OPT_REPLACED("playlist-pos", "playlist-start"),
|
||||
OPT_REPLACED("sub-text-font", "sub-font"),
|
||||
OPT_REPLACED("sub-text-font-size", "sub-font-size"),
|
||||
OPT_REPLACED("sub-text-color", "sub-color"),
|
||||
@@ -884,11 +894,12 @@ const m_option_t mp_opts[] = {
|
||||
OPT_REMOVED("video-stereo-mode", "removed, try --vf=stereo3d"),
|
||||
OPT_REMOVED("chapter", "use '--start=#123' '--end=#124' (for chapter 123)"),
|
||||
OPT_REPLACED("video-aspect", "video-aspect-override"),
|
||||
OPT_REPLACED("display-fps", "override-display-fps"),
|
||||
|
||||
{0}
|
||||
};
|
||||
|
||||
const struct MPOpts mp_default_opts = {
|
||||
static const struct MPOpts mp_default_opts = {
|
||||
.use_terminal = 1,
|
||||
.msg_color = 1,
|
||||
.audio_decoders = NULL,
|
||||
@@ -899,7 +910,6 @@ const struct MPOpts mp_default_opts = {
|
||||
.gapless_audio = -1,
|
||||
.wintitle = "${?media-title:${media-title}}${!media-title:No file} - mpv",
|
||||
.stop_screensaver = 1,
|
||||
.cursor_autohide_delay = 1000,
|
||||
.video_osd = 1,
|
||||
.osd_level = 1,
|
||||
.osd_on_seek = 1,
|
||||
@@ -910,6 +920,7 @@ const struct MPOpts mp_default_opts = {
|
||||
.lua_ytdl_format = NULL,
|
||||
.lua_ytdl_raw_options = NULL,
|
||||
.lua_load_stats = 1,
|
||||
.lua_load_console = 1,
|
||||
#endif
|
||||
.auto_load_scripts = 1,
|
||||
.loop_times = 1,
|
||||
@@ -983,4 +994,10 @@ const struct MPOpts mp_default_opts = {
|
||||
.cuda_device = -1,
|
||||
};
|
||||
|
||||
const struct m_sub_options mp_opt_root = {
|
||||
.opts = mp_opts,
|
||||
.size = sizeof(struct MPOpts),
|
||||
.defaults = &mp_default_opts,
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_CFG_MPLAYER_H */
|
||||
+13
-8
@@ -17,6 +17,8 @@ typedef struct mp_vo_opts {
|
||||
int border;
|
||||
int fit_border;
|
||||
int all_workspaces;
|
||||
int window_minimized;
|
||||
int window_maximized;
|
||||
|
||||
int screen_id;
|
||||
int fsscreen_id;
|
||||
@@ -39,6 +41,9 @@ typedef struct mp_vo_opts {
|
||||
struct m_geometry autofit_smaller;
|
||||
double window_scale;
|
||||
|
||||
int cursor_autohide_delay;
|
||||
int cursor_autohide_fs;
|
||||
|
||||
int keepaspect;
|
||||
int keepaspect_window;
|
||||
int hidpi_window_scale;
|
||||
@@ -55,9 +60,6 @@ typedef struct mp_vo_opts {
|
||||
double override_display_fps;
|
||||
double timing_offset;
|
||||
|
||||
struct sws_opts *sws_opts;
|
||||
struct zimg_opts *zimg_opts;
|
||||
|
||||
// vo_drm
|
||||
struct drm_opts *drm_opts;
|
||||
|
||||
@@ -126,6 +128,7 @@ typedef struct MPOpts {
|
||||
int msg_time;
|
||||
char *log_file;
|
||||
|
||||
char *test_mode;
|
||||
int operation_mode;
|
||||
|
||||
char **reset_options;
|
||||
@@ -136,6 +139,7 @@ typedef struct MPOpts {
|
||||
char *lua_ytdl_format;
|
||||
char **lua_ytdl_raw_options;
|
||||
int lua_load_stats;
|
||||
int lua_load_console;
|
||||
|
||||
int auto_load_scripts;
|
||||
|
||||
@@ -162,8 +166,6 @@ typedef struct MPOpts {
|
||||
struct mp_csp_equalizer_opts *video_equalizer;
|
||||
|
||||
int stop_screensaver;
|
||||
int cursor_autohide_delay;
|
||||
int cursor_autohide_fs;
|
||||
|
||||
int video_rotate;
|
||||
|
||||
@@ -192,7 +194,6 @@ typedef struct MPOpts {
|
||||
int chapter_merge_threshold;
|
||||
double chapter_seek_threshold;
|
||||
char *chapter_file;
|
||||
int load_unsafe_playlists;
|
||||
int merge_files;
|
||||
int quiet;
|
||||
int load_config;
|
||||
@@ -234,6 +235,7 @@ typedef struct MPOpts {
|
||||
double ab_loop[2];
|
||||
double step_sec;
|
||||
int position_resume;
|
||||
int position_check_mtime;
|
||||
int position_save_on_quit;
|
||||
int write_filename_in_watch_later_config;
|
||||
int ignore_path_in_watch_later_config;
|
||||
@@ -310,6 +312,7 @@ typedef struct MPOpts {
|
||||
|
||||
struct demux_opts *demux_opts;
|
||||
struct demux_cache_opts *demux_cache_opts;
|
||||
struct stream_opts *stream_opts;
|
||||
|
||||
struct vd_lavc_params *vd_lavc_params;
|
||||
struct ad_lavc_params *ad_lavc_params;
|
||||
@@ -339,6 +342,8 @@ typedef struct MPOpts {
|
||||
struct wayland_opts *wayland_opts;
|
||||
struct dvd_opts *dvd_opts;
|
||||
struct vaapi_opts *vaapi_opts;
|
||||
struct sws_opts *sws_opts;
|
||||
struct zimg_opts *zimg_opts;
|
||||
|
||||
int cuda_device;
|
||||
} MPOpts;
|
||||
@@ -353,13 +358,13 @@ struct filter_opts {
|
||||
int deinterlace;
|
||||
};
|
||||
|
||||
extern const m_option_t mp_opts[];
|
||||
extern const struct MPOpts mp_default_opts;
|
||||
extern const struct m_sub_options vo_sub_opts;
|
||||
extern const struct m_sub_options dvd_conf;
|
||||
extern const struct m_sub_options mp_subtitle_sub_opts;
|
||||
extern const struct m_sub_options mp_osd_render_sub_opts;
|
||||
extern const struct m_sub_options filter_conf;
|
||||
extern const struct m_sub_options resample_conf;
|
||||
extern const struct m_sub_options stream_conf;
|
||||
extern const struct m_sub_options mp_opt_root;
|
||||
|
||||
#endif
|
||||
@@ -1,378 +0,0 @@
|
||||
//
|
||||
// HIDRemote.h
|
||||
// HIDRemote V1.2
|
||||
//
|
||||
// Created by Felix Schwarz on 06.04.07.
|
||||
// Copyright 2007-2011 IOSPIRIT GmbH. All rights reserved.
|
||||
//
|
||||
// The latest version of this class is available at
|
||||
// http://www.iospirit.com/developers/hidremote/
|
||||
//
|
||||
// ** LICENSE *************************************************************************
|
||||
//
|
||||
// Copyright (c) 2007-2011 IOSPIRIT GmbH (http://www.iospirit.com/)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
// are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list
|
||||
// of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this
|
||||
// list of conditions and the following disclaimer in the documentation and/or other
|
||||
// materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of IOSPIRIT GmbH nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior
|
||||
// written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
|
||||
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
||||
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
// DAMAGE.
|
||||
//
|
||||
// ************************************************************************************
|
||||
|
||||
|
||||
// ************************************************************************************
|
||||
// ********************************** DOCUMENTATION ***********************************
|
||||
// ************************************************************************************
|
||||
//
|
||||
// - a reference is available at http://www.iospirit.com/developers/hidremote/reference/
|
||||
// - for a guide, please see http://www.iospirit.com/developers/hidremote/guide/
|
||||
//
|
||||
// ************************************************************************************
|
||||
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <mach/mach.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/IOCFPlugIn.h>
|
||||
#include <IOKit/IOMessage.h>
|
||||
#include <IOKit/hid/IOHIDKeys.h>
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
#include <IOKit/hid/IOHIDUsageTables.h>
|
||||
#include <IOKit/hidsystem/IOHIDLib.h>
|
||||
#include <IOKit/hidsystem/IOHIDParameter.h>
|
||||
#include <IOKit/hidsystem/IOHIDShared.h>
|
||||
|
||||
#pragma mark -- Enums / Codes --
|
||||
|
||||
typedef enum
|
||||
{
|
||||
kHIDRemoteModeNone = 0L,
|
||||
kHIDRemoteModeShared, // Share the remote with others - let's you listen to the remote control events as long as no one has an exclusive lock on it
|
||||
// (RECOMMENDED ONLY FOR SPECIAL PURPOSES)
|
||||
|
||||
kHIDRemoteModeExclusive, // Try to acquire an exclusive lock on the remote (NOT RECOMMENDED)
|
||||
|
||||
kHIDRemoteModeExclusiveAuto // Try to acquire an exclusive lock on the remote whenever the application has focus. Temporarily release control over the
|
||||
// remote when another application has focus (RECOMMENDED)
|
||||
} HIDRemoteMode;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
/* A code reserved for "no button" (needed for tracking) */
|
||||
kHIDRemoteButtonCodeNone = 0L,
|
||||
|
||||
/* Standard codes - available for white plastic and aluminum remote */
|
||||
kHIDRemoteButtonCodeUp,
|
||||
kHIDRemoteButtonCodeDown,
|
||||
kHIDRemoteButtonCodeLeft,
|
||||
kHIDRemoteButtonCodeRight,
|
||||
kHIDRemoteButtonCodeCenter,
|
||||
kHIDRemoteButtonCodeMenu,
|
||||
|
||||
/* Extra codes - Only available for the new aluminum version of the remote */
|
||||
kHIDRemoteButtonCodePlay,
|
||||
|
||||
/* Masks */
|
||||
kHIDRemoteButtonCodeCodeMask = 0xFFL,
|
||||
kHIDRemoteButtonCodeHoldMask = (1L << 16L),
|
||||
kHIDRemoteButtonCodeSpecialMask = (1L << 17L),
|
||||
kHIDRemoteButtonCodeAluminumMask = (1L << 21L), // PRIVATE - only used internally
|
||||
|
||||
/* Hold button standard codes - available for white plastic and aluminum remote */
|
||||
kHIDRemoteButtonCodeUpHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeUp),
|
||||
kHIDRemoteButtonCodeDownHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeDown),
|
||||
kHIDRemoteButtonCodeLeftHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeLeft),
|
||||
kHIDRemoteButtonCodeRightHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeRight),
|
||||
kHIDRemoteButtonCodeCenterHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeCenter),
|
||||
kHIDRemoteButtonCodeMenuHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodeMenu),
|
||||
|
||||
/* Hold button extra codes - Only available for aluminum version of the remote */
|
||||
kHIDRemoteButtonCodePlayHold = (kHIDRemoteButtonCodeHoldMask|kHIDRemoteButtonCodePlay),
|
||||
|
||||
/* DEPRECATED codes - compatibility with HIDRemote 1.0 */
|
||||
kHIDRemoteButtonCodePlus = kHIDRemoteButtonCodeUp,
|
||||
kHIDRemoteButtonCodePlusHold = kHIDRemoteButtonCodeUpHold,
|
||||
kHIDRemoteButtonCodeMinus = kHIDRemoteButtonCodeDown,
|
||||
kHIDRemoteButtonCodeMinusHold = kHIDRemoteButtonCodeDownHold,
|
||||
kHIDRemoteButtonCodePlayPause = kHIDRemoteButtonCodeCenter,
|
||||
kHIDRemoteButtonCodePlayPauseHold = kHIDRemoteButtonCodeCenterHold,
|
||||
|
||||
/* Special purpose codes */
|
||||
kHIDRemoteButtonCodeIDChanged = (kHIDRemoteButtonCodeSpecialMask|(1L << 18L)), // (the ID of the connected remote has changed, you can safely ignore this)
|
||||
#ifdef _HIDREMOTE_EXTENSIONS
|
||||
#define _HIDREMOTE_EXTENSIONS_SECTION 1
|
||||
#include "HIDRemoteAdditions.h"
|
||||
#undef _HIDREMOTE_EXTENSIONS_SECTION
|
||||
#endif /* _HIDREMOTE_EXTENSIONS */
|
||||
} HIDRemoteButtonCode;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
kHIDRemoteModelUndetermined = 0L, // Assume a white plastic remote
|
||||
kHIDRemoteModelWhitePlastic, // Signal *likely* to be coming from a white plastic remote
|
||||
kHIDRemoteModelAluminum // Signal *definitely* coming from an aluminum remote
|
||||
} HIDRemoteModel;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
kHIDRemoteAluminumRemoteSupportLevelNone = 0L, // This system has no support for the Aluminum Remote at all
|
||||
kHIDRemoteAluminumRemoteSupportLevelEmulation, // This system possibly has support for the Aluminum Remote (via emulation)
|
||||
kHIDRemoteAluminumRemoteSupportLevelNative // This system has native support for the Aluminum Remote
|
||||
} HIDRemoteAluminumRemoteSupportLevel;
|
||||
|
||||
@class HIDRemote;
|
||||
|
||||
#pragma mark -- Delegate protocol (mandatory) --
|
||||
@protocol HIDRemoteDelegate
|
||||
|
||||
// Notification of button events
|
||||
- (void)hidRemote:(HIDRemote *)hidRemote // The instance of HIDRemote sending this
|
||||
eventWithButton:(HIDRemoteButtonCode)buttonCode // Event for the button specified by code
|
||||
isPressed:(BOOL)isPressed // The button was pressed (YES) / released (NO)
|
||||
fromHardwareWithAttributes:(NSMutableDictionary *)attributes; // Information on the device this event comes from
|
||||
|
||||
@optional
|
||||
|
||||
// Notification of ID changes
|
||||
- (void)hidRemote:(HIDRemote *)hidRemote // Invoked when the user switched to a remote control with a different ID
|
||||
remoteIDChangedOldID:(SInt32)old
|
||||
newID:(SInt32)newID
|
||||
forHardwareWithAttributes:(NSMutableDictionary *)attributes;
|
||||
|
||||
// Notification about hardware additions/removals
|
||||
- (void)hidRemote:(HIDRemote *)hidRemote // Invoked when new hardware was found / added to HIDRemote's pool
|
||||
foundNewHardwareWithAttributes:(NSMutableDictionary *)attributes;
|
||||
|
||||
- (void)hidRemote:(HIDRemote *)hidRemote // Invoked when initialization of new hardware as requested failed
|
||||
failedNewHardwareWithError:(NSError *)error;
|
||||
|
||||
- (void)hidRemote:(HIDRemote *)hidRemote // Invoked when hardware was removed from HIDRemote's pool
|
||||
releasedHardwareWithAttributes:(NSMutableDictionary *)attributes;
|
||||
|
||||
// ### WARNING: Unless you know VERY PRECISELY what you are doing, do not implement any of the delegate methods below. ###
|
||||
|
||||
// Matching of newly found receiver hardware
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote // Invoked when new hardware is inspected
|
||||
inspectNewHardwareWithService:(io_service_t)service //
|
||||
prematchResult:(BOOL)prematchResult; // Return YES if HIDRemote should go on with this hardware and try
|
||||
// to use it, or NO if it should not be pursued further.
|
||||
|
||||
// Exlusive lock lending
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote
|
||||
lendExclusiveLockToApplicationWithInfo:(NSDictionary *)applicationInfo;
|
||||
|
||||
- (void)hidRemote:(HIDRemote *)hidRemote
|
||||
exclusiveLockReleasedByApplicationWithInfo:(NSDictionary *)applicationInfo;
|
||||
|
||||
- (BOOL)hidRemote:(HIDRemote *)hidRemote
|
||||
shouldRetryExclusiveLockWithInfo:(NSDictionary *)applicationInfo;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark -- Actual header file for class --
|
||||
|
||||
@interface HIDRemote : NSObject
|
||||
{
|
||||
// IOMasterPort
|
||||
mach_port_t _masterPort;
|
||||
|
||||
// Notification ports
|
||||
IONotificationPortRef _notifyPort;
|
||||
CFRunLoopSourceRef _notifyRLSource;
|
||||
|
||||
// Matching iterator
|
||||
io_iterator_t _matchingServicesIterator;
|
||||
|
||||
// SecureInput notification
|
||||
io_object_t _secureInputNotification;
|
||||
|
||||
// Service attributes
|
||||
NSMutableDictionary *_serviceAttribMap;
|
||||
|
||||
// Mode
|
||||
HIDRemoteMode _mode;
|
||||
BOOL _autoRecover;
|
||||
NSTimer *_autoRecoveryTimer;
|
||||
|
||||
// Delegate
|
||||
NSObject <HIDRemoteDelegate> *_delegate;
|
||||
|
||||
// Last seen ID and remote model
|
||||
SInt32 _lastSeenRemoteID;
|
||||
HIDRemoteModel _lastSeenModel;
|
||||
SInt32 _lastSeenModelRemoteID;
|
||||
|
||||
// Unused button codes
|
||||
NSArray *_unusedButtonCodes;
|
||||
|
||||
// Simulate Plus/Minus Hold
|
||||
BOOL _simulateHoldEvents;
|
||||
|
||||
// SecureEventInput workaround
|
||||
BOOL _secureEventInputWorkAround;
|
||||
UInt64 _lastSecureEventInputPIDSum;
|
||||
uid_t _lastFrontUserSession;
|
||||
|
||||
// Exclusive lock lending
|
||||
BOOL _exclusiveLockLending;
|
||||
BOOL _sendExclusiveResourceReuseNotification;
|
||||
NSNumber *_waitForReturnByPID;
|
||||
NSNumber *_returnToPID;
|
||||
BOOL _isRestarting;
|
||||
|
||||
// Status notifications
|
||||
BOOL _sendStatusNotifications;
|
||||
NSString *_pidString;
|
||||
|
||||
// Status
|
||||
BOOL _applicationIsTerminating;
|
||||
BOOL _isStopping;
|
||||
|
||||
// Thread safety
|
||||
#ifdef HIDREMOTE_THREADSAFETY_HARDENED_NOTIFICATION_HANDLING /* #define HIDREMOTE_THREADSAFETY_HARDENED_NOTIFICATION_HANDLING if you're running your HIDRemote instance on a background thread (requires OS X 10.5 or later) */
|
||||
NSThread *_runOnThread;
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma mark -- PUBLIC: Shared HID Remote --
|
||||
+ (HIDRemote *)sharedHIDRemote;
|
||||
|
||||
#pragma mark -- PUBLIC: System Information --
|
||||
+ (BOOL)isCandelairInstalled;
|
||||
+ (BOOL)isCandelairInstallationRequiredForRemoteMode:(HIDRemoteMode)remoteMode;
|
||||
- (HIDRemoteAluminumRemoteSupportLevel)aluminiumRemoteSystemSupportLevel;
|
||||
|
||||
#pragma mark -- PUBLIC: Interface / API --
|
||||
- (BOOL)startRemoteControl:(HIDRemoteMode)hidRemoteMode;
|
||||
- (void)stopRemoteControl;
|
||||
|
||||
- (BOOL)isStarted;
|
||||
- (HIDRemoteMode)startedInMode;
|
||||
|
||||
- (unsigned)activeRemoteControlCount;
|
||||
|
||||
- (SInt32)lastSeenRemoteControlID;
|
||||
|
||||
- (void)setLastSeenModel:(HIDRemoteModel)aModel;
|
||||
- (HIDRemoteModel)lastSeenModel;
|
||||
|
||||
- (void)setDelegate:(NSObject <HIDRemoteDelegate> *)newDelegate;
|
||||
- (NSObject <HIDRemoteDelegate> *)delegate;
|
||||
|
||||
- (void)setSimulateHoldEvents:(BOOL)newSimulateHoldEvents;
|
||||
- (BOOL)simulateHoldEvents;
|
||||
|
||||
- (void)setUnusedButtonCodes:(NSArray *)newArrayWithUnusedButtonCodesAsNSNumbers;
|
||||
- (NSArray *)unusedButtonCodes;
|
||||
|
||||
#pragma mark -- PUBLIC: Expert APIs --
|
||||
- (void)setEnableSecureEventInputWorkaround:(BOOL)newEnableSecureEventInputWorkaround;
|
||||
- (BOOL)enableSecureEventInputWorkaround;
|
||||
|
||||
- (void)setExclusiveLockLendingEnabled:(BOOL)newExclusiveLockLendingEnabled;
|
||||
- (BOOL)exclusiveLockLendingEnabled;
|
||||
|
||||
- (BOOL)isApplicationTerminating;
|
||||
- (BOOL)isStopping;
|
||||
|
||||
#pragma mark -- PRIVATE: HID Event handling --
|
||||
- (void)_handleButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict;
|
||||
- (void)_sendButtonCode:(HIDRemoteButtonCode)buttonCode isPressed:(BOOL)isPressed hidAttribsDict:(NSMutableDictionary *)hidAttribsDict;
|
||||
- (void)_hidEventFor:(io_service_t)hidDevice from:(IOHIDQueueInterface **)interface withResult:(IOReturn)result;
|
||||
|
||||
#pragma mark -- PRIVATE: Service setup and destruction --
|
||||
- (BOOL)_prematchService:(io_object_t)service;
|
||||
- (HIDRemoteButtonCode)buttonCodeForUsage:(unsigned int)usage usagePage:(unsigned int)usagePage;
|
||||
- (BOOL)_setupService:(io_object_t)service;
|
||||
- (void)_destructService:(io_object_t)service;
|
||||
|
||||
#pragma mark -- PRIVATE: Distributed notifiations handling --
|
||||
- (void)_postStatusWithAction:(NSString *)action;
|
||||
- (void)_handleNotifications:(NSNotification *)notification;
|
||||
- (void)_setSendStatusNotifications:(BOOL)doSend;
|
||||
- (BOOL)_sendStatusNotifications;
|
||||
|
||||
#pragma mark -- PRIVATE: Application becomes active / inactive handling for kHIDRemoteModeExclusiveAuto --
|
||||
- (void)_appStatusChanged:(NSNotification *)notification;
|
||||
- (void)_delayedAutoRecovery:(NSTimer *)aTimer;
|
||||
|
||||
#pragma mark -- PRIVATE: Notification handling --
|
||||
- (void)_serviceMatching:(io_iterator_t)iterator;
|
||||
- (void)_serviceNotificationFor:(io_service_t)service messageType:(natural_t)messageType messageArgument:(void *)messageArgument;
|
||||
- (void)_updateSessionInformation;
|
||||
- (void)_secureInputNotificationFor:(io_service_t)service messageType:(natural_t)messageType messageArgument:(void *)messageArgument;
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -- Information attribute keys --
|
||||
extern NSString *kHIDRemoteManufacturer;
|
||||
extern NSString *kHIDRemoteProduct;
|
||||
extern NSString *kHIDRemoteTransport;
|
||||
|
||||
#pragma mark -- Internal/Expert attribute keys (AKA: don't touch these unless you really, really, REALLY know what you do) --
|
||||
extern NSString *kHIDRemoteCFPluginInterface;
|
||||
extern NSString *kHIDRemoteHIDDeviceInterface;
|
||||
extern NSString *kHIDRemoteCookieButtonCodeLUT;
|
||||
extern NSString *kHIDRemoteHIDQueueInterface;
|
||||
extern NSString *kHIDRemoteServiceNotification;
|
||||
extern NSString *kHIDRemoteCFRunLoopSource;
|
||||
extern NSString *kHIDRemoteLastButtonPressed;
|
||||
extern NSString *kHIDRemoteService;
|
||||
extern NSString *kHIDRemoteSimulateHoldEventsTimer;
|
||||
extern NSString *kHIDRemoteSimulateHoldEventsOriginButtonCode;
|
||||
extern NSString *kHIDRemoteAluminumRemoteSupportLevel;
|
||||
extern NSString *kHIDRemoteAluminumRemoteSupportOnDemand;
|
||||
|
||||
#pragma mark -- Distributed notifications --
|
||||
extern NSString *kHIDRemoteDNHIDRemotePing;
|
||||
extern NSString *kHIDRemoteDNHIDRemoteRetry;
|
||||
extern NSString *kHIDRemoteDNHIDRemoteStatus;
|
||||
|
||||
extern NSString *kHIDRemoteDNHIDRemoteRetryGlobalObject;
|
||||
|
||||
#pragma mark -- Distributed notifications userInfo keys and values --
|
||||
extern NSString *kHIDRemoteDNStatusHIDRemoteVersionKey;
|
||||
extern NSString *kHIDRemoteDNStatusPIDKey;
|
||||
extern NSString *kHIDRemoteDNStatusModeKey;
|
||||
extern NSString *kHIDRemoteDNStatusUnusedButtonCodesKey;
|
||||
extern NSString *kHIDRemoteDNStatusRemoteControlCountKey;
|
||||
extern NSString *kHIDRemoteDNStatusReturnToPIDKey;
|
||||
extern NSString *kHIDRemoteDNStatusActionKey;
|
||||
extern NSString *kHIDRemoteDNStatusActionStart;
|
||||
extern NSString *kHIDRemoteDNStatusActionStop;
|
||||
extern NSString *kHIDRemoteDNStatusActionUpdate;
|
||||
extern NSString *kHIDRemoteDNStatusActionNoNeed;
|
||||
|
||||
#pragma mark -- Driver compatibility flags --
|
||||
typedef enum
|
||||
{
|
||||
kHIDRemoteCompatibilityFlagsStandardHIDRemoteDevice = 1L,
|
||||
} HIDRemoteCompatibilityFlags;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -26,6 +26,7 @@
|
||||
#include <stdatomic.h>
|
||||
typedef _Atomic float mp_atomic_float;
|
||||
typedef _Atomic int64_t mp_atomic_int64;
|
||||
typedef _Atomic uint64_t mp_atomic_uint64;
|
||||
#else
|
||||
|
||||
// Emulate the parts of C11 stdatomic.h needed by mpv.
|
||||
@@ -40,6 +41,7 @@ typedef struct { unsigned long long v; } atomic_ullong;
|
||||
|
||||
typedef struct { float v; } mp_atomic_float;
|
||||
typedef struct { int64_t v; } mp_atomic_int64;
|
||||
typedef struct { uint64_t v; } mp_atomic_uint64;
|
||||
|
||||
#define ATOMIC_VAR_INIT(x) \
|
||||
{.v = (x)}
|
||||
|
||||
@@ -172,6 +172,9 @@ void mp_globfree(mp_glob_t *pglob);
|
||||
#undef fstat
|
||||
#define fstat(...) mp_fstat(__VA_ARGS__)
|
||||
|
||||
#define utime(...) _utime(__VA_ARGS__)
|
||||
#define utimbuf _utimbuf
|
||||
|
||||
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
|
||||
int munmap(void *addr, size_t length);
|
||||
int msync(void *addr, size_t length, int flags);
|
||||
|
||||
Loaded 100 of 240 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user