Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70b991749d | ||
|
|
bc1c024ae0 | ||
|
|
80423e5b55 | ||
|
|
48880d827d | ||
|
|
1ae17074bd | ||
|
|
b316534902 | ||
|
|
dd14d06835 | ||
|
|
1dea80d3fd | ||
|
|
2aa4186a25 | ||
|
|
44f6e79439 | ||
|
|
e2c59b4177 | ||
|
|
3275cd04b7 | ||
|
|
465f48fb0c | ||
|
|
695d850412 | ||
|
|
3d16ab1a31 | ||
|
|
77d42d5532 | ||
|
|
ae0520cafc | ||
|
|
1ea145a9a1 | ||
|
|
65f3c7453d | ||
|
|
3ef0754102 | ||
|
|
e666546910 | ||
|
|
da913998d3 | ||
|
|
90c11fa729 | ||
|
|
00cdda2ae8 | ||
|
|
ee7be62dbc | ||
|
|
ad1ae64251 | ||
|
|
044996e112 | ||
|
|
a500608845 | ||
|
|
7ed6b5f44d | ||
|
|
40832773c1 | ||
|
|
3259494d9e | ||
|
|
4d51660195 | ||
|
|
9f7c271087 | ||
|
|
a870145fb9 | ||
|
|
b8a42cc41c | ||
|
|
6cb3d024c8 | ||
|
|
63bf362d11 | ||
|
|
68292a2780 | ||
|
|
a3f220ba44 | ||
|
|
4b168bcda3 | ||
|
|
b947bfcf1f | ||
|
|
7555e2a42a | ||
|
|
a009b57c77 | ||
|
|
a3ddddff3a | ||
|
|
f23dcfef82 | ||
|
|
cfceaec266 | ||
|
|
8b85b40b2f | ||
|
|
28609cc498 | ||
|
|
ea9f9af76c | ||
|
|
e6c9e33a31 | ||
|
|
7bb3f53cf5 | ||
|
|
57f9de7b53 | ||
|
|
363048077f | ||
|
|
3a9f0f537f | ||
|
|
33e999de82 | ||
|
|
5e0875c9e0 | ||
|
|
f612de1712 | ||
|
|
1c83ded3b0 | ||
|
|
4a65c22c50 | ||
|
|
a58585d5e0 | ||
|
|
7ce41cda05 | ||
|
|
11b9315b3f | ||
|
|
db9048f21a | ||
|
|
d3cef97ad3 | ||
|
|
5b56be0e91 | ||
|
|
bcc921bd2f | ||
|
|
343bf8a9ac | ||
|
|
28650e116a | ||
|
|
9eb399193e | ||
|
|
d26b5daf3e | ||
|
|
1b0129c414 | ||
|
|
119bad4daa | ||
|
|
1b283f6b60 | ||
|
|
4231ce6f5f | ||
|
|
5016a1e4a6 | ||
|
|
8150b8552b | ||
|
|
4419d29bb0 | ||
|
|
04bde06095 | ||
|
|
657ce1b15c | ||
|
|
99700bc52c | ||
|
|
daa125cb45 | ||
|
|
5feee11e43 | ||
|
|
7d08491cd9 | ||
|
|
934f8e1026 | ||
|
|
3b6c4e7be1 | ||
|
|
b721f9d095 | ||
|
|
fcf0b80dc9 | ||
|
|
a6d8e9b7ff | ||
|
|
5fcad696a9 | ||
|
|
9c05be8999 | ||
|
|
f0d0822595 | ||
|
|
0737d842b0 | ||
|
|
49cbc5017c | ||
|
|
eb852dc50c | ||
|
|
5a26150717 | ||
|
|
582f3f7cc0 | ||
|
|
4564a22d13 | ||
|
|
65337c032f | ||
|
|
e2c6919da0 | ||
|
|
1571276d64 | ||
|
|
c4600394d4 | ||
|
|
01de2a9bd5 | ||
|
|
13b8363c47 | ||
|
|
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
@@ -1,26 +0,0 @@
|
||||
### mpv version and platform
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### 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.
|
||||
|
||||
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.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: 'README: DO NOT IGNORE OR DELETE THE ISSUE TEMPLATE'
|
||||
about: 'Chose and fill out one of the following templates!'
|
||||
title: ''
|
||||
labels: priority:ignored-issue-template
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
We ask you to not ignore the issue template. Fill it out as good and correct as
|
||||
possible. Issues that don't adhere to our request 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.
|
||||
|
||||
Please go back and chose the proper issue template. Opening issues with this
|
||||
template will be closed immediately.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: 'Report a Linux Issue'
|
||||
about: 'Create a report for a runtime related Linux Issue'
|
||||
title: ''
|
||||
labels: 'os:linux'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Linux Distribution and Version
|
||||
- Source of the mpv binary
|
||||
- If known which version of mpv introduced the problem
|
||||
- Window Manager and version
|
||||
- GPU driver and version
|
||||
- Possible screenshot or video of visual glitches
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
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.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
name: 'Report a macOS Issue'
|
||||
about: 'Create a report for a runtime related macOS Issue'
|
||||
title: ''
|
||||
labels: 'os:mac'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- macOS Version
|
||||
- Source of the mpv binary or bundle
|
||||
- If known which version of mpv introduced the problem
|
||||
- Possible screenshot or video of visual glitches
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
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.
|
||||
|
||||
In the case of a crash please provide the macOS Crash Report (Backtrace).
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: 'Report a Windows Issue'
|
||||
about: 'Create a report for a runtime related Windows Issue'
|
||||
title: ''
|
||||
labels: 'os:win'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Windows Version
|
||||
- Source of the mpv binary
|
||||
- If known which version of mpv introduced the problem
|
||||
- Possible screenshot or video of visual glitches
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
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.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: 'Report a different Issue'
|
||||
about: 'Create a report for a runtime related Issue'
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### Important Information
|
||||
|
||||
Provide following Information:
|
||||
- mpv version
|
||||
- Platform and Version
|
||||
- Source of the mpv binary
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
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.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
Do not use garbage like "cloud storage", especially not Google Drive.
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: 'Report a build Problem'
|
||||
about: 'Create a report for a build related Issue'
|
||||
title: ''
|
||||
labels: 'core:waf'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
### mpv version and platform versions
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
Describe the reproduction steps as precise as possible. It's very likely that
|
||||
the bug you experience wasn't reproduced by the developer because the workflow
|
||||
differes from your own.
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log file
|
||||
|
||||
Upload build/config.log or ./waf configure output to https://0x0.st/ or attach
|
||||
it to the github issue, and replace this text with a link to it.
|
||||
|
||||
The issue will be closed for ignoring the issue template.
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: 'Request a new Feature'
|
||||
about: 'Create a request for a new feature'
|
||||
title: ''
|
||||
labels: 'meta:feature-request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Before requesting a new feature make sure it hasn't been requested yet.
|
||||
https://github.com/mpv-player/mpv/labels/meta%3Afeature-request
|
||||
|
||||
### Expected behavior of the wanted feature
|
||||
|
||||
### Alternative behavior of the wanted feature
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: 'Ask a Question'
|
||||
about: 'Ask a question about mpv'
|
||||
title: ''
|
||||
labels: 'meta:question'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
Don't ask questions about issues, errors or problems you have and instead open
|
||||
a proper issue with the right template from the previous selection.
|
||||
|
||||
This template is meant for questions about the workings of mpv, to clarify about
|
||||
unspecified behaviour of options or command, or anything else that is not well
|
||||
described and needs clarification.
|
||||
|
||||
Before asking a question make sure it hasn't been asked or answered yet.
|
||||
https://github.com/mpv-player/mpv/labels/meta%3Aquestion
|
||||
@@ -0,0 +1,9 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: mpv irc channel
|
||||
url: https://mpv.io/community/
|
||||
about: Feel free to ask question here irc://irc.freenode.net/mpv
|
||||
- name: mpv irc developer channel
|
||||
url: https://mpv.io/community/
|
||||
about: Ask question related to the development of mpv here
|
||||
irc://irc.freenode.net/mpv-devel
|
||||
+55
-21
@@ -1,16 +1,29 @@
|
||||
language: c
|
||||
|
||||
macbase:
|
||||
- &macOld
|
||||
os: osx
|
||||
compiler: clang
|
||||
env:
|
||||
- HOMEBREW_NO_AUTO_UPDATE=1
|
||||
- HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||
- &macNew
|
||||
<<: *macOld
|
||||
addons:
|
||||
homebrew:
|
||||
packages: ['autoconf', 'automake', 'ffmpeg', 'freetype', 'fribidi',
|
||||
'libass', 'libtool', 'little-cms2', 'luajit', 'nasm',
|
||||
'pkg-config', 'python']
|
||||
update: true
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
osx_image: xcode11
|
||||
compiler: clang
|
||||
- os: osx
|
||||
- <<: *macNew
|
||||
osx_image: xcode11.3
|
||||
- <<: *macNew
|
||||
osx_image: xcode10.1
|
||||
compiler: clang
|
||||
- os: osx
|
||||
- <<: *macOld
|
||||
osx_image: xcode9.2
|
||||
compiler: clang
|
||||
- os: linux
|
||||
compiler: gcc
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh
|
||||
@@ -21,6 +34,10 @@ matrix:
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:i686 CI_SCRIPT=ci/build-mingw64.sh TARGET=i686-w64-mingw32
|
||||
- os: linux
|
||||
env: CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:x86_64 CI_SCRIPT=ci/build-mingw64.sh TARGET=x86_64-w64-mingw32
|
||||
allow_failures:
|
||||
- os: osx
|
||||
osx_image: xcode9.2
|
||||
fast_finish: true
|
||||
|
||||
dist: bionic
|
||||
services:
|
||||
@@ -46,7 +63,36 @@ before_install:
|
||||
- if [ "$TRAVIS_COMPILER" = "clang" ]; then export CXX="clang++"; fi
|
||||
- if [ "$TRAVIS_COMPILER" = "gcc" ]; then export CXX="g++"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then docker pull $CONTAINER; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then "$TRAVIS_BUILD_DIR/ci/get_ffmpeg.sh"; fi
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
if [[ "$TRAVIS_OSX_IMAGE" == "xcode9.2" ]]; then
|
||||
brew update
|
||||
pushd "/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core"
|
||||
git checkout 55e02323812604add9a69bab8730319b9255a697
|
||||
popd
|
||||
brew install autoconf
|
||||
brew install automake
|
||||
brew install pkg-config
|
||||
brew install libtool
|
||||
brew install python
|
||||
brew install freetype
|
||||
brew install fribidi
|
||||
brew install little-cms2
|
||||
brew install luajit
|
||||
brew install libass
|
||||
brew install ffmpeg
|
||||
else
|
||||
brew link --overwrite python
|
||||
fi
|
||||
fi
|
||||
|
||||
before_cache:
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew cleanup -s ; fi
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/Library/Caches/Homebrew
|
||||
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then docker run --env CC --env TARGET -v $TRAVIS_BUILD_DIR:/build $CONTAINER /bin/sh -c "cd /build && $CI_SCRIPT"; fi
|
||||
@@ -57,6 +103,7 @@ after_script: TOOLS/travis-rebuild-website
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
if: fork = false
|
||||
channels:
|
||||
- "irc.freenode.org#mpv-devel"
|
||||
on_success: change
|
||||
@@ -71,16 +118,3 @@ addons:
|
||||
build_command_prepend: "./bootstrap.py && ./waf configure"
|
||||
build_command: "./waf build"
|
||||
branch_pattern: coverity_scan
|
||||
homebrew:
|
||||
packages:
|
||||
- autoconf
|
||||
- automake
|
||||
- freetype
|
||||
- fribidi
|
||||
- libass
|
||||
- libtool
|
||||
- little-cms2
|
||||
- luajit
|
||||
- nasm
|
||||
- pkg-config
|
||||
update: true
|
||||
@@ -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.
|
||||
+6
-2
@@ -30,9 +30,13 @@ section::
|
||||
[myencprofile]
|
||||
vf-add = scale=480:-2
|
||||
ovc = libx264
|
||||
ovcopts-add = preset=medium,tune=fastdecode
|
||||
ovcopts-add = preset=medium
|
||||
ovcopts-add = tune=fastdecode
|
||||
ovcopts-add = crf=23
|
||||
ovcopts-add = maxrate=1500k,bufsize=1000k,rc_init_occupancy=900k,refs=2
|
||||
ovcopts-add = maxrate=1500k
|
||||
ovcopts-add = bufsize=1000k
|
||||
ovcopts-add = rc_init_occupancy=900k
|
||||
ovcopts-add = refs=2
|
||||
ovcopts-add = profile=baseline
|
||||
oac = aac
|
||||
oacopts-add = b=96k
|
||||
|
||||
@@ -24,6 +24,72 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.32.0 ---
|
||||
- change behavior when using legacy option syntax with options that start
|
||||
with two dashes (``--`` instead of a ``-``). Now, using the recommended
|
||||
syntax is required for options starting with ``--``, which means an option
|
||||
value must be strictly passed after a ``=``, instead of as separate
|
||||
argument. For example, ``--log-file f.txt`` was previously accepted and
|
||||
behaved like ``--log-file=f.txt``, but now causes an error. Use of legacy
|
||||
syntax that is still supported now prints a deprecation warning.
|
||||
--- 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."
|
||||
+16
-12
@@ -15,11 +15,11 @@ 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.
|
||||
``--ofopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
|
||||
``--ofopts=""``
|
||||
Completely empties the options list.
|
||||
@@ -41,11 +41,11 @@ 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.
|
||||
``--oacopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
|
||||
``--oacopts=""``
|
||||
Completely empties the options list.
|
||||
@@ -74,11 +74,11 @@ 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.
|
||||
``--ovcopts-add=<option>``
|
||||
Appends the option given as an argument to the options list. (Passing
|
||||
multiple options is currently still possible, but deprecated.)
|
||||
|
||||
``--ovcopts=""``
|
||||
Completely empties the options list.
|
||||
@@ -103,6 +103,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 +114,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``"
|
||||
|
||||
+340
-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
|
||||
-------------------
|
||||
|
||||
@@ -350,6 +426,12 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_).
|
||||
Shuffle the playlist. This is similar to what is done on start if the
|
||||
``--shuffle`` option is used.
|
||||
|
||||
``playlist-unshuffle``
|
||||
Attempt to revert the previous ``playlist-shuffle`` command. This works
|
||||
only once (multiple successive ``playlist-unshuffle`` commands do nothing).
|
||||
May not work correctly if new recursive playlists have been opened since
|
||||
a ``playlist-shuffle`` command.
|
||||
|
||||
``run <command> [<arg1> [<arg2> [...]]]``
|
||||
Run the given command. Unlike in MPlayer/mplayer2 and earlier versions of
|
||||
mpv (0.2.x and older), this doesn't call the shell. Instead, the command
|
||||
@@ -649,6 +731,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 +758,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 +814,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 +838,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 +943,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 +1058,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 +1072,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 +1615,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 +2116,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 +2139,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 +2159,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.
|
||||
@@ -1963,9 +2177,40 @@ Property list
|
||||
``overlay-add`` command. It gives you the actual OSD size, which can be
|
||||
different from the window size in some cases.
|
||||
|
||||
Alias to ``osd-dimensions/w`` and ``osd-dimensions/h``.
|
||||
|
||||
``osd-par``
|
||||
Last known OSD display pixel aspect (can be 0).
|
||||
|
||||
Alias to ``osd-dimensions/osd-par``.
|
||||
|
||||
``osd-dimensions``
|
||||
Last known OSD dimensions.
|
||||
|
||||
Has the following sub-properties (which can be read as ``MPV_FORMAT_NODE``
|
||||
or Lua table with ``mp.get_property_native``):
|
||||
|
||||
``w``
|
||||
Size of the VO window in OSD render units (usually pixels, but may be
|
||||
scaled pixels with VOs like ``xv``).
|
||||
|
||||
``h``
|
||||
Size of the VO window in OSD render units,
|
||||
|
||||
``par``
|
||||
Pixel aspect ratio of the OSD (usually 1).
|
||||
|
||||
``aspect``
|
||||
Display aspect ratio of the VO window. (Computing from the properties
|
||||
above.)
|
||||
|
||||
``mt``, ``mb``, ``ml``, ``mr``
|
||||
OSD to video margins (top, bottom, left, right). This describes the
|
||||
area into which the video is rendered.
|
||||
|
||||
Any of these properties may be unavailable or set to dummy values if the
|
||||
VO window is not created or visible.
|
||||
|
||||
``sub-text``
|
||||
Return the current subtitle text regardless of sub visibility.
|
||||
Formatting is stripped. If the subtitle is not text-based
|
||||
@@ -2050,7 +2295,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 +2656,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 +2811,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 +2868,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::
|
||||
|
||||
+182
-83
@@ -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.
|
||||
|
||||
|
||||
@@ -299,7 +314,7 @@ Legacy option syntax
|
||||
--------------------
|
||||
|
||||
The ``--option=value`` syntax is not strictly enforced, and the alternative
|
||||
legacy syntax ``-option value`` and ``--option value`` will also work. This is
|
||||
legacy syntax ``-option value`` and ``-option=value`` will also work. This is
|
||||
mostly for compatibility with MPlayer. Using these should be avoided. Their
|
||||
semantics can change any time in the future.
|
||||
|
||||
@@ -309,9 +324,15 @@ because ``--fs`` is a flag option that requires no parameter. If an option
|
||||
changes and its parameter becomes optional, then a command line using the
|
||||
alternative syntax will break.
|
||||
|
||||
Currently, the parser makes no difference whether an option starts with ``--``
|
||||
or a single ``-``. This might also change in the future, and ``--option value``
|
||||
might always interpret ``value`` as filename in order to reduce ambiguities.
|
||||
Until mpv 0.31.0, there was no difference whether an option started with ``--``
|
||||
or a single ``-``. Newer mpv releases strictly expect that you pass the option
|
||||
value after a ``=``. For example, before ``mpv --log-file f.txt`` would write
|
||||
a log to ``f.txt``, but now this command line fails, as ``--log-file`` expects
|
||||
an option value, and ``f.txt`` is simply considered a normal file to be played
|
||||
(as in ``mpv f.txt``).
|
||||
|
||||
The future plan is that ``-option value`` will not work anymore, and options
|
||||
with a single ``-`` behave the same as ``--`` options.
|
||||
|
||||
Escaping spaces and other special characters
|
||||
--------------------------------------------
|
||||
@@ -463,62 +484,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 +698,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 +709,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 +881,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 +909,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 +941,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 +1064,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 +1103,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
|
||||
@@ -1144,9 +1242,10 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
|
||||
``~/.config/mpv/scripts/``
|
||||
All files in this directory are loaded as if they were passed to the
|
||||
``--script`` option. They are loaded in alphabetical order, and sub-directories
|
||||
and files with no ``.lua`` extension are ignored. The ``--load-scripts=no``
|
||||
option disables loading these files.
|
||||
``--script`` option. They are loaded in alphabetical order. Directory entries
|
||||
other than files are ignored. Files with unknown extension lead to an
|
||||
initialization error. Files with ``.disable`` extension are ignored. The
|
||||
``--load-scripts=no`` option disables loading these files.
|
||||
|
||||
``~/.config/mpv/watch_later/``
|
||||
Contains temporary config files needed for resuming playback of files with
|
||||
|
||||
+368
-107
@@ -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``
|
||||
@@ -966,10 +1053,10 @@ Video
|
||||
:mediacodec-copy: copies video back to system RAM (Android only)
|
||||
:mmal: requires ``--vo=gpu`` (Raspberry Pi only - default if available)
|
||||
:mmal-copy: copies video back to system RAM (Raspberry Pi only)
|
||||
:cuda: requires ``--vo=gpu`` (Any platform CUDA is available)
|
||||
:cuda-copy: copies video back to system RAM (Any platform CUDA is available)
|
||||
:nvdec: requires ``--vo=gpu`` (Any platform CUDA is available)
|
||||
:nvdec-copy: copies video back to system RAM (Any platform CUDA is available)
|
||||
:cuda: requires ``--vo=gpu`` (Any platform CUDA is available)
|
||||
:cuda-copy: copies video back to system RAM (Any platform CUDA is available)
|
||||
:crystalhd: copies video back to system RAM (Any platform supported by hardware)
|
||||
:rkmpp: requires ``--vo=gpu`` (some RockChip devices only)
|
||||
|
||||
@@ -980,49 +1067,53 @@ 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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
``nvdec`` is a newer implementation of CUVID/CUDA decoding, which uses the
|
||||
FFmpeg decoders for file parsing. Experimental, is known not to correctly
|
||||
check whether decoding is supported by the hardware at all. Deinterlacing
|
||||
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.
|
||||
Because these copy the decoded video back to system RAM, they're often 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.
|
||||
only the ``vaapi``, ``nvdec`` and ``cuda`` methods work with Vulkan.
|
||||
|
||||
The ``vaapi`` mode, if used with ``--vo=gpu``, requires Mesa 11, and most
|
||||
likely works with Intel and AMD GPUs only. It also requires the opengl EGL
|
||||
backend.
|
||||
|
||||
``nvdec`` and ``nvdec-copy`` are the newest, and recommended method to do
|
||||
hardware decoding on Nvidia GPUs.
|
||||
|
||||
``cuda`` and ``cuda-copy`` are an older implementation of hardware decoding
|
||||
on Nvidia GPUs that uses Nvidia's bitstream parsers rather than FFmpeg's.
|
||||
This can lead to feature deficiencies, such as incorrect playback of HDR
|
||||
content, and ``nvdec``/``nvdec-copy`` should always be preferred unless you
|
||||
specifically need Nvidia's deinterlacing algorithms. 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.
|
||||
|
||||
.. admonition:: Quality reduction with hardware decoding
|
||||
|
||||
@@ -1040,10 +1131,10 @@ Video
|
||||
cases, hardware decoding can also reduce the bit depth of the decoded
|
||||
image, which can introduce banding or precision loss for 10-bit files.
|
||||
|
||||
``vdpau`` is usually safe, except for 10 bit video. If deinterlacing
|
||||
enabled (or the ``vdpaupp`` video filter is active in general), it
|
||||
forces RGB conversion. The latter currently does not treat certain
|
||||
colorspaces like BT.2020 correctly.
|
||||
``vdpau`` always does RGB conversion in hardware, which does not
|
||||
support newer colorspaces like BT.2020 correctly. However, ``vdpau``
|
||||
doesn't support 10 bit or HDR encodings, so these limitations are
|
||||
unlikely to be relevant.
|
||||
|
||||
``vaapi`` and ``d3d11va`` are safe. Enabling deinterlacing (or simply
|
||||
their respective post-processing filters) will possibly at least reduce
|
||||
@@ -1059,10 +1150,11 @@ Video
|
||||
``rpi`` always uses the hardware overlay renderer, even with
|
||||
``--vo=gpu``.
|
||||
|
||||
``cuda`` should be safe, but it has been reported to corrupt the
|
||||
timestamps causing glitched, flashing frames on some files. It can also
|
||||
sometimes cause massive framedrops for unknown reasons. Caution is
|
||||
advised.
|
||||
``cuda`` should usually be safe, but depending on how a file/stream
|
||||
has been mixed, it has been reported to corrupt the timestamps causing
|
||||
glitched, flashing frames. It can also sometimes cause massive
|
||||
framedrops for unknown reasons. Caution is advised, and ``nvdec``
|
||||
should always be preferred.
|
||||
|
||||
``crystalhd`` is not safe. It always converts to 4:2:2 YUV, which
|
||||
may be lossy, depending on how chroma sub-sampling is done during
|
||||
@@ -1088,13 +1180,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``.
|
||||
@@ -1140,17 +1232,18 @@ Video
|
||||
|
||||
``--cuda-decode-device=<auto|0..>``
|
||||
Choose the GPU device used for decoding when using the ``cuda`` or
|
||||
``nvdec`` hwdecs with the OpenGL GPU backend.
|
||||
``nvdec`` hwdecs with the OpenGL GPU backend, and with the ``cuda-copy``
|
||||
or ``nvdec-copy`` hwdecs in all cases.
|
||||
|
||||
By default, the device that is being used to provide ``gpu`` output will
|
||||
also be used for decoding (and in the vast majority of cases, only one
|
||||
GPU will be present).
|
||||
For the OpenGL GPU backend, the default device used for decoding is the one
|
||||
being used to provide ``gpu`` output (and in the vast majority of cases,
|
||||
only one GPU will be present).
|
||||
|
||||
Note that when using the ``cuda-copy`` or ``nvdec-copy`` hwdec, a
|
||||
different option must be passed: ``--vd-lavc-o=gpu=<0..>``.
|
||||
For the ``copy`` hwdecs, the default device will be the first device
|
||||
enumerated by the CUDA libraries - however that is done.
|
||||
|
||||
Note that this option is not available with the Vulkan GPU backend. With
|
||||
Vulkan, decoding must always happen on the display device.
|
||||
For the Vulkan GPU backend, decoding must always happen on the display
|
||||
device, and this option has no effect.
|
||||
|
||||
``--vaapi-device=<device file>``
|
||||
Choose the DRM device for ``vaapi-copy``. This should be the path to a
|
||||
@@ -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
|
||||
@@ -3321,6 +3458,18 @@ Demuxer
|
||||
after the initial caching. This option is useless if the file cannot be
|
||||
cached completely.
|
||||
|
||||
``--rar-list-all-volumes=<yes|no>``
|
||||
When opening multi-volume rar files, open all volumes to create a full list
|
||||
of contained files (default: no). If disabled, only the archive entries
|
||||
whose headers are located within the first volume are listed (and thus
|
||||
played when opening a .rar file with mpv). Doing so speeds up opening, and
|
||||
the typical idiotic use-case of playing uncompressed multi-volume rar files
|
||||
that contain a single media file is made faster.
|
||||
|
||||
Opening is still slow, because for unknown, idiotic, and unnecessary reasons
|
||||
libarchive opens all volumes anyway when playing the main file, even though
|
||||
mpv iterated no archive entries yet.
|
||||
|
||||
Input
|
||||
-----
|
||||
|
||||
@@ -3368,6 +3517,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 +3554,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 +3980,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 +4008,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 +4086,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 +4309,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 +4351,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 +4396,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 +4408,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 +4544,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 +4871,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 +4909,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 +4961,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 +4986,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 +5017,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 +5506,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 +5535,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 +6011,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 +6169,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 +6201,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 +6311,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
|
||||
|
||||
+23
-88
@@ -1,9 +1,7 @@
|
||||
Release 0.30.0
|
||||
Release 0.32.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,25 @@ 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
|
||||
- stream_libarchive: enable RAR5 support
|
||||
- bash completion: add initial implementation
|
||||
- cocoa-cb: add support for forcing the dedicated GPU for rendering
|
||||
- cocoa-cb: add pinch to resize window gesture
|
||||
- w32_common: support minimizing/maximizing using osc window controls
|
||||
|
||||
|
||||
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
|
||||
- wayland: print warning on GNOME due to serious issues with their compositor
|
||||
- player: write watch-later config even for unseekable streams
|
||||
|
||||
|
||||
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
|
||||
- vo_gpu: hwdec_vdpau: remove direct_mode
|
||||
- vo_gpu: hwdec_vaegl: remove support for old-style interop
|
||||
|
||||
|
||||
Options and Commands
|
||||
@@ -68,67 +37,33 @@ 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
|
||||
- command: add a playlist-unshuffle command
|
||||
- command: add osd-dimensions property
|
||||
- input: new PLAYONLY and PAUSEONLY keycodes
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate --video-aspect, replaced by --video-aspect-override
|
||||
- options: deprecate --record-file, --spirv-compiler
|
||||
|
||||
|
||||
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)
|
||||
- options: change option parsing when using a single dash
|
||||
This adds a warning for `-o file.mkv` and disallows the use of
|
||||
`--o file.mkv` (use `--o=file.mkv` instead).
|
||||
|
||||
|
||||
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
|
||||
- lua: fix mp.file_info for large files
|
||||
- vo_gpu: fix crash if dither texture fails to allocate
|
||||
- wayland: unscrew up cursors, fix various issues
|
||||
- osc: usability improvements for osc window controls
|
||||
|
||||
|
||||
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.31.0..v0.32.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.31.0...v0.32.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.31.0..v0.32.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
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
FFMPEG_SRC_DIR="${HOME}/deps/src/ffmpeg"
|
||||
FFMPEG_BUILD_DIR="${FFMPEG_SRC_DIR}/${TRAVIS_OS_NAME}"
|
||||
FFMPEG_SYSROOT="${HOME}/deps/sysroot"
|
||||
FFMPEG_HASH="18928e2bb4568cbe5e9061c3e6b63559392af3d2"
|
||||
|
||||
# Get the sauce if not around
|
||||
if [[ ! -d "${FFMPEG_SRC_DIR}" ]] ; then
|
||||
git clone "https://git.videolan.org/git/ffmpeg.git" "${FFMPEG_SRC_DIR}"
|
||||
fi
|
||||
|
||||
# pop into FFmpeg's source dir and clean up & check out our wanted revision
|
||||
pushd "${FFMPEG_SRC_DIR}"
|
||||
git reset --hard HEAD && git clean -dfx
|
||||
git checkout "${FFMPEG_HASH}"
|
||||
popd
|
||||
|
||||
# If a build dir of the same type is around, clean it up
|
||||
if [[ -d "${FFMPEG_BUILD_DIR}" ]] ; then
|
||||
rm -rf "${FFMPEG_BUILD_DIR}"
|
||||
fi
|
||||
|
||||
# Create and move into the build dir, configure and build!
|
||||
mkdir -p "${FFMPEG_BUILD_DIR}" && pushd "${FFMPEG_BUILD_DIR}"
|
||||
|
||||
PKG_CONFIG_PATH="${FFMPEG_SYSROOT}/lib/pkgconfig/" ../configure \
|
||||
--disable-{autodetect,stripping} \
|
||||
--cc="${CC}" \
|
||||
--cxx="${CXX}" \
|
||||
--prefix="${FFMPEG_SYSROOT}" \
|
||||
--enable-{zlib,securetransport,videotoolbox}
|
||||
|
||||
make -j4 && make install && popd
|
||||
|
||||
exit 0
|
||||
+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);
|
||||
|
||||
|
||||
+150
-121
@@ -32,6 +32,8 @@ 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;
|
||||
e->original_index = -1;
|
||||
return e;
|
||||
}
|
||||
|
||||
@@ -49,85 +51,68 @@ void playlist_entry_add_params(struct playlist_entry *e,
|
||||
playlist_entry_add_param(e, params[n].name, params[n].value);
|
||||
}
|
||||
|
||||
// Add entry "add" after entry "after".
|
||||
// If "after" is NULL, add as first entry.
|
||||
// Post condition: add->prev == after
|
||||
void playlist_insert(struct playlist *pl, struct playlist_entry *after,
|
||||
struct playlist_entry *add)
|
||||
static void playlist_update_indexes(struct playlist *pl, int start, int end)
|
||||
{
|
||||
assert(pl && add->pl == NULL && add->next == NULL && add->prev == NULL);
|
||||
if (after) {
|
||||
assert(after->pl == pl);
|
||||
assert(pl->first && pl->last);
|
||||
}
|
||||
add->prev = after;
|
||||
if (after) {
|
||||
add->next = after->next;
|
||||
after->next = add;
|
||||
} else {
|
||||
add->next = pl->first;
|
||||
pl->first = add;
|
||||
}
|
||||
if (add->next) {
|
||||
add->next->prev = add;
|
||||
} else {
|
||||
pl->last = add;
|
||||
}
|
||||
add->pl = pl;
|
||||
talloc_steal(pl, add);
|
||||
start = MPMAX(start, 0);
|
||||
end = end < 0 ? pl->num_entries : MPMIN(end, pl->num_entries);
|
||||
|
||||
for (int n = start; n < end; n++)
|
||||
pl->entries[n]->pl_index = n;
|
||||
}
|
||||
|
||||
void playlist_add(struct playlist *pl, struct playlist_entry *add)
|
||||
{
|
||||
playlist_insert(pl, pl->last, add);
|
||||
}
|
||||
|
||||
static void playlist_unlink(struct playlist *pl, struct playlist_entry *entry)
|
||||
{
|
||||
assert(pl && entry->pl == pl);
|
||||
|
||||
if (pl->current == entry) {
|
||||
pl->current = entry->next;
|
||||
pl->current_was_replaced = true;
|
||||
}
|
||||
|
||||
if (entry->next) {
|
||||
entry->next->prev = entry->prev;
|
||||
} else {
|
||||
pl->last = entry->prev;
|
||||
}
|
||||
if (entry->prev) {
|
||||
entry->prev->next = entry->next;
|
||||
} else {
|
||||
pl->first = entry->next;
|
||||
}
|
||||
entry->next = entry->prev = NULL;
|
||||
// xxx: we'd want to reset the talloc parent of entry
|
||||
entry->pl = NULL;
|
||||
MP_TARRAY_APPEND(pl, pl->entries, pl->num_entries, add);
|
||||
add->pl = pl;
|
||||
add->pl_index = pl->num_entries - 1;
|
||||
talloc_steal(pl, add);
|
||||
}
|
||||
|
||||
void playlist_entry_unref(struct playlist_entry *e)
|
||||
{
|
||||
e->reserved--;
|
||||
if (e->reserved < 0)
|
||||
if (e->reserved < 0) {
|
||||
assert(!e->pl);
|
||||
talloc_free(e);
|
||||
}
|
||||
}
|
||||
|
||||
void playlist_remove(struct playlist *pl, struct playlist_entry *entry)
|
||||
{
|
||||
playlist_unlink(pl, entry);
|
||||
assert(pl && entry->pl == pl);
|
||||
|
||||
if (pl->current == entry) {
|
||||
pl->current = playlist_entry_get_rel(entry, 1);
|
||||
pl->current_was_replaced = true;
|
||||
}
|
||||
|
||||
MP_TARRAY_REMOVE_AT(pl->entries, pl->num_entries, entry->pl_index);
|
||||
playlist_update_indexes(pl, entry->pl_index, -1);
|
||||
|
||||
entry->pl = NULL;
|
||||
entry->pl_index = -1;
|
||||
ta_set_parent(entry, NULL);
|
||||
|
||||
entry->removed = true;
|
||||
playlist_entry_unref(entry);
|
||||
}
|
||||
|
||||
void playlist_clear(struct playlist *pl)
|
||||
{
|
||||
while (pl->first)
|
||||
playlist_remove(pl, pl->first);
|
||||
for (int n = pl->num_entries - 1; n >= 0; n--)
|
||||
playlist_remove(pl, pl->entries[n]);
|
||||
assert(!pl->current);
|
||||
pl->current_was_replaced = false;
|
||||
}
|
||||
|
||||
void playlist_clear_except_current(struct playlist *pl)
|
||||
{
|
||||
for (int n = pl->num_entries - 1; n >= 0; n--) {
|
||||
if (pl->entries[n] != pl->current)
|
||||
playlist_remove(pl, pl->entries[n]);
|
||||
}
|
||||
}
|
||||
|
||||
// Moves the entry so that it takes "at"'s place (or move to end, if at==NULL).
|
||||
void playlist_move(struct playlist *pl, struct playlist_entry *entry,
|
||||
struct playlist_entry *at)
|
||||
@@ -135,14 +120,19 @@ void playlist_move(struct playlist *pl, struct playlist_entry *entry,
|
||||
if (entry == at)
|
||||
return;
|
||||
|
||||
struct playlist_entry *save_current = pl->current;
|
||||
bool save_replaced = pl->current_was_replaced;
|
||||
assert(entry && entry->pl == pl);
|
||||
assert(!at || at->pl == pl);
|
||||
|
||||
playlist_unlink(pl, entry);
|
||||
playlist_insert(pl, at ? at->prev : pl->last, entry);
|
||||
int index = at ? at->pl_index : pl->num_entries;
|
||||
MP_TARRAY_INSERT_AT(pl, pl->entries, pl->num_entries, index, entry);
|
||||
|
||||
pl->current = save_current;
|
||||
pl->current_was_replaced = save_replaced;
|
||||
int old_index = entry->pl_index;
|
||||
if (old_index >= index)
|
||||
old_index += 1;
|
||||
MP_TARRAY_REMOVE_AT(pl->entries, pl->num_entries, old_index);
|
||||
|
||||
playlist_update_indexes(pl, MPMIN(index - 1, old_index - 1),
|
||||
MPMAX(index + 1, old_index + 1));
|
||||
}
|
||||
|
||||
void playlist_add_file(struct playlist *pl, const char *filename)
|
||||
@@ -150,34 +140,46 @@ void playlist_add_file(struct playlist *pl, const char *filename)
|
||||
playlist_add(pl, playlist_entry_new(filename));
|
||||
}
|
||||
|
||||
static int playlist_count(struct playlist *pl)
|
||||
{
|
||||
int c = 0;
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next)
|
||||
c++;
|
||||
return c;
|
||||
}
|
||||
|
||||
void playlist_shuffle(struct playlist *pl)
|
||||
{
|
||||
struct playlist_entry *save_current = pl->current;
|
||||
bool save_replaced = pl->current_was_replaced;
|
||||
int count = playlist_count(pl);
|
||||
struct playlist_entry **arr = talloc_array(NULL, struct playlist_entry *,
|
||||
count);
|
||||
for (int n = 0; n < count; n++) {
|
||||
arr[n] = pl->first;
|
||||
playlist_unlink(pl, pl->first);
|
||||
for (int n = 0; n < pl->num_entries; n++)
|
||||
pl->entries[n]->original_index = n;
|
||||
for (int n = 0; n < pl->num_entries - 1; n++) {
|
||||
int j = (int)((double)(pl->num_entries - n) * rand() / (RAND_MAX + 1.0));
|
||||
MPSWAP(struct playlist_entry *, pl->entries[n], pl->entries[n + j]);
|
||||
}
|
||||
for (int n = 0; n < count - 1; n++) {
|
||||
int j = (int)((double)(count - n) * rand() / (RAND_MAX + 1.0));
|
||||
MPSWAP(struct playlist_entry *, arr[n], arr[n + j]);
|
||||
}
|
||||
for (int n = 0; n < count; n++)
|
||||
playlist_add(pl, arr[n]);
|
||||
talloc_free(arr);
|
||||
pl->current = save_current;
|
||||
pl->current_was_replaced = save_replaced;
|
||||
playlist_update_indexes(pl, 0, -1);
|
||||
}
|
||||
|
||||
#define CMP_INT(a, b) ((a) == (b) ? 0 : ((a) > (b) ? 1 : -1))
|
||||
|
||||
static int cmp_unshuffle(const void *a, const void *b)
|
||||
{
|
||||
struct playlist_entry *ea = *(struct playlist_entry **)a;
|
||||
struct playlist_entry *eb = *(struct playlist_entry **)b;
|
||||
|
||||
if (ea->original_index >= 0 && ea->original_index != eb->original_index)
|
||||
return CMP_INT(ea->original_index, eb->original_index);
|
||||
return CMP_INT(ea->pl_index, eb->pl_index);
|
||||
}
|
||||
|
||||
void playlist_unshuffle(struct playlist *pl)
|
||||
{
|
||||
if (pl->num_entries)
|
||||
qsort(pl->entries, pl->num_entries, sizeof(pl->entries[0]), cmp_unshuffle);
|
||||
playlist_update_indexes(pl, 0, -1);
|
||||
}
|
||||
|
||||
// (Explicitly ignores current_was_replaced.)
|
||||
struct playlist_entry *playlist_get_first(struct playlist *pl)
|
||||
{
|
||||
return pl->num_entries ? pl->entries[0] : NULL;
|
||||
}
|
||||
|
||||
// (Explicitly ignores current_was_replaced.)
|
||||
struct playlist_entry *playlist_get_last(struct playlist *pl)
|
||||
{
|
||||
return pl->num_entries ? pl->entries[pl->num_entries - 1] : NULL;
|
||||
}
|
||||
|
||||
struct playlist_entry *playlist_get_next(struct playlist *pl, int direction)
|
||||
@@ -187,15 +189,27 @@ struct playlist_entry *playlist_get_next(struct playlist *pl, int direction)
|
||||
return NULL;
|
||||
assert(pl->current->pl == pl);
|
||||
if (direction < 0)
|
||||
return pl->current->prev;
|
||||
return pl->current_was_replaced ? pl->current : pl->current->next;
|
||||
return playlist_entry_get_rel(pl->current, -1);
|
||||
return pl->current_was_replaced ? pl->current :
|
||||
playlist_entry_get_rel(pl->current, 1);
|
||||
}
|
||||
|
||||
// (Explicitly ignores current_was_replaced.)
|
||||
struct playlist_entry *playlist_entry_get_rel(struct playlist_entry *e,
|
||||
int direction)
|
||||
{
|
||||
assert(direction == -1 || direction == +1);
|
||||
if (!e->pl)
|
||||
return NULL;
|
||||
return playlist_entry_from_index(e->pl, e->pl_index + direction);
|
||||
}
|
||||
|
||||
void playlist_add_base_path(struct playlist *pl, bstr base_path)
|
||||
{
|
||||
if (base_path.len == 0 || bstrcmp0(base_path, ".") == 0)
|
||||
return;
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next) {
|
||||
for (int n = 0; n < pl->num_entries; n++) {
|
||||
struct playlist_entry *e = pl->entries[n];
|
||||
if (!mp_is_url(bstr0(e->filename))) {
|
||||
char *new_file = mp_path_join_bstr(e, base_path, bstr0(e->filename));
|
||||
talloc_free(e->filename);
|
||||
@@ -207,72 +221,84 @@ void playlist_add_base_path(struct playlist *pl, bstr base_path)
|
||||
// Add redirected_from as new redirect entry to each item in pl.
|
||||
void playlist_add_redirect(struct playlist *pl, const char *redirected_from)
|
||||
{
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next) {
|
||||
for (int n = 0; n < pl->num_entries; n++) {
|
||||
struct playlist_entry *e = pl->entries[n];
|
||||
if (e->num_redirects >= 10) // arbitrary limit for sanity
|
||||
break;
|
||||
continue;
|
||||
char *s = talloc_strdup(e, redirected_from);
|
||||
if (s)
|
||||
MP_TARRAY_APPEND(e, e->redirects, e->num_redirects, s);
|
||||
}
|
||||
}
|
||||
|
||||
void playlist_set_stream_flags(struct playlist *pl, int flags)
|
||||
{
|
||||
for (int n = 0; n < pl->num_entries; n++)
|
||||
pl->entries[n]->stream_flags = flags;
|
||||
}
|
||||
|
||||
static void playlist_transfer_entries_to(struct playlist *pl, int dst_index,
|
||||
struct playlist *source_pl)
|
||||
{
|
||||
assert(pl != source_pl);
|
||||
|
||||
int count = source_pl->num_entries;
|
||||
MP_TARRAY_INSERT_N_AT(pl, pl->entries, pl->num_entries, dst_index, count);
|
||||
|
||||
for (int n = 0; n < count; n++) {
|
||||
struct playlist_entry *e = source_pl->entries[n];
|
||||
e->pl = pl;
|
||||
e->pl_index = dst_index + n;
|
||||
pl->entries[e->pl_index] = e;
|
||||
talloc_steal(pl, e);
|
||||
}
|
||||
|
||||
playlist_update_indexes(pl, dst_index + count, -1);
|
||||
source_pl->num_entries = 0;
|
||||
}
|
||||
|
||||
// Move all entries from source_pl to pl, appending them after the current entry
|
||||
// of pl. source_pl will be empty, and all entries have changed ownership to pl.
|
||||
void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl)
|
||||
{
|
||||
struct playlist_entry *add_after = pl->current;
|
||||
if (pl->current && pl->current_was_replaced)
|
||||
add_after = pl->current->next;
|
||||
if (!add_after)
|
||||
add_after = pl->last;
|
||||
|
||||
while (source_pl->first) {
|
||||
struct playlist_entry *e = source_pl->first;
|
||||
playlist_unlink(source_pl, e);
|
||||
playlist_insert(pl, add_after, e);
|
||||
add_after = e;
|
||||
int add_at = pl->num_entries;
|
||||
if (pl->current) {
|
||||
add_at = pl->current->pl_index + 1;
|
||||
if (pl->current_was_replaced)
|
||||
add_at += 1;
|
||||
}
|
||||
assert(add_at >= 0);
|
||||
assert(add_at <= pl->num_entries);
|
||||
|
||||
playlist_transfer_entries_to(pl, add_at, source_pl);
|
||||
}
|
||||
|
||||
void playlist_append_entries(struct playlist *pl, struct playlist *source_pl)
|
||||
{
|
||||
while (source_pl->first) {
|
||||
struct playlist_entry *e = source_pl->first;
|
||||
playlist_unlink(source_pl, e);
|
||||
playlist_add(pl, e);
|
||||
}
|
||||
playlist_transfer_entries_to(pl, pl->num_entries, source_pl);
|
||||
}
|
||||
|
||||
// Return number of entries between list start and e.
|
||||
// Return -1 if e is not on the list, or if e is NULL.
|
||||
int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e)
|
||||
{
|
||||
struct playlist_entry *cur = pl->first;
|
||||
int pos = 0;
|
||||
if (!e)
|
||||
return -1;
|
||||
while (cur && cur != e) {
|
||||
cur = cur->next;
|
||||
pos++;
|
||||
}
|
||||
return cur == e ? pos : -1;
|
||||
assert(e->pl == pl);
|
||||
return e->pl_index;
|
||||
}
|
||||
|
||||
int playlist_entry_count(struct playlist *pl)
|
||||
{
|
||||
return playlist_entry_to_index(pl, pl->last) + 1;
|
||||
return pl->num_entries;
|
||||
}
|
||||
|
||||
// Return entry for which playlist_entry_to_index() would return index.
|
||||
// Return NULL if not found.
|
||||
struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index)
|
||||
{
|
||||
struct playlist_entry *e = pl->first;
|
||||
for (int n = 0; ; n++) {
|
||||
if (!e || n == index)
|
||||
return e;
|
||||
e = e->next;
|
||||
}
|
||||
return index >= 0 && index < pl->num_entries ? pl->entries[index] : NULL;
|
||||
}
|
||||
|
||||
struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel,
|
||||
@@ -281,7 +307,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);
|
||||
@@ -305,7 +334,7 @@ struct playlist *playlist_parse_file(const char *file, struct mp_cancel *cancel,
|
||||
mp_err(log, "Error while parsing playlist\n");
|
||||
}
|
||||
|
||||
if (ret && !ret->first)
|
||||
if (ret && !ret->num_entries)
|
||||
mp_warn(log, "Warning: empty playlist\n");
|
||||
|
||||
talloc_free(log);
|
||||
|
||||
+15
-10
@@ -26,8 +26,9 @@ struct playlist_param {
|
||||
};
|
||||
|
||||
struct playlist_entry {
|
||||
struct playlist_entry *prev, *next;
|
||||
// Invariant: (pl && pl->entries[pl_index] == this) || (!pl && pl_index < 0)
|
||||
struct playlist *pl;
|
||||
int pl_index;
|
||||
|
||||
char *filename;
|
||||
|
||||
@@ -41,6 +42,9 @@ struct playlist_entry {
|
||||
char **redirects;
|
||||
int num_redirects;
|
||||
|
||||
// Used for unshuffling: the pl_index before it was shuffled. -1 => unknown.
|
||||
int original_index;
|
||||
|
||||
// Set to true if playback didn't seem to work, or if the file could be
|
||||
// played only for a very short time. This is used to make playlist
|
||||
// navigation just work in case the user has unplayable files in the
|
||||
@@ -53,24 +57,20 @@ 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;
|
||||
};
|
||||
|
||||
struct playlist {
|
||||
struct playlist_entry *first, *last;
|
||||
struct playlist_entry **entries;
|
||||
int num_entries;
|
||||
|
||||
// This provides some sort of stable iterator. If this entry is removed from
|
||||
// the playlist, current is set to the next element (or NULL), and
|
||||
// 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);
|
||||
@@ -80,20 +80,25 @@ void playlist_entry_add_params(struct playlist_entry *e,
|
||||
|
||||
struct playlist_entry *playlist_entry_new(const char *filename);
|
||||
|
||||
void playlist_insert(struct playlist *pl, struct playlist_entry *after,
|
||||
struct playlist_entry *add);
|
||||
void playlist_add(struct playlist *pl, struct playlist_entry *add);
|
||||
void playlist_remove(struct playlist *pl, struct playlist_entry *entry);
|
||||
void playlist_clear(struct playlist *pl);
|
||||
void playlist_clear_except_current(struct playlist *pl);
|
||||
|
||||
void playlist_move(struct playlist *pl, struct playlist_entry *entry,
|
||||
struct playlist_entry *at);
|
||||
|
||||
void playlist_add_file(struct playlist *pl, const char *filename);
|
||||
void playlist_shuffle(struct playlist *pl);
|
||||
void playlist_unshuffle(struct playlist *pl);
|
||||
struct playlist_entry *playlist_get_first(struct playlist *pl);
|
||||
struct playlist_entry *playlist_get_last(struct playlist *pl);
|
||||
struct playlist_entry *playlist_get_next(struct playlist *pl, int direction);
|
||||
struct playlist_entry *playlist_entry_get_rel(struct playlist_entry *e,
|
||||
int direction);
|
||||
void playlist_add_base_path(struct playlist *pl, bstr base_path);
|
||||
void playlist_add_redirect(struct playlist *pl, const char *redirected_from);
|
||||
void playlist_set_stream_flags(struct playlist *pl, int flags);
|
||||
void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl);
|
||||
void playlist_append_entries(struct playlist *pl, struct playlist *source_pl);
|
||||
|
||||
|
||||
+131
-18
@@ -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,12 +98,15 @@ 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
|
||||
|
||||
#define MAX_BYTES MPMIN(INT64_MAX, SIZE_MAX / 2)
|
||||
|
||||
static bool get_demux_sub_opts(int index, const struct m_sub_options **sub);
|
||||
|
||||
const struct m_sub_options demux_conf = {
|
||||
.opts = (const struct m_option[]){
|
||||
OPT_CHOICE("cache", enable_cache, 0,
|
||||
@@ -128,6 +132,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,7 +151,9 @@ const struct m_sub_options demux_conf = {
|
||||
[STREAM_VIDEO] = 1,
|
||||
[STREAM_AUDIO] = 10,
|
||||
},
|
||||
.meta_cp = "utf-8",
|
||||
},
|
||||
.get_sub_options = get_demux_sub_opts,
|
||||
};
|
||||
|
||||
struct demux_internal {
|
||||
@@ -181,6 +188,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 +282,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 {
|
||||
@@ -330,6 +340,7 @@ struct demux_queue {
|
||||
bool correct_dts; // packet DTS is strictly monotonically increasing
|
||||
bool correct_pos; // packet pos is strictly monotonically increasing
|
||||
int64_t last_pos; // for determining correct_pos
|
||||
int64_t last_pos_fixup; // for filling in unset dp->pos values
|
||||
double last_dts; // for determining correct_dts
|
||||
double last_ts; // timestamp of the last packet added to queue
|
||||
|
||||
@@ -442,6 +453,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)
|
||||
{
|
||||
@@ -733,6 +745,7 @@ static void clear_queue(struct demux_queue *queue)
|
||||
queue->correct_dts = queue->correct_pos = true;
|
||||
queue->last_pos = -1;
|
||||
queue->last_ts = queue->last_dts = MP_NOPTS_VALUE;
|
||||
queue->last_pos_fixup = -1;
|
||||
|
||||
queue->is_eof = false;
|
||||
queue->is_bof = false;
|
||||
@@ -979,13 +992,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 +1611,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);
|
||||
@@ -1760,6 +1766,8 @@ static void attempt_range_joining(struct demux_internal *in)
|
||||
q1->keyframe_latest = q2->keyframe_latest;
|
||||
q1->is_eof = q2->is_eof;
|
||||
|
||||
q1->last_pos_fixup = -1;
|
||||
|
||||
q2->head = q2->tail = NULL;
|
||||
q2->keyframe_first = NULL;
|
||||
q2->keyframe_latest = NULL;
|
||||
@@ -1995,6 +2003,15 @@ static void add_packet_locked(struct sh_stream *stream, demux_packet_t *dp)
|
||||
struct demux_queue *queue = ds->queue;
|
||||
|
||||
bool drop = !ds->selected || in->seeking || ds->sh->attached_picture;
|
||||
|
||||
if (!drop) {
|
||||
// If libavformat splits packets, some packets will have pos unset, so
|
||||
// make up one based on the first packet => makes refresh seeks work.
|
||||
if (dp->pos < 0 && !dp->keyframe && queue->last_pos_fixup >= 0)
|
||||
dp->pos = queue->last_pos_fixup + 1;
|
||||
queue->last_pos_fixup = dp->pos;
|
||||
}
|
||||
|
||||
if (!drop && ds->refreshing) {
|
||||
// Resume reading once the old position was reached (i.e. we start
|
||||
// returning packets where we left off before the refresh).
|
||||
@@ -2377,6 +2394,9 @@ static void execute_seek(struct demux_internal *in)
|
||||
!(flags & (SEEK_FORWARD | SEEK_FACTOR)) &&
|
||||
pts <= in->d_thread->start_time;
|
||||
|
||||
for (int n = 0; n < in->num_streams; n++)
|
||||
in->streams[n]->ds->queue->last_pos_fixup = -1;
|
||||
|
||||
if (in->recorder)
|
||||
mp_recorder_mark_discontinuity(in->recorder);
|
||||
|
||||
@@ -2443,8 +2463,8 @@ static void update_opts(struct demux_internal *in)
|
||||
mp_recorder_destroy(in->recorder);
|
||||
in->recorder = NULL;
|
||||
}
|
||||
in->record_filename = talloc_strdup(in, opts->record_file);
|
||||
talloc_free(in->record_filename);
|
||||
in->record_filename = talloc_strdup(in, opts->record_file);
|
||||
// Note: actual recording only starts once packets are read. It may be
|
||||
// important to delay creating in->recorder to that point, because the
|
||||
// demuxer might detect more streams until finding the first packet.
|
||||
@@ -2900,6 +2920,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 +3123,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 +3135,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 +3170,7 @@ struct parent_stream_info {
|
||||
bool seekable;
|
||||
bool is_network;
|
||||
bool is_streaming;
|
||||
int stream_origin;
|
||||
struct mp_cancel *cancel;
|
||||
char *filename;
|
||||
};
|
||||
@@ -3182,6 +3202,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 +3235,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 +3256,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 +3337,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 +3349,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 +3393,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 +3783,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 +4002,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 +4375,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 +4427,85 @@ 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);
|
||||
}
|
||||
|
||||
static bool get_demux_sub_opts(int index, const struct m_sub_options **sub)
|
||||
{
|
||||
if (!demuxer_list[index])
|
||||
return false;
|
||||
*sub = demuxer_list[index]->options;
|
||||
return true;
|
||||
}
|
||||
@@ -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
|
||||
@@ -97,6 +98,9 @@ typedef struct demuxer_desc {
|
||||
const char *name; // Demuxer name, used with -demuxer switch
|
||||
const char *desc; // Displayed to user
|
||||
|
||||
// If non-NULL, these are added to the global option list.
|
||||
const struct m_sub_options *options;
|
||||
|
||||
// Return 0 on success, otherwise -1
|
||||
int (*open)(struct demuxer *demuxer, enum demux_check check);
|
||||
// The following functions are all optional
|
||||
@@ -196,6 +200,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);
|
||||
|
||||
+15
-13
@@ -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,15 +397,14 @@ 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)
|
||||
{
|
||||
if (bstr_equals0(mp_split_proto(bstr0(source_path), NULL), "edl"))
|
||||
return;
|
||||
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));
|
||||
part->filename = mp_path_join_bstr(parts, dirname, bstr0(part->filename));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,8 +424,7 @@ 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);
|
||||
fix_filenames(parts, tl->demuxer->filename);
|
||||
struct timeline_par *par = build_timeline(tl, parts);
|
||||
if (!par)
|
||||
break;
|
||||
@@ -454,12 +456,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))
|
||||
{
|
||||
|
||||
@@ -20,12 +20,17 @@
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/playlist.h"
|
||||
#include "options/m_config.h"
|
||||
#include "stream/stream.h"
|
||||
#include "misc/natural_sort.h"
|
||||
#include "demux.h"
|
||||
|
||||
#include "stream/stream_libarchive.h"
|
||||
|
||||
struct demux_libarchive_opts {
|
||||
int rar_list_all_volumes;
|
||||
};
|
||||
|
||||
static int cmp_filename(const void *a, const void *b)
|
||||
{
|
||||
return mp_natural_sort_cmp(*(char **)a, *(char **)b);
|
||||
@@ -43,28 +48,33 @@ 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);
|
||||
struct mp_archive *mpa = mp_archive_new(mp_null_log, probe_stream, flags);
|
||||
stream_memory_open(demuxer->global, probe, probe_got);
|
||||
struct mp_archive *mpa = mp_archive_new(mp_null_log, probe_stream, flags, 0);
|
||||
bool ok = !!mpa;
|
||||
free_stream(probe_stream);
|
||||
mp_archive_free(mpa);
|
||||
ta_free(probe);
|
||||
if (!ok)
|
||||
return -1;
|
||||
|
||||
mpa = mp_archive_new(demuxer->log, demuxer->stream, flags);
|
||||
struct demux_libarchive_opts *opts =
|
||||
mp_get_config_group(demuxer, demuxer->global, demuxer->desc->options);
|
||||
|
||||
if (!opts->rar_list_all_volumes)
|
||||
flags |= MP_ARCHIVE_FLAG_NO_RAR_VOLUMES;
|
||||
|
||||
mpa = mp_archive_new(demuxer->log, demuxer->stream, flags, 0);
|
||||
if (!mpa)
|
||||
return -1;
|
||||
|
||||
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 +82,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 +93,8 @@ 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]);
|
||||
|
||||
playlist_set_stream_flags(pl, demuxer->stream_origin);
|
||||
|
||||
demuxer->filetype = "archive";
|
||||
demuxer->fully_read = true;
|
||||
|
||||
@@ -92,8 +104,17 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_libarchive_opts
|
||||
|
||||
const struct demuxer_desc demuxer_desc_libarchive = {
|
||||
.name = "libarchive",
|
||||
.desc = "libarchive wrapper",
|
||||
.open = open_file,
|
||||
.options = &(const struct m_sub_options){
|
||||
.opts = (const struct m_option[]) {
|
||||
OPT_FLAG("rar-list-all-volumes", rar_list_all_volumes, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(OPT_BASE_STRUCT),
|
||||
},
|
||||
};
|
||||
+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))
|
||||
@@ -257,8 +258,10 @@ static void find_ordered_chapter_sources(struct tl_ctx *ctx)
|
||||
playlist_parse_file(opts->ordered_chapters_files,
|
||||
ctx->tl->cancel, ctx->global);
|
||||
talloc_steal(tmp, pl);
|
||||
for (struct playlist_entry *e = pl ? pl->first : NULL; e; e = e->next)
|
||||
MP_TARRAY_APPEND(tmp, filenames, num_filenames, e->filename);
|
||||
for (int n = 0; n < pl->num_entries; n++) {
|
||||
MP_TARRAY_APPEND(tmp, filenames, num_filenames,
|
||||
pl->entries[n]->filename);
|
||||
}
|
||||
} else if (!ctx->demuxer->stream->is_local_file) {
|
||||
MP_WARN(ctx, "Playback source is not a "
|
||||
"normal disk file. Will not search for related files.\n");
|
||||
@@ -515,7 +518,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) {
|
||||
|
||||
+79
-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,7 @@ 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));
|
||||
playlist_set_stream_flags(p->pl, 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]
|
||||
|
||||
+5
-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)
|
||||
@@ -146,6 +149,8 @@
|
||||
#PLAY cycle pause
|
||||
#PAUSE cycle pause
|
||||
#PLAYPAUSE cycle pause
|
||||
#PLAYONLY set pause no
|
||||
#PAUSEONLY set pause yes
|
||||
#STOP quit
|
||||
#FORWARD seek 60
|
||||
#REWIND seek -60
|
||||
@@ -165,22 +170,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
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
|
||||
_mpv_get_args()
|
||||
{
|
||||
local doc=$(mpv --list-options | grep -E "^\\s*$1\\s")
|
||||
local partial="$2"
|
||||
local type=$(echo "$doc" | awk '{print $2;}')
|
||||
|
||||
if [ "$1" = "--show-profile" ]; then
|
||||
# This is a special case
|
||||
type="Profile"
|
||||
fi
|
||||
|
||||
declare -a candidates
|
||||
case $type in
|
||||
String)
|
||||
echo "$doc" | grep -q '\[file\]'
|
||||
if [ $? -eq 0 ]; then
|
||||
if [ "$cur" = '=' ]; then
|
||||
# Without this, _filedir will try and complete files starting with '='
|
||||
cur=""
|
||||
fi
|
||||
_filedir
|
||||
return 0
|
||||
else
|
||||
candidates=($(mpv $1=help | grep -v ':' | awk '{print $1;}'))
|
||||
candidates+=("help")
|
||||
fi
|
||||
;;
|
||||
Flag)
|
||||
candidates=("yes" "no" "help")
|
||||
;;
|
||||
Choices:)
|
||||
candidates=($(mpv $1=help | grep -v ':' | awk '{print $1;}'))
|
||||
candidates+=("help")
|
||||
;;
|
||||
Image)
|
||||
candidates=($(mpv $1=help))
|
||||
candidates=("${candidates[@]:2}")
|
||||
candidates+=("help")
|
||||
;;
|
||||
Profile)
|
||||
candidates=($(mpv $1= | grep -v ':' | awk '{print $1;}'))
|
||||
;;
|
||||
*)
|
||||
# There are other categories; some of which we could do something smarter
|
||||
# about, with enough work.
|
||||
;;
|
||||
esac
|
||||
COMPREPLY=($(compgen -W "${candidates[*]}" -- "${partial}"))
|
||||
if [ ${#COMPREPLY[@]} -gt 1 ]; then
|
||||
compopt -o nospace mpv
|
||||
fi
|
||||
}
|
||||
|
||||
_mpv()
|
||||
{
|
||||
compopt +o nospace mpv
|
||||
|
||||
# _filedir requires the current candidate be in $cur
|
||||
local cur=${COMP_WORDS[COMP_CWORD]}
|
||||
local prev=${COMP_WORDS[((COMP_CWORD - 1))]}
|
||||
|
||||
if [ "$cur" = '=' ]; then
|
||||
# If the current word is '=' then we are looking for an argument for the
|
||||
# option specified by the previous word.
|
||||
_mpv_get_args "$prev"
|
||||
elif [ "$prev" = '=' ]; then
|
||||
# If the previous word is '=' then we are completing an argument for the
|
||||
# option specified by the word before the '='.
|
||||
local prevprev=${COMP_WORDS[((COMP_CWORD - 2))]}
|
||||
_mpv_get_args "$prevprev" "$cur"
|
||||
else
|
||||
case $cur in
|
||||
-*)
|
||||
# This regex detects special options where we don't want an '=' appended
|
||||
local special_regex='Flag.*\[not in config files\]|Print'
|
||||
local options=($(mpv --list-options | grep -v -E "$special_regex" |awk '{print "\\"$1;}' | grep '\--'))
|
||||
local specials=($(mpv --list-options | grep -E "$special_regex" |awk '{print "\\"$1;}' | grep '\--'))
|
||||
|
||||
COMPREPLY=($(compgen -W "${options[*]}" -S '=' -- "${cur}"))
|
||||
local normal_count=${#COMPREPLY[@]}
|
||||
COMPREPLY+=($(compgen -W "${specials[*]}" -- "${cur}"))
|
||||
if [ $normal_count -gt 0 -o ${#COMPREPLY[@]} -gt 1 ]; then
|
||||
compopt -o nospace mpv
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
_filedir
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
complete -F _mpv mpv
|
||||
+5
-70
@@ -59,17 +59,6 @@ static const struct mp_filter_info convert_filter = {
|
||||
.name = "convert",
|
||||
};
|
||||
|
||||
// For hw decoding: thing which can convert between underlying surface formats.
|
||||
// The filter detects the needed target format from struct mp_hwdec_ctx.
|
||||
struct subfmt_conv {
|
||||
int hw_imgfmt;
|
||||
struct mp_filter *(*create)(struct mp_filter *parent);
|
||||
};
|
||||
|
||||
static const struct subfmt_conv subfmt_converters[] = {
|
||||
{0}
|
||||
};
|
||||
|
||||
void mp_autoconvert_clear(struct mp_autoconvert *c)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
@@ -121,44 +110,6 @@ void mp_autoconvert_add_all_sw_imgfmts(struct mp_autoconvert *c)
|
||||
}
|
||||
}
|
||||
|
||||
void mp_autoconvert_add_vo_hwdec_subfmts(struct mp_autoconvert *c,
|
||||
struct mp_hwdec_devices *devs)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
assert(devs);
|
||||
|
||||
int prev_format = 0;
|
||||
|
||||
for (int n = 0; ; n++) {
|
||||
struct mp_hwdec_ctx *ctx = hwdec_devices_get_n(devs, n);
|
||||
if (!ctx)
|
||||
break;
|
||||
if (!ctx->hw_imgfmt || !ctx->supported_formats)
|
||||
continue;
|
||||
// Very hacky: don't let d3d11-egl-rgb overwrite d3d11-egl
|
||||
if (ctx->hw_imgfmt == prev_format)
|
||||
continue;
|
||||
prev_format = ctx->hw_imgfmt;
|
||||
// Stupidity: VOs export imgfmt only, so subfmt is always 0. Remove it
|
||||
// to fix it up.
|
||||
for (int i = 0; i < p->num_imgfmts; i++) {
|
||||
if (p->imgfmts[i] != ctx->hw_imgfmt)
|
||||
continue;
|
||||
|
||||
int count = p->num_imgfmts;
|
||||
MP_TARRAY_REMOVE_AT(p->imgfmts, count, i);
|
||||
count = p->num_imgfmts;
|
||||
MP_TARRAY_REMOVE_AT(p->subfmts, count, i);
|
||||
p->num_imgfmts -= 1;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; ctx->supported_formats[i]; i++)
|
||||
mp_autoconvert_add_imgfmt(c, ctx->hw_imgfmt, ctx->supported_formats[i]);
|
||||
}
|
||||
|
||||
p->vo_convert = true;
|
||||
}
|
||||
|
||||
void mp_autoconvert_add_afmt(struct mp_autoconvert *c, int afmt)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
@@ -198,13 +149,9 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log,
|
||||
if (!p->num_imgfmts)
|
||||
return true;
|
||||
|
||||
bool different_subfmt = false;
|
||||
|
||||
for (int n = 0; n < p->num_imgfmts; n++) {
|
||||
bool samefmt = img->params.imgfmt == p->imgfmts[n];
|
||||
bool samesubffmt = img->params.hw_subfmt == p->subfmts[n];
|
||||
if (samefmt && !samesubffmt)
|
||||
different_subfmt = true;
|
||||
if (samefmt && (samesubffmt || !p->subfmts[n])) {
|
||||
if (p->imgparams_set) {
|
||||
if (!mp_image_params_equal(&p->imgparams, &img->params))
|
||||
@@ -214,8 +161,6 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log,
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_stream_info *info = mp_filter_find_stream_info(f);
|
||||
|
||||
struct mp_filter *conv = mp_filter_create(f, &convert_filter);
|
||||
mp_filter_add_pin(conv, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(conv, MP_PIN_OUT, "out");
|
||||
@@ -230,6 +175,7 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log,
|
||||
|
||||
int *fmts = p->imgfmts;
|
||||
int num_fmts = p->num_imgfmts;
|
||||
int hwupload_fmt = 0;
|
||||
|
||||
bool imgfmt_is_sw = !IMGFMT_IS_HWACCEL(img->imgfmt);
|
||||
|
||||
@@ -256,22 +202,11 @@ static bool build_image_converter(struct mp_autoconvert *c, struct mp_log *log,
|
||||
if (upload) {
|
||||
mp_info(log, "HW-uploading to %s\n", mp_imgfmt_to_name(fmts[0]));
|
||||
filters[2] = upload->f;
|
||||
fmts = upload->upload_fmts;
|
||||
num_fmts = upload->num_upload_fmts;
|
||||
hwupload_fmt = mp_hwupload_find_upload_format(upload, img->imgfmt);
|
||||
fmts = &hwupload_fmt;
|
||||
num_fmts = hwupload_fmt ? 1 : 0;
|
||||
hw_to_sw = false;
|
||||
}
|
||||
} else if (p->vo_convert && different_subfmt && info && info->hwdec_devs) {
|
||||
for (int n = 0; subfmt_converters[n].hw_imgfmt; n++) {
|
||||
if (subfmt_converters[n].hw_imgfmt == img->imgfmt) {
|
||||
mp_info(log, "Using HW sub-conversion.\n");
|
||||
filters[2] = subfmt_converters[n].create(conv);
|
||||
if (filters[2]) {
|
||||
need_sws = false;
|
||||
hw_to_sw = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int src_fmt = img->imgfmt;
|
||||
@@ -309,7 +244,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));
|
||||
|
||||
@@ -35,7 +35,7 @@ void mp_autoconvert_set_target_image_params(struct mp_autoconvert *c,
|
||||
// Each call adds to the list of allowed formats. Before the first call, all
|
||||
// formats are allowed (even non-video).
|
||||
// subfmt can be used to specify underlying surface formats for hardware formats,
|
||||
// otherwise must be 0.
|
||||
// otherwise must be 0. (Mismatches lead to conversion errors.)
|
||||
void mp_autoconvert_add_imgfmt(struct mp_autoconvert *c, int imgfmt, int subfmt);
|
||||
|
||||
// Add all sw image formats. The effect is that hardware video image formats are
|
||||
|
||||
+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);
|
||||
+130
-59
@@ -19,65 +19,92 @@ struct priv {
|
||||
int last_upload_fmt;
|
||||
int last_sw_fmt;
|
||||
|
||||
// Hardware wrapper format, e.g. IMGFMT_VAAPI.
|
||||
int hw_imgfmt;
|
||||
|
||||
// List of supported underlying surface formats.
|
||||
int *fmts;
|
||||
int num_fmts;
|
||||
// List of supported upload image formats. May contain duplicate entries
|
||||
// (which should be ignored).
|
||||
int *upload_fmts;
|
||||
int num_upload_fmts;
|
||||
// For fmts[n], fmt_upload_index[n] gives the index of the first supported
|
||||
// upload format in upload_fmts[], and fmt_upload_num[n] gives the number
|
||||
// of formats at this position.
|
||||
int *fmt_upload_index;
|
||||
int *fmt_upload_num;
|
||||
|
||||
struct mp_hwupload public;
|
||||
};
|
||||
|
||||
static bool update_format_decision(struct priv *p, int input_fmt)
|
||||
{
|
||||
struct mp_hwupload *u = &p->public;
|
||||
struct ffmpeg_and_other_bugs {
|
||||
int imgfmt; // hw format
|
||||
const int *const whitelist_formats; // if non-NULL, allow only these
|
||||
// sw formats
|
||||
bool force_same_upload_fmt; // force upload fmt == sw fmt
|
||||
};
|
||||
|
||||
// This garbage is so complex and buggy. Hardcoding knowledge makes it work,
|
||||
// trying to use the dynamic information returned by the API does not. So fuck
|
||||
// this shit, I'll just whitelist the cases that work, what the fuck.
|
||||
static const struct ffmpeg_and_other_bugs shitlist[] = {
|
||||
{
|
||||
.imgfmt = IMGFMT_VAAPI,
|
||||
.whitelist_formats = (const int[]){IMGFMT_NV12, IMGFMT_P010, IMGFMT_BGRA,
|
||||
IMGFMT_ABGR, IMGFMT_RGB0, 0},
|
||||
.force_same_upload_fmt = true,
|
||||
},
|
||||
{0}
|
||||
};
|
||||
|
||||
static bool select_format(struct priv *p, int input_fmt, int *out_sw_fmt,
|
||||
int *out_upload_fmt)
|
||||
{
|
||||
if (!input_fmt)
|
||||
return false;
|
||||
|
||||
if (input_fmt == p->last_input_fmt)
|
||||
return true;
|
||||
|
||||
p->last_input_fmt = 0;
|
||||
|
||||
int res = mp_imgfmt_select_best_list(u->upload_fmts, u->num_upload_fmts,
|
||||
input_fmt);
|
||||
|
||||
if (!res)
|
||||
// First find the closest sw fmt. Some hwdec APIs return crazy lists of
|
||||
// "supported" formats, which then are not supported or crash (???), so
|
||||
// the this is a good way to avoid problems.
|
||||
// (Actually we should just have hardcoded everything instead of relying on
|
||||
// this fragile bullshit FFmpeg API and the fragile bullshit hwdec drivers.)
|
||||
int sw_fmt = mp_imgfmt_select_best_list(p->fmts, p->num_fmts, input_fmt);
|
||||
if (!sw_fmt)
|
||||
return false;
|
||||
|
||||
// Find which sw format we should use.
|
||||
// NOTE: if there are ever any hw APIs that actually do expensive
|
||||
// conversions on mismatching format uploads, we should probably first look
|
||||
// which sw format is preferred?
|
||||
// Dumb, but find index for p->fmts[index]==sw_fmt.
|
||||
int index = -1;
|
||||
for (int n = 0; n < u->num_upload_fmts; n++) {
|
||||
if (u->upload_fmts[n] == res)
|
||||
for (int n = 0; n < p->num_fmts; n++) {
|
||||
if (p->fmts[n] == sw_fmt)
|
||||
index = n;
|
||||
}
|
||||
|
||||
if (index < 0)
|
||||
return false;
|
||||
|
||||
for (int n = 0; n < u->num_fmts; n++) {
|
||||
if (index >= u->fmt_upload_index[n] &&
|
||||
index < u->fmt_upload_index[n] + u->fmt_upload_num[n])
|
||||
{
|
||||
p->last_input_fmt = input_fmt;
|
||||
p->last_upload_fmt = u->upload_fmts[index];
|
||||
p->last_sw_fmt = u->fmts[n];
|
||||
MP_INFO(u->f, "upload %s -> %s (%s)\n",
|
||||
mp_imgfmt_to_name(p->last_input_fmt),
|
||||
mp_imgfmt_to_name(p->last_upload_fmt),
|
||||
mp_imgfmt_to_name(p->last_sw_fmt));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Now check the available upload formats. This is the format our sw frame
|
||||
// has to be in, and which the upload API will take (probably).
|
||||
|
||||
return false;
|
||||
int *upload_fmts = &p->upload_fmts[p->fmt_upload_index[index]];
|
||||
int num_upload_fmts = p->fmt_upload_num[index];
|
||||
|
||||
int up_fmt = mp_imgfmt_select_best_list(upload_fmts, num_upload_fmts,
|
||||
input_fmt);
|
||||
if (!up_fmt)
|
||||
return false;
|
||||
|
||||
*out_sw_fmt = sw_fmt;
|
||||
*out_upload_fmt = up_fmt;
|
||||
return true;
|
||||
}
|
||||
|
||||
int mp_hwupload_find_upload_format(struct mp_hwupload *u, int imgfmt)
|
||||
{
|
||||
struct priv *p = u->f->priv;
|
||||
|
||||
if (!update_format_decision(p, imgfmt))
|
||||
return 0;
|
||||
return p->last_upload_fmt;
|
||||
int sw = 0, up = 0;
|
||||
select_format(p, imgfmt, &sw, &up);
|
||||
return up;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
@@ -109,14 +136,27 @@ static void process(struct mp_filter *f)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!update_format_decision(p, src->imgfmt)) {
|
||||
MP_ERR(f, "no hw upload format found\n");
|
||||
goto error;
|
||||
if (src->imgfmt != p->last_input_fmt) {
|
||||
if (!select_format(p, src->imgfmt, &p->last_sw_fmt, &p->last_upload_fmt))
|
||||
{
|
||||
MP_ERR(f, "no hw upload format found\n");
|
||||
goto error;
|
||||
}
|
||||
if (src->imgfmt != p->last_upload_fmt) {
|
||||
// Should not fail; if it does, mp_hwupload_find_upload_format()
|
||||
// does not return the src->imgfmt format.
|
||||
MP_ERR(f, "input format not an upload format\n");
|
||||
goto error;
|
||||
}
|
||||
p->last_input_fmt = src->imgfmt;
|
||||
MP_INFO(f, "upload %s -> %s[%s]\n",
|
||||
mp_imgfmt_to_name(p->last_input_fmt),
|
||||
mp_imgfmt_to_name(p->hw_imgfmt),
|
||||
mp_imgfmt_to_name(p->last_sw_fmt));
|
||||
}
|
||||
|
||||
if (!mp_update_av_hw_frames_pool(&p->hw_pool, p->av_device_ctx,
|
||||
p->public.hw_imgfmt, p->last_sw_fmt,
|
||||
src->w, src->h))
|
||||
if (!mp_update_av_hw_frames_pool(&p->hw_pool, p->av_device_ctx, p->hw_imgfmt,
|
||||
p->last_sw_fmt, src->w, src->h))
|
||||
{
|
||||
MP_ERR(f, "failed to create frame pool\n");
|
||||
goto error;
|
||||
@@ -175,9 +215,9 @@ static bool probe_formats(struct mp_hwupload *u, int hw_imgfmt)
|
||||
{
|
||||
struct priv *p = u->f->priv;
|
||||
|
||||
u->hw_imgfmt = hw_imgfmt;
|
||||
u->num_fmts = 0;
|
||||
u->num_upload_fmts = 0;
|
||||
p->hw_imgfmt = hw_imgfmt;
|
||||
p->num_fmts = 0;
|
||||
p->num_upload_fmts = 0;
|
||||
|
||||
struct mp_stream_info *info = mp_filter_find_stream_info(u->f);
|
||||
if (!info || !info->hwdec_devs) {
|
||||
@@ -221,6 +261,14 @@ static bool probe_formats(struct mp_hwupload *u, int hw_imgfmt)
|
||||
// supported formats. This should be relatively cheap as we don't create
|
||||
// any real frames (although some backends do for probing info).
|
||||
|
||||
const struct ffmpeg_and_other_bugs *bugs = NULL;
|
||||
for (int n = 0; shitlist[n].imgfmt; n++) {
|
||||
if (shitlist[n].imgfmt == hw_imgfmt) {
|
||||
bugs = &shitlist[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int n = 0; cstr->valid_sw_formats &&
|
||||
cstr->valid_sw_formats[n] != AV_PIX_FMT_NONE; n++)
|
||||
{
|
||||
@@ -228,12 +276,28 @@ static bool probe_formats(struct mp_hwupload *u, int hw_imgfmt)
|
||||
if (!imgfmt)
|
||||
continue;
|
||||
|
||||
MP_VERBOSE(u->f, "looking at format %s\n", mp_imgfmt_to_name(imgfmt));
|
||||
MP_VERBOSE(u->f, "looking at format %s/%s\n",
|
||||
mp_imgfmt_to_name(hw_imgfmt),
|
||||
mp_imgfmt_to_name(imgfmt));
|
||||
|
||||
if (bugs && bugs->whitelist_formats) {
|
||||
bool found = false;
|
||||
for (int i = 0; bugs->whitelist_formats[i]; i++) {
|
||||
if (bugs->whitelist_formats[i] == imgfmt) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
MP_VERBOSE(u->f, "... skipping blacklisted format\n");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Creates an AVHWFramesContexts with the given parameters.
|
||||
AVBufferRef *frames = NULL;
|
||||
if (!mp_update_av_hw_frames_pool(&frames, ctx->av_device_ref,
|
||||
hw_imgfmt, imgfmt, 128, 128))
|
||||
hw_imgfmt, imgfmt, 128, 128))
|
||||
{
|
||||
MP_WARN(u->f, "failed to allocate pool\n");
|
||||
continue;
|
||||
@@ -243,24 +307,31 @@ static bool probe_formats(struct mp_hwupload *u, int hw_imgfmt)
|
||||
if (av_hwframe_transfer_get_formats(frames,
|
||||
AV_HWFRAME_TRANSFER_DIRECTION_TO, &fmts, 0) >= 0)
|
||||
{
|
||||
int index = u->num_fmts;
|
||||
MP_TARRAY_APPEND(p, u->fmts, u->num_fmts, imgfmt);
|
||||
MP_TARRAY_GROW(p, u->fmt_upload_index, index);
|
||||
MP_TARRAY_GROW(p, u->fmt_upload_num, index);
|
||||
int index = p->num_fmts;
|
||||
MP_TARRAY_APPEND(p, p->fmts, p->num_fmts, imgfmt);
|
||||
MP_TARRAY_GROW(p, p->fmt_upload_index, index);
|
||||
MP_TARRAY_GROW(p, p->fmt_upload_num, index);
|
||||
|
||||
u->fmt_upload_index[index] = u->num_upload_fmts;
|
||||
p->fmt_upload_index[index] = p->num_upload_fmts;
|
||||
|
||||
for (int i = 0; fmts[i] != AV_PIX_FMT_NONE; i++) {
|
||||
int fmt = pixfmt2imgfmt(fmts[i]);
|
||||
if (!fmt)
|
||||
continue;
|
||||
MP_VERBOSE(u->f, "supports %s\n", mp_imgfmt_to_name(fmt));
|
||||
if (vo_supports(ctx, hw_imgfmt, fmt))
|
||||
MP_TARRAY_APPEND(p, u->upload_fmts, u->num_upload_fmts, fmt);
|
||||
MP_VERBOSE(u->f, " supports %s\n", mp_imgfmt_to_name(fmt));
|
||||
if (bugs && bugs->force_same_upload_fmt && imgfmt != fmt) {
|
||||
MP_VERBOSE(u->f, " ... skipping blacklisted format\n");
|
||||
continue;
|
||||
}
|
||||
if (!vo_supports(ctx, hw_imgfmt, fmt)) {
|
||||
MP_VERBOSE(u->f, " ... not supported by VO\n");
|
||||
continue;
|
||||
}
|
||||
MP_TARRAY_APPEND(p, p->upload_fmts, p->num_upload_fmts, fmt);
|
||||
}
|
||||
|
||||
u->fmt_upload_num[index] =
|
||||
u->num_upload_fmts - u->fmt_upload_index[index];
|
||||
p->fmt_upload_num[index] =
|
||||
p->num_upload_fmts - p->fmt_upload_index[index];
|
||||
|
||||
av_free(fmts);
|
||||
}
|
||||
@@ -272,7 +343,7 @@ static bool probe_formats(struct mp_hwupload *u, int hw_imgfmt)
|
||||
if (!p->av_device_ctx)
|
||||
return false;
|
||||
|
||||
return u->num_upload_fmts > 0;
|
||||
return p->num_upload_fmts > 0;
|
||||
}
|
||||
|
||||
struct mp_hwupload *mp_hwupload_create(struct mp_filter *parent, int hw_imgfmt)
|
||||
|
||||
@@ -5,22 +5,6 @@
|
||||
// A filter which uploads sw frames to hw. Ignores hw frames.
|
||||
struct mp_hwupload {
|
||||
struct mp_filter *f;
|
||||
|
||||
// Hardware wrapper format, e.g. IMGFMT_VAAPI.
|
||||
int hw_imgfmt;
|
||||
|
||||
// List of supported underlying surface formats.
|
||||
int *fmts;
|
||||
int num_fmts;
|
||||
// List of supported upload image formats. May contain duplicate entries
|
||||
// (which should be ignored).
|
||||
int *upload_fmts;
|
||||
int num_upload_fmts;
|
||||
// For fmts[n], fmt_upload_index[n] gives the index of the first supported
|
||||
// upload format in upload_fmts[], and fmt_upload_num[n] gives the number
|
||||
// of formats at this position.
|
||||
int *fmt_upload_index;
|
||||
int *fmt_upload_num;
|
||||
};
|
||||
|
||||
struct mp_hwupload *mp_hwupload_create(struct mp_filter *parent, int hw_imgfmt);
|
||||
|
||||
@@ -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);
|
||||
@@ -88,9 +88,6 @@ static void update_output_caps(struct chain *p)
|
||||
if (allowed_output_formats[n])
|
||||
mp_autoconvert_add_imgfmt(p->convert, IMGFMT_START + n, 0);
|
||||
}
|
||||
|
||||
if (p->vo->hwdec_devs)
|
||||
mp_autoconvert_add_vo_hwdec_subfmts(p->convert, p->vo->hwdec_devs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,33 +501,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 +521,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;
|
||||
+30
-4
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "cmd.h"
|
||||
#include "input.h"
|
||||
#include "misc/json.h"
|
||||
|
||||
#include "libmpv/client.h"
|
||||
|
||||
@@ -413,7 +414,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 +450,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 +469,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 +519,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;
|
||||
@@ -546,7 +559,14 @@ void mp_cmd_dump(struct mp_log *log, int msgl, char *header, struct mp_cmd *cmd)
|
||||
char *s = m_option_print(cmd->args[n].type, &cmd->args[n].v);
|
||||
if (n)
|
||||
mp_msg(log, msgl, ", ");
|
||||
mp_msg(log, msgl, "%s", s ? s : "(NULL)");
|
||||
struct mpv_node node = {
|
||||
.format = MPV_FORMAT_STRING,
|
||||
.u.string = s ? s : "(NULL)",
|
||||
};
|
||||
char *esc = NULL;
|
||||
json_write(&esc, &node);
|
||||
mp_msg(log, msgl, "%s", esc ? esc : "<error>");
|
||||
talloc_free(esc);
|
||||
talloc_free(s);
|
||||
}
|
||||
mp_msg(log, msgl, "]\n");
|
||||
@@ -595,6 +615,11 @@ static int parse_cycle_dir(struct mp_log *log, const struct m_option *opt,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *print_cycle_dir(const m_option_t *opt, const void *val)
|
||||
{
|
||||
return talloc_asprintf(NULL, "%f", *(double *)val);
|
||||
}
|
||||
|
||||
static void copy_opt(const m_option_t *opt, void *dst, const void *src)
|
||||
{
|
||||
if (dst && src)
|
||||
@@ -604,6 +629,7 @@ static void copy_opt(const m_option_t *opt, void *dst, const void *src)
|
||||
const struct m_option_type m_option_type_cycle_dir = {
|
||||
.name = "up|down",
|
||||
.parse = parse_cycle_dir,
|
||||
.print = print_cycle_dir,
|
||||
.copy = copy_opt,
|
||||
.size = sizeof(double),
|
||||
};
|
||||
+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){
|
||||
|
||||
+4
-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" },
|
||||
@@ -166,6 +151,8 @@ static const struct key_name key_names[] = {
|
||||
{ MP_KEY_RECORD, "RECORD" },
|
||||
{ MP_KEY_CHANNEL_UP, "CHANNEL_UP" },
|
||||
{ MP_KEY_CHANNEL_DOWN,"CHANNEL_DOWN" },
|
||||
{ MP_KEY_PLAYONLY, "PLAYONLY" },
|
||||
{ MP_KEY_PAUSEONLY, "PAUSEONLY" },
|
||||
|
||||
// These are kept for backward compatibility
|
||||
{ MP_KEY_PAUSE, "XF86_PAUSE" },
|
||||
@@ -208,6 +195,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 +259,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;
|
||||
}
|
||||
|
||||
+10
-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
|
||||
|
||||
@@ -76,6 +80,8 @@
|
||||
#define MP_KEY_RECORD (MP_KEY_MM_BASE+20)
|
||||
#define MP_KEY_CHANNEL_UP (MP_KEY_MM_BASE+21)
|
||||
#define MP_KEY_CHANNEL_DOWN (MP_KEY_MM_BASE+22)
|
||||
#define MP_KEY_PLAYONLY (MP_KEY_MM_BASE+23)
|
||||
#define MP_KEY_PAUSEONLY (MP_KEY_MM_BASE+24)
|
||||
|
||||
/* Function keys */
|
||||
#define MP_KEY_F (MP_KEY_BASE+0x40)
|
||||
@@ -137,23 +143,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 +199,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");
|
||||
|
||||
Loaded 100 of 272 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user