Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0536841647 | ||
|
|
0803f4ad21 | ||
|
|
b968d779af | ||
|
|
b18bfd0352 | ||
|
|
87cb2339a6 | ||
|
|
a2db0105ab | ||
|
|
89571312bf | ||
|
|
5f4b246bcc | ||
|
|
4561649f2d | ||
|
|
6325bdf197 | ||
|
|
813372d6e9 | ||
|
|
0812719497 | ||
|
|
f033481551 | ||
|
|
f34d086cb9 | ||
|
|
8a67606c20 | ||
|
|
f009d16f36 | ||
|
|
32a92071b4 | ||
|
|
a606b92f4a | ||
|
|
796b32c4d7 | ||
|
|
7a5312e9a6 | ||
|
|
afd685490d | ||
|
|
da3489353b | ||
|
|
2720e60ad0 | ||
|
|
c0e13d54a8 | ||
|
|
669a3228a2 | ||
|
|
d8ba84134f | ||
|
|
e5af1d9061 | ||
|
|
67888a41e7 | ||
|
|
aec928d753 | ||
|
|
7089175d8d | ||
|
|
eb740673b8 | ||
|
|
a54f6717c2 | ||
|
|
64791a0832 | ||
|
|
a3361ad0ce | ||
|
|
2dcf18c0c0 | ||
|
|
0d74652232 | ||
|
|
d0238711dc | ||
|
|
c971220cdd | ||
|
|
38c813c919 | ||
|
|
0e7bdce907 | ||
|
|
02dfc2c051 | ||
|
|
d88934815d | ||
|
|
c0848e18e9 | ||
|
|
213c611471 | ||
|
|
a7110862c8 | ||
|
|
872d0838a4 | ||
|
|
3984d2acca | ||
|
|
1107a070e4 | ||
|
|
ec6e8a31e0 | ||
|
|
dae23fff09 | ||
|
|
b95a10c2dd | ||
|
|
8d69731871 | ||
|
|
57506b27ed | ||
|
|
e1264d3976 | ||
|
|
d73d5327b3 | ||
|
|
cdd30fd0ab | ||
|
|
bd98d9e232 | ||
|
|
b8b2a465d1 | ||
|
|
5827d9cc09 | ||
|
|
e5b5cc2a2f | ||
|
|
fb70819048 | ||
|
|
c51fe7944d | ||
|
|
4470976cee | ||
|
|
447da032a3 | ||
|
|
e0cf90a0c8 | ||
|
|
a4168d85ab | ||
|
|
d5c5a2b05e | ||
|
|
32fde273d3 | ||
|
|
5843392db5 | ||
|
|
83b8d3f52c | ||
|
|
5fe2812494 | ||
|
|
dafafc90de | ||
|
|
ef625a78a4 | ||
|
|
a76f3e8e46 | ||
|
|
da015d9d00 | ||
|
|
73682a634a | ||
|
|
4300bfd518 | ||
|
|
f0febc35eb | ||
|
|
b34befbab8 | ||
|
|
30e94fa711 | ||
|
|
f9084c7bce | ||
|
|
c7f802ee45 | ||
|
|
a07832313d | ||
|
|
cd50ebba36 | ||
|
|
5bf473d5ca | ||
|
|
ba4569cf70 | ||
|
|
fd3ae6c561 | ||
|
|
c8b6ca9070 | ||
|
|
d6c99c8513 | ||
|
|
5f1ff78516 | ||
|
|
b9c48ca8f3 | ||
|
|
3353923f29 | ||
|
|
769f79bd39 | ||
|
|
f0ba2a54e6 | ||
|
|
5447cd033c | ||
|
|
b29af6a4bd | ||
|
|
717845dc5d | ||
|
|
4f699d0500 | ||
|
|
fe67aa7b18 | ||
|
|
042ca849ea | ||
|
|
fd57503890 | ||
|
|
0d19316de2 | ||
|
|
45db7d52a9 | ||
|
|
e26462599b | ||
|
|
96053d53a7 | ||
|
|
a0884c82a9 | ||
|
|
a993cdc481 | ||
|
|
71642f5d23 | ||
|
|
f8ec535131 | ||
|
|
7258e92250 | ||
|
|
d756dd8cbf | ||
|
|
95c09fa93b | ||
|
|
cd70665803 | ||
|
|
7e75e2a5dc | ||
|
|
718cc27f34 | ||
|
|
3375d65bc5 | ||
|
|
37aee2304a | ||
|
|
a1ffd7407d | ||
|
|
c13c9945bf | ||
|
|
9cd0555177 | ||
|
|
e8355597f3 | ||
|
|
52d336e4c1 | ||
|
|
3c59f54ec8 | ||
|
|
717380ad71 | ||
|
|
dff1314cec | ||
|
|
438af2e633 | ||
|
|
1cd2511752 | ||
|
|
8ff09f3217 | ||
|
|
d54b60c63d | ||
|
|
f7244ebe9b | ||
|
|
0e1e4005fb | ||
|
|
5199c2ee3a | ||
|
|
881bc23927 | ||
|
|
06fcabd06e | ||
|
|
9bc5c020de | ||
|
|
d8b27ee4de | ||
|
|
953ff6b390 | ||
|
|
7387766445 | ||
|
|
95ca308d44 | ||
|
|
876a3bafc5 | ||
|
|
75a36662cb | ||
|
|
ed254f29a9 | ||
|
|
f5bfe01932 | ||
|
|
87ae215853 | ||
|
|
0cdd8fff67 | ||
|
|
2db1b9e474 | ||
|
|
c68721b832 | ||
|
|
9f91bc4b75 | ||
|
|
5fa45fb564 | ||
|
|
b7617f42d8 | ||
|
|
c53c6bbd38 | ||
|
|
11d0290543 | ||
|
|
ead73032af | ||
|
|
05b2cd08dc | ||
|
|
5c1fe2a4f3 | ||
|
|
a714f8e928 | ||
|
|
a4dfc28fe1 | ||
|
|
b81036524a | ||
|
|
8ac6f6acf0 | ||
|
|
93546f0c2f | ||
|
|
fb2f8abaaa | ||
|
|
740b7013ba | ||
|
|
cda0dc9070 | ||
|
|
9972847265 | ||
|
|
3f60548df4 | ||
|
|
a6f8a6977e | ||
|
|
5c20263364 | ||
|
|
a4e29e67f9 | ||
|
|
3a7563a999 | ||
|
|
a19307d598 | ||
|
|
fa8b2be4de | ||
|
|
a888f08b78 | ||
|
|
46a3165cde | ||
|
|
a5eef06225 | ||
|
|
4e53272376 | ||
|
|
e094499197 | ||
|
|
33774e18ed | ||
|
|
af66fa8fa5 | ||
|
|
4bb94f1306 | ||
|
|
d7de123110 | ||
|
|
0a1e926670 | ||
|
|
041c9f1782 | ||
|
|
9d2980dab7 | ||
|
|
21cd4ff05b | ||
|
|
1f436f65f2 | ||
|
|
9001083685 | ||
|
|
92ba630796 | ||
|
|
b638a413c3 | ||
|
|
ad2d10af19 | ||
|
|
3ab6af4f59 | ||
|
|
49660bcc3e | ||
|
|
183e2cda30 | ||
|
|
67b7038be3 | ||
|
|
534571f794 | ||
|
|
af90616ebe | ||
|
|
1841cac9f8 | ||
|
|
82f102cfe3 | ||
|
|
84a3c21beb | ||
|
|
31539884c8 | ||
|
|
5e124a4ac3 | ||
|
|
a842ad8f50 | ||
|
|
1762d33971 | ||
|
|
72aea5a12b | ||
|
|
a6a358ce61 | ||
|
|
8bafd68fff | ||
|
|
3d2fb9e9a8 | ||
|
|
08dbaf1dcc | ||
|
|
71fa2e6fc2 | ||
|
|
b4bbfea71d | ||
|
|
174ca8b172 | ||
|
|
b654aaea0a | ||
|
|
503c6f7fd6 | ||
|
|
f0b15ad447 | ||
|
|
441b605de2 | ||
|
|
933fa7d225 | ||
|
|
b5f620ae75 | ||
|
|
36c6c0f79b | ||
|
|
2f562825e0 | ||
|
|
f0794d0544 | ||
|
|
935c8402bc | ||
|
|
ebc341ddd4 | ||
|
|
87b09a180a | ||
|
|
f3549ff76a | ||
|
|
0e298bb95a | ||
|
|
7c181e5b9b | ||
|
|
9ee340c3af | ||
|
|
289edadb8d | ||
|
|
d52b2981c0 | ||
|
|
65b858f7d3 | ||
|
|
1bb1543a88 | ||
|
|
943f76e6ce | ||
|
|
3ca0208566 | ||
|
|
4ee7ff026c | ||
|
|
da24cb9e3e | ||
|
|
297fdcc095 | ||
|
|
d549c4e402 | ||
|
|
f04cb7bf48 | ||
|
|
6640b22a8c | ||
|
|
b3804e71af | ||
|
|
ee5ba9678a | ||
|
|
e832e46bd6 | ||
|
|
53ef4822e8 | ||
|
|
bf0801a14a | ||
|
|
c01aaabb3e | ||
|
|
57c31f4130 | ||
|
|
d6af58c699 | ||
|
|
0d40140668 | ||
|
|
2d1f42089c | ||
|
|
084162d6fe | ||
|
|
941885b62c | ||
|
|
50d9a2609a | ||
|
|
f371367aac | ||
|
|
3dfb07854b | ||
|
|
88c83f450a | ||
|
|
5d761dd342 | ||
|
|
d5348a66dc | ||
|
|
01743f4ecd | ||
|
|
cd0a34feb3 | ||
|
|
ce0b99314b | ||
|
|
3c3cd0c540 | ||
|
|
0af5335383 | ||
|
|
ae55896f42 | ||
|
|
65f9af1d40 | ||
|
|
f2b039da77 | ||
|
|
f219a48dca | ||
|
|
8aeaa34a5c | ||
|
|
733e0b23c8 | ||
|
|
39ab426f05 | ||
|
|
b7034db4af | ||
|
|
012a8ef2cc | ||
|
|
acf239a45c | ||
|
|
06f1e934db | ||
|
|
543f6df2a6 | ||
|
|
74a8fd3702 | ||
|
|
e1e052a58e | ||
|
|
fa821de8b2 | ||
|
|
d3ed550792 | ||
|
|
0970763f69 | ||
|
|
90089b860c | ||
|
|
cf323d0cc1 | ||
|
|
f9f3175487 | ||
|
|
0868546f9d | ||
|
|
1cfcc1e1d5 | ||
|
|
0b427c54ad | ||
|
|
0287736119 | ||
|
|
fd80fcd3f3 | ||
|
|
337ad4fef4 | ||
|
|
8e4eee9a9a | ||
|
|
6eae6a785c | ||
|
|
166fa30f4b | ||
|
|
dccda5189d | ||
|
|
d3f32cba32 | ||
|
|
eff2e575f1 | ||
|
|
69b0af06ec | ||
|
|
ed6c04b1e5 | ||
|
|
8af7112802 | ||
|
|
4b97869e5f | ||
|
|
ff0112e08d | ||
|
|
a75f40e0d7 | ||
|
|
57ea367963 | ||
|
|
7caecc53b0 | ||
|
|
b7f6dfc19a | ||
|
|
668ba7441a | ||
|
|
3d09bd77dd | ||
|
|
5ca35aafdb | ||
|
|
3d9e1ad363 | ||
|
|
09d61032ca | ||
|
|
c3348d86f2 | ||
|
|
5d0c89a039 | ||
|
|
9f6dfaabf9 | ||
|
|
f04e7bc529 | ||
|
|
eb6b2b6e50 | ||
|
|
77cb44d759 | ||
|
|
3314483a02 | ||
|
|
563b4785da | ||
|
|
a0d3ce4819 | ||
|
|
fb5f732798 | ||
|
|
bb6ae0e50b | ||
|
|
ffb1d7807e | ||
|
|
1661ba09eb | ||
|
|
c0de087ba1 | ||
|
|
45345d9c41 | ||
|
|
8af70561a4 | ||
|
|
242dbc6c2c | ||
|
|
1aa2ae5404 | ||
|
|
2b58738545 | ||
|
|
fd339e3f53 | ||
|
|
b4f63cbbec | ||
|
|
155f7fac9d | ||
|
|
6cbd7d6390 | ||
|
|
363a225364 | ||
|
|
54d0f5bc9a | ||
|
|
a38f313bcc | ||
|
|
1b6a191ea3 | ||
|
|
35bdb63952 | ||
|
|
5d94977477 | ||
|
|
04783e11c9 | ||
|
|
8996f79edb | ||
|
|
b7e613f261 | ||
|
|
a080432992 | ||
|
|
5c8378b71a | ||
|
|
ab318aeea8 | ||
|
|
07d8a0e142 | ||
|
|
d53942dd79 | ||
|
|
05ffde6599 | ||
|
|
a9dfd8d557 | ||
|
|
c440ee99a4 | ||
|
|
006c9694c0 | ||
|
|
ca9124cd07 | ||
|
|
e42a08340b | ||
|
|
effc466222 | ||
|
|
a8dd0abb6d | ||
|
|
942a6729fa | ||
|
|
526d578bee | ||
|
|
c00dc5c5c6 | ||
|
|
c5a48c6332 | ||
|
|
340deb4e6e | ||
|
|
d980fd0856 | ||
|
|
354c1fc06d | ||
|
|
946ee29a7d | ||
|
|
939ad0889b | ||
|
|
d2942f1bcd | ||
|
|
4d5d25fdbb | ||
|
|
e927ff1666 | ||
|
|
f1072be3b7 | ||
|
|
dea42f77db | ||
|
|
c7ec8fdfe7 | ||
|
|
a18aa9e632 | ||
|
|
fb3b8e1e25 | ||
|
|
f8bb24184b | ||
|
|
34bead4859 | ||
|
|
7b6e3772ab | ||
|
|
beb7094301 | ||
|
|
fc3ca14ef7 | ||
|
|
d8aeeaa4b1 | ||
|
|
502763fcc7 | ||
|
|
475d346174 | ||
|
|
6462eabff3 | ||
|
|
521110054d | ||
|
|
bd1fb6f9b1 | ||
|
|
b53cb8de5e | ||
|
|
271cabe6a5 | ||
|
|
7f300b4204 | ||
|
|
eaf2eebb50 | ||
|
|
a160405284 | ||
|
|
266d8368e8 | ||
|
|
223588b36a | ||
|
|
3a015b9ec7 | ||
|
|
3c4f13c23a | ||
|
|
d917efcd58 | ||
|
|
75d29b1457 | ||
|
|
809786df42 | ||
|
|
7c991706c5 | ||
|
|
fae88b8113 | ||
|
|
2e3a508387 | ||
|
|
f176104ed5 | ||
|
|
4c25f49236 | ||
|
|
135a7217b9 | ||
|
|
ce0b26c60f | ||
|
|
ff7884e635 | ||
|
|
2df448b1f3 | ||
|
|
ff4bd73fcb | ||
|
|
657dd4b807 | ||
|
|
50c701574d | ||
|
|
04ec417d41 | ||
|
|
536efe6faf | ||
|
|
7bb9203f7f | ||
|
|
fef8b7984b | ||
|
|
27ecc417fe | ||
|
|
03b50d4bf6 | ||
|
|
9a2a0b0bba | ||
|
|
b2a5c16a9d | ||
|
|
346cf7abf0 | ||
|
|
ab59c5f522 | ||
|
|
e904129b79 | ||
|
|
c406f83d37 | ||
|
|
68366b05f2 | ||
|
|
ca00e347fc | ||
|
|
ac966ded11 | ||
|
|
930b0d1cf6 | ||
|
|
ff43215960 | ||
|
|
f352669157 | ||
|
|
a8abb53a5d | ||
|
|
aaafbfcc06 | ||
|
|
30031edce3 | ||
|
|
c365b44e19 | ||
|
|
7737499a74 | ||
|
|
0d3a67ab2c | ||
|
|
e4ec0f42e4 | ||
|
|
8a9b64329c | ||
|
|
6fafdd5142 | ||
|
|
ae4b0f3f7c | ||
|
|
a99b63db08 | ||
|
|
cd5eb1bb19 | ||
|
|
48c9101a5b | ||
|
|
f9cefbfec4 | ||
|
|
ed3e5330ec | ||
|
|
1781bf26f1 | ||
|
|
5053f4cc3f | ||
|
|
46bcdb7039 | ||
|
|
5986861c9c | ||
|
|
f15a79d144 | ||
|
|
488f569d99 | ||
|
|
6f35d4df5a | ||
|
|
2bbed80135 | ||
|
|
dfe630c5d1 | ||
|
|
418c98dec7 | ||
|
|
0ed170ec0b | ||
|
|
d1d6257731 | ||
|
|
ba9b2f1e49 | ||
|
|
7b4ccb3e9f | ||
|
|
4195a345a5 | ||
|
|
004bc95263 | ||
|
|
24fc176dfb | ||
|
|
056901b2be | ||
|
|
20991a95b8 | ||
|
|
31adc87bde | ||
|
|
2d4e8b623d | ||
|
|
bc1dce5d5b | ||
|
|
0fad73862c | ||
|
|
49690dd54b | ||
|
|
c9204fe3a5 | ||
|
|
24e7fac50b | ||
|
|
554c65e466 | ||
|
|
8128b7c8e2 | ||
|
|
0c9b655477 | ||
|
|
9fb949b49a | ||
|
|
909b8f89d8 | ||
|
|
77bac5b550 | ||
|
|
64067a9b7c | ||
|
|
62acd315ad | ||
|
|
c55b242b4e | ||
|
|
1e46bda0f8 | ||
|
|
16723d4bb1 | ||
|
|
4c3f95708f | ||
|
|
9a88b118b4 | ||
|
|
785eacf4f3 | ||
|
|
5722f93a74 | ||
|
|
bf13bd0d47 | ||
|
|
dd973da108 | ||
|
|
cbda46370c | ||
|
|
a4cdf1a727 | ||
|
|
e420464ba6 | ||
|
|
6fc0fe4426 | ||
|
|
671df54e4d | ||
|
|
81f3b3aafe | ||
|
|
796d5266f1 | ||
|
|
7c804cd796 | ||
|
|
ea442fa047 | ||
|
|
0915a8497b | ||
|
|
8135838018 | ||
|
|
f54ae2031e | ||
|
|
453ea2cb6c | ||
|
|
9fee7077d4 | ||
|
|
30fd858e5e | ||
|
|
9acea8759b | ||
|
|
31a4547187 | ||
|
|
994459e4ce | ||
|
|
a1f949d3b8 | ||
|
|
459b40cc4b | ||
|
|
9628edede0 | ||
|
|
bd5a02d080 | ||
|
|
2a80680d95 | ||
|
|
8e87ddd7fe | ||
|
|
e4a1086cfb | ||
|
|
12b4f177ec | ||
|
|
6a73fcc9ed | ||
|
|
2dd8982f73 | ||
|
|
6eccd4a573 | ||
|
|
27bc881cd8 | ||
|
|
b0c1455aa4 | ||
|
|
e2d90b383b | ||
|
|
c19f634e6c | ||
|
|
82e81421d7 | ||
|
|
0d05038790 | ||
|
|
38636345b1 | ||
|
|
9604c45e24 | ||
|
|
e69d1f2780 | ||
|
|
e9af0592d9 | ||
|
|
ce60c1adf7 | ||
|
|
35f43dfacb | ||
|
|
b7e179f6d3 | ||
|
|
3909e4cdfc | ||
|
|
3e90a5fe81 | ||
|
|
27ccad541a | ||
|
|
bf611ff0f6 | ||
|
|
0c877d2fdc | ||
|
|
8368ead1fa | ||
|
|
d22d24a6d5 | ||
|
|
fb84c6974d | ||
|
|
92ded6c6fd | ||
|
|
3d03298e86 | ||
|
|
c1002f6a28 | ||
|
|
861c126b08 | ||
|
|
8fda7247ff | ||
|
|
172ce98433 | ||
|
|
481d15ae60 | ||
|
|
0b5af5639b | ||
|
|
0015afd059 | ||
|
|
d0cd7fa31b | ||
|
|
a655432bbe | ||
|
|
09f0f68959 | ||
|
|
dac5b598f5 | ||
|
|
cb8b0cc329 | ||
|
|
029e31f1c5 | ||
|
|
efb9943637 | ||
|
|
243a2976a8 | ||
|
|
c725f39bae | ||
|
|
1f38c2107d | ||
|
|
2f4bd58f4a | ||
|
|
fa315705be | ||
|
|
2c3f4850b8 | ||
|
|
13ade4f1bc | ||
|
|
9163bdc38a | ||
|
|
d5f29a3c36 | ||
|
|
de0523ba7b | ||
|
|
ad072b17ce | ||
|
|
2059ba2c40 | ||
|
|
492e3deb95 | ||
|
|
fb185e4e79 | ||
|
|
dcffeaccf1 | ||
|
|
b5d0195023 | ||
|
|
b47bf06f97 | ||
|
|
9b3daa4974 | ||
|
|
4c1deb680d | ||
|
|
97d50538cd | ||
|
|
1f62f38106 | ||
|
|
d85753b79e | ||
|
|
50c379e2d8 | ||
|
|
d9f8b9f7b2 | ||
|
|
d804e7b903 | ||
|
|
7df475f860 | ||
|
|
aa587580c8 | ||
|
|
4d43a0c997 | ||
|
|
b637d6366c | ||
|
|
05444e8135 | ||
|
|
190dea149a | ||
|
|
8d4a179c14 | ||
|
|
ce8524cb47 | ||
|
|
504286b006 | ||
|
|
fb8285c7a7 | ||
|
|
7dff6a2842 | ||
|
|
0710ced079 | ||
|
|
6d9cb89333 | ||
|
|
940007cbf9 | ||
|
|
bbb65ed84b | ||
|
|
30074f8440 | ||
|
|
a042dcc700 | ||
|
|
3973a953df | ||
|
|
082c23515f | ||
|
|
6bec6ac558 | ||
|
|
946bd52a1d | ||
|
|
25497226cf | ||
|
|
f9ba1a3ddf | ||
|
|
b0381d27eb | ||
|
|
3e1aed8f40 | ||
|
|
ff520054c8 | ||
|
|
1a6f3c56ea | ||
|
|
af958221a1 | ||
|
|
2037426a65 | ||
|
|
e8960c69e9 | ||
|
|
23a5329690 | ||
|
|
eac0665b8d | ||
|
|
3de6c9aa42 | ||
|
|
8673343957 | ||
|
|
d1119cc069 | ||
|
|
ad25de5903 | ||
|
|
f8b09658f4 | ||
|
|
d5274d9298 | ||
|
|
5afa68835a | ||
|
|
5360baa49e | ||
|
|
fccc3d3894 | ||
|
|
7fa06e46c4 | ||
|
|
0afb1acab3 | ||
|
|
05b6646d7a | ||
|
|
35296c1f33 | ||
|
|
3ae726e8dd | ||
|
|
c188240ab9 | ||
|
|
099fdde7a4 | ||
|
|
cbc951d491 | ||
|
|
fc7212b214 | ||
|
|
f2187de8bb | ||
|
|
31c29495ca | ||
|
|
a191712169 | ||
|
|
517a35da94 | ||
|
|
821e8fb9d0 | ||
|
|
4b81398b4e | ||
|
|
d1cbff37be | ||
|
|
aa5f04c7a0 | ||
|
|
e15526153e | ||
|
|
48b103cad7 | ||
|
|
7f313afd08 | ||
|
|
7558d1ed7b | ||
|
|
0563eb914f | ||
|
|
abbb43759a | ||
|
|
6205d87a7f | ||
|
|
491958a724 | ||
|
|
808653a9b6 | ||
|
|
d2013b3f50 | ||
|
|
5790bbba61 | ||
|
|
f1a2610c4f | ||
|
|
2da533bec3 | ||
|
|
127da1613f | ||
|
|
c01935986c | ||
|
|
0a0bb9059f | ||
|
|
1f7c099dc0 | ||
|
|
78bfee53dd | ||
|
|
cd24fdcd5a | ||
|
|
47f2f554a3 | ||
|
|
a0519f1d18 | ||
|
|
4cc1861378 | ||
|
|
6154c1d06d | ||
|
|
32cd85bc7e | ||
|
|
3394d37b4e | ||
|
|
d2baaaa7df | ||
|
|
fc9eef3b81 | ||
|
|
58d45f7b3e | ||
|
|
8a9cde4999 | ||
|
|
3925dff2e6 | ||
|
|
f1909bc231 | ||
|
|
5dd5dc66c1 | ||
|
|
00135a87f0 | ||
|
|
687b552db1 | ||
|
|
2c7db48195 | ||
|
|
7137afeb2c | ||
|
|
ad80cc4eee | ||
|
|
f36c17d715 | ||
|
|
04af005c35 | ||
|
|
74c11f0c84 | ||
|
|
e798cf1ff6 | ||
|
|
cab942acae | ||
|
|
c7985fe5f7 | ||
|
|
29226e6a99 | ||
|
|
d0fd68f6df | ||
|
|
8d0a6cd035 | ||
|
|
f24ba544cd | ||
|
|
000285ee8e | ||
|
|
67a4892ee3 |
No files matched your search
@@ -0,0 +1,32 @@
|
||||
Reporting bugs
|
||||
==============
|
||||
|
||||
- State your platform and mpv version.
|
||||
- Make sure you're actually using the latest mpv release. Linux distributions
|
||||
in particular love redistributing ancient and unmaintained mpv releases.
|
||||
- Attach a log file made with ``-v`` or ``--log-file=output.txt``.
|
||||
- Avoid attaching text files packed in archive files (zip/rar/...), unless
|
||||
explicitly asked so. There are better means, such as using http://sprunge.us
|
||||
- If you think this is file specific, upload a sample file and link it.
|
||||
- Don't report multiple unrelated/barely related bugs in one issue.
|
||||
- If you don't get a reply after days, be sure to post a new message to the
|
||||
issue - it could have been overlooked or forgotten.
|
||||
- Read: https://mpv.io/bug-reports/
|
||||
|
||||
Asking questions
|
||||
================
|
||||
|
||||
Preferably, asking questions should be done via IRC (#mpv on irc.freenode.net),
|
||||
but asking questions by opening a github issue is also barely tolerated.
|
||||
|
||||
When asking on IRC (#mpv on irc.freenode.net), don't ask to ask, just state
|
||||
your problem. Stay long enough until someone gets a chance to reply. Sometimes
|
||||
it can take hours.
|
||||
|
||||
Sending patches
|
||||
===============
|
||||
|
||||
See: https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
|
||||
In particular, be aware that all changes to GPL code must come with the
|
||||
implicit agreement that they might be LGPLv2.1+ relicensed at a later point.
|
||||
@@ -1,14 +0,0 @@
|
||||
All examples in this directory and its sub-directories are licensed
|
||||
under one of the following licenses:
|
||||
|
||||
WTFPL, ISC, Ms-PL, AGPLv3, BSD (any)
|
||||
|
||||
Pick any license of your liking, and disregard the others.
|
||||
|
||||
(The full text of each license is available on this website:
|
||||
http://opensource.org/licenses/alphabetical )
|
||||
|
||||
Additionally, you may consider the example code to be public domain.
|
||||
You are free to use any of the example code without further
|
||||
requirements or need for attribution.
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
# Client API examples
|
||||
|
||||
All these examples use the mpv client API through libmpv.
|
||||
|
||||
## Where are the docs?
|
||||
|
||||
The libmpv C API is documented directly in the header files (on normal Unix
|
||||
systems, this is in `/usr/include/mpv/client.h`.
|
||||
|
||||
libmpv merely gives you access to mpv's command interface, which is documented
|
||||
here:
|
||||
* Options (`mpv_set_option()` and friends): http://mpv.io/manual/master/#options
|
||||
* Commands (`mpv_command()` and friends): http://mpv.io/manual/master/#list-of-input-commands
|
||||
* Properties (`mpv_set_property()` and friends): http://mpv.io/manual/master/#properties
|
||||
|
||||
Essentially everything is done with them, including loading a file, retrieving
|
||||
playback progress, and so on.
|
||||
|
||||
## Methods of embedding the video window
|
||||
|
||||
All of these examples concentrate on how to integrate mpv's video renderers
|
||||
with your own GUI. This is generally the hardest part. libmpv enforces a
|
||||
somewhat efficient video output method, rather than e.g. returning a RGBA
|
||||
surface in memory for each frame. The latter would be prohibitively inefficient,
|
||||
because it would require conversion on the CPU. The goal is also not requiring
|
||||
the API users to reinvent their own video rendering/scaling/presentation
|
||||
mechanisms.
|
||||
|
||||
There are currently 2 methods of embedding video.
|
||||
|
||||
### Native window embedding
|
||||
|
||||
This uses the platform's native method of nesting multiple windows. For example,
|
||||
Linux/X11 can nest a window from a completely different process. The nested
|
||||
window can redraw contents on its own, and receive user input if the user
|
||||
interacts with this window.
|
||||
|
||||
libmpv lets you specify a parent window for its own video window via the `wid`
|
||||
option. Then libmpv will create its window with your window as parent, and
|
||||
render its video inside of it.
|
||||
|
||||
This method is highly OS-dependent. Some behavior is OS-specific. There are
|
||||
problems with focusing on X11 (the ancient X11 focus policy mismatches with
|
||||
that of modern UI toolkits - it's normally worked around, but this is not
|
||||
easily possible with raw window embedding). It seems to have stability problems
|
||||
on OSX when using the Qt toolkit.
|
||||
|
||||
### OpenGL embedding
|
||||
|
||||
This method lets you use libmpv's OpenGL renderer directly. You create an
|
||||
OpenGL context, and then use `mpv_opengl_cb_draw()` to render the video on
|
||||
each frame.
|
||||
|
||||
This is more complicated, because libmpv will work directly on your own OpenGL
|
||||
state. It's also not possible to have mpv automatically receive user input.
|
||||
You will have to simulate this with the `mouse`/`keypress`/`keydown`/`keyup`
|
||||
commands.
|
||||
|
||||
You also get much more flexibility. For example, you can actually render your
|
||||
own OSD on top of the video, something that is not possible with raw window
|
||||
embedding.
|
||||
|
||||
### Which one to use?
|
||||
|
||||
Due to the various platform-specific behavior and problems (in particular on
|
||||
OSX), OpenGL embedding is recommended. If you're not comfortable with requiring
|
||||
OpenGL, or want to support "direct" video output such as vdpau (which might
|
||||
win when it comes to performance and energy-saving), you should probably
|
||||
support both methods if possible.
|
||||
|
||||
## List of examples
|
||||
|
||||
### simple
|
||||
|
||||
Very primitive terminal-only example. Shows some most basic API usage.
|
||||
|
||||
### cocoa
|
||||
|
||||
Shows how to embed the mpv video window in Objective-C/Cocoa.
|
||||
|
||||
### cocoa-openglcb
|
||||
|
||||
Similar to the cocoa sample, but shows how to integrate mpv's OpenGL renderer
|
||||
using libmpv's opengl-cb API. Since it does not require complicated interaction
|
||||
with Cocoa elements from different libraries, it's more robust.
|
||||
|
||||
### qt
|
||||
|
||||
Shows how to embed the mpv video window in Qt (using normal desktop widgets).
|
||||
|
||||
### qt_opengl
|
||||
|
||||
Shows how to use mpv's OpenGL video renderer in Qt. This uses the opengl-cb API
|
||||
for video. Since it does not rely on embedding "foreign" native Windows, it's
|
||||
usually more robust, potentially faster, and it's easier to control how your
|
||||
GUI interacts with the video. You can do your own OpenGL rendering on top of
|
||||
the video as well.
|
||||
|
||||
### qml
|
||||
|
||||
Shows how to use mpv's OpenGL video renderer in QtQuick2 with QML. Uses the
|
||||
opengl-cb API for video. Since the video is a normal QML element, it's trivial
|
||||
to create OSD overlays with QML-native graphical elements as well.
|
||||
|
||||
### qml_direct
|
||||
|
||||
Alternative example, which typically avoids a FBO indirection. Might be
|
||||
slightly faster, but is less flexible and harder to use. In particular, the
|
||||
video is not a normal QML element. Uses the opengl-cb API for video.
|
||||
|
||||
### sdl
|
||||
|
||||
Show how to embed the mpv OpenGL renderer in SDL. Uses the opengl-cb API for
|
||||
video.
|
||||
@@ -1,304 +0,0 @@
|
||||
// Plays a video from the command line in an opengl view in its own window.
|
||||
|
||||
// Build with: clang -o cocoa-openglcb cocoa-openglcb.m `pkg-config --libs --cflags mpv` -framework Cocoa -framework OpenGL
|
||||
|
||||
#import <mpv/client.h>
|
||||
#import <mpv/opengl_cb.h>
|
||||
|
||||
#import <stdio.h>
|
||||
#import <stdlib.h>
|
||||
#import <OpenGL/gl.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
|
||||
static inline void check_error(int status)
|
||||
{
|
||||
if (status < 0) {
|
||||
printf("mpv API error: %s\n", mpv_error_string(status));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void *get_proc_address(void *ctx, const char *name)
|
||||
{
|
||||
CFStringRef symbolName = CFStringCreateWithCString(kCFAllocatorDefault, name, kCFStringEncodingASCII);
|
||||
void *addr = CFBundleGetFunctionPointerForName(CFBundleGetBundleWithIdentifier(CFSTR("com.apple.opengl")), symbolName);
|
||||
CFRelease(symbolName);
|
||||
return addr;
|
||||
}
|
||||
|
||||
static void glupdate(void *ctx);
|
||||
|
||||
@interface MpvClientOGLView : NSOpenGLView
|
||||
@property mpv_opengl_cb_context *mpvGL;
|
||||
- (instancetype)initWithFrame:(NSRect)frame;
|
||||
- (void)drawRect;
|
||||
- (void)fillBlack;
|
||||
@end
|
||||
|
||||
@implementation MpvClientOGLView
|
||||
- (instancetype)initWithFrame:(NSRect)frame
|
||||
{
|
||||
// make sure the pixel format is double buffered so we can use
|
||||
// [[self openGLContext] flushBuffer].
|
||||
NSOpenGLPixelFormatAttribute attributes[] = {
|
||||
NSOpenGLPFADoubleBuffer,
|
||||
0
|
||||
};
|
||||
self = [super initWithFrame:frame
|
||||
pixelFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]];
|
||||
|
||||
if (self) {
|
||||
[self setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
|
||||
// swap on vsyncs
|
||||
GLint swapInt = 1;
|
||||
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
|
||||
[[self openGLContext] makeCurrentContext];
|
||||
self.mpvGL = nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)fillBlack
|
||||
{
|
||||
glClearColor(0, 0, 0, 0);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
- (void)drawRect
|
||||
{
|
||||
if (self.mpvGL)
|
||||
mpv_opengl_cb_draw(self.mpvGL, 0, self.bounds.size.width, -self.bounds.size.height);
|
||||
else
|
||||
[self fillBlack];
|
||||
[[self openGLContext] flushBuffer];
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)dirtyRect
|
||||
{
|
||||
[self drawRect];
|
||||
}
|
||||
@end
|
||||
|
||||
@interface CocoaWindow : NSWindow
|
||||
@property(retain, readonly) MpvClientOGLView *glView;
|
||||
@property(retain, readonly) NSButton *pauseButton;
|
||||
@end
|
||||
|
||||
@implementation CocoaWindow
|
||||
- (BOOL)canBecomeMainWindow { return YES; }
|
||||
- (BOOL)canBecomeKeyWindow { return YES; }
|
||||
- (void)initOGLView {
|
||||
NSRect bounds = [[self contentView] bounds];
|
||||
// window coordinate origin is bottom left
|
||||
NSRect glFrame = NSMakeRect(bounds.origin.x, bounds.origin.y + 30, bounds.size.width, bounds.size.height - 30);
|
||||
_glView = [[MpvClientOGLView alloc] initWithFrame:glFrame];
|
||||
[self.contentView addSubview:_glView];
|
||||
|
||||
NSRect buttonFrame = NSMakeRect(bounds.origin.x, bounds.origin.y, 60, 30);
|
||||
_pauseButton = [[NSButton alloc] initWithFrame:buttonFrame];
|
||||
_pauseButton.buttonType = NSToggleButton;
|
||||
// button target has to be the delegate (it holds the mpv context
|
||||
// pointer), so that's set later.
|
||||
_pauseButton.action = @selector(togglePause:);
|
||||
_pauseButton.title = @"Pause";
|
||||
_pauseButton.alternateTitle = @"Play";
|
||||
[self.contentView addSubview:_pauseButton];
|
||||
}
|
||||
@end
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||
{
|
||||
mpv_handle *mpv;
|
||||
dispatch_queue_t queue;
|
||||
CocoaWindow *window;
|
||||
}
|
||||
@end
|
||||
|
||||
static void wakeup(void *);
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (void)createWindow {
|
||||
|
||||
int mask = NSTitledWindowMask|NSClosableWindowMask|
|
||||
NSMiniaturizableWindowMask|NSResizableWindowMask;
|
||||
|
||||
window = [[CocoaWindow alloc]
|
||||
initWithContentRect:NSMakeRect(0, 0, 1280, 720)
|
||||
styleMask:mask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
|
||||
// force a minimum size to stop opengl from exploding.
|
||||
[window setMinSize:NSMakeSize(200, 200)];
|
||||
[window initOGLView];
|
||||
[window setTitle:@"cocoa-openglcb example"];
|
||||
[window makeMainWindow];
|
||||
[window makeKeyAndOrderFront:nil];
|
||||
|
||||
NSMenu *m = [[NSMenu alloc] initWithTitle:@"AMainMenu"];
|
||||
NSMenuItem *item = [m addItemWithTitle:@"Apple" action:nil keyEquivalent:@""];
|
||||
NSMenu *sm = [[NSMenu alloc] initWithTitle:@"Apple"];
|
||||
[m setSubmenu:sm forItem:item];
|
||||
[sm addItemWithTitle: @"quit" action:@selector(terminate:) keyEquivalent:@"q"];
|
||||
[NSApp setMenu:m];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
atexit_b(^{
|
||||
// Because activation policy has just been set to behave like a real
|
||||
// application, that policy must be reset on exit to prevent, among
|
||||
// other things, the menubar created here from remaining on screen.
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
|
||||
});
|
||||
|
||||
// Read filename
|
||||
NSArray *args = [NSProcessInfo processInfo].arguments;
|
||||
if (args.count < 2) {
|
||||
NSLog(@"Expected filename on command line");
|
||||
exit(1);
|
||||
}
|
||||
NSString *filename = args[1];
|
||||
|
||||
[self createWindow];
|
||||
window.pauseButton.target = self;
|
||||
|
||||
mpv = mpv_create();
|
||||
if (!mpv) {
|
||||
printf("failed creating context\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
check_error(mpv_set_option_string(mpv, "input-media-keys", "yes"));
|
||||
// request important errors
|
||||
check_error(mpv_request_log_messages(mpv, "warn"));
|
||||
|
||||
check_error(mpv_initialize(mpv));
|
||||
check_error(mpv_set_option_string(mpv, "vo", "opengl-cb"));
|
||||
mpv_opengl_cb_context *mpvGL = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpvGL) {
|
||||
puts("libmpv does not have the opengl-cb sub-API.");
|
||||
exit(1);
|
||||
}
|
||||
// pass the mpvGL context to our view
|
||||
window.glView.mpvGL = mpvGL;
|
||||
int r = mpv_opengl_cb_init_gl(mpvGL, NULL, get_proc_address, NULL);
|
||||
if (r < 0) {
|
||||
puts("gl init has failed.");
|
||||
exit(1);
|
||||
}
|
||||
mpv_opengl_cb_set_update_callback(mpvGL, glupdate, (__bridge void *)window.glView);
|
||||
|
||||
// Deal with MPV in the background.
|
||||
queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_async(queue, ^{
|
||||
// Register to be woken up whenever mpv generates new events.
|
||||
mpv_set_wakeup_callback(mpv, wakeup, (__bridge void *)self);
|
||||
// Load the indicated file
|
||||
const char *cmd[] = {"loadfile", filename.UTF8String, NULL};
|
||||
check_error(mpv_command(mpv, cmd));
|
||||
});
|
||||
}
|
||||
|
||||
static void glupdate(void *ctx)
|
||||
{
|
||||
MpvClientOGLView *glView = (__bridge MpvClientOGLView *)ctx;
|
||||
// I'm still not sure what the best way to handle this is, but this
|
||||
// works.
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[glView drawRect];
|
||||
});
|
||||
}
|
||||
|
||||
- (void) handleEvent:(mpv_event *)event
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_SHUTDOWN: {
|
||||
mpv_detach_destroy(mpv);
|
||||
mpv_opengl_cb_uninit_gl(window.glView.mpvGL);
|
||||
mpv = NULL;
|
||||
printf("event: shutdown\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case MPV_EVENT_LOG_MESSAGE: {
|
||||
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
|
||||
printf("[%s] %s: %s", msg->prefix, msg->level, msg->text);
|
||||
}
|
||||
|
||||
default:
|
||||
printf("event: %s\n", mpv_event_name(event->event_id));
|
||||
}
|
||||
}
|
||||
|
||||
- (void)togglePause:(NSButton *)button
|
||||
{
|
||||
if (mpv) {
|
||||
switch (button.state) {
|
||||
case NSOffState:
|
||||
{
|
||||
int pause = 0;
|
||||
mpv_set_property(mpv, "pause", MPV_FORMAT_FLAG, &pause);
|
||||
}
|
||||
break;
|
||||
case NSOnState:
|
||||
{
|
||||
int pause = 1;
|
||||
mpv_set_property(mpv, "pause", MPV_FORMAT_FLAG, &pause);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
NSLog(@"This should never happen.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void) readEvents
|
||||
{
|
||||
dispatch_async(queue, ^{
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
[self handleEvent:event];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void wakeup(void *context)
|
||||
{
|
||||
AppDelegate *a = (__bridge AppDelegate *) context;
|
||||
[a readEvents];
|
||||
}
|
||||
|
||||
// quit when the window is closed.
|
||||
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
|
||||
{
|
||||
NSLog(@"Terminating.");
|
||||
const char *args[] = {"quit", NULL};
|
||||
mpv_command(mpv, args);
|
||||
[window.glView clearGLContext];
|
||||
return NSTerminateNow;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Delete this if you already have a main.m.
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
AppDelegate *delegate = [AppDelegate new];
|
||||
app.delegate = delegate;
|
||||
[app run];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,209 +0,0 @@
|
||||
// Plays a video from the command line in a view provided by the client
|
||||
// application.
|
||||
|
||||
// Build with: clang -o cocoabasic cocoabasic.m `pkg-config --libs --cflags mpv` -framework cocoa
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static inline void check_error(int status)
|
||||
{
|
||||
if (status < 0) {
|
||||
printf("mpv API error: %s\n", mpv_error_string(status));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@interface CocoaWindow : NSWindow
|
||||
@end
|
||||
|
||||
@implementation CocoaWindow
|
||||
- (BOOL)canBecomeMainWindow { return YES; }
|
||||
- (BOOL)canBecomeKeyWindow { return YES; }
|
||||
@end
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||
{
|
||||
mpv_handle *mpv;
|
||||
dispatch_queue_t queue;
|
||||
NSWindow *w;
|
||||
NSView *wrapper;
|
||||
}
|
||||
@end
|
||||
|
||||
static void wakeup(void *);
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (void)createWindow {
|
||||
|
||||
int mask = NSTitledWindowMask|NSClosableWindowMask|
|
||||
NSMiniaturizableWindowMask|NSResizableWindowMask;
|
||||
|
||||
self->w = [[CocoaWindow alloc]
|
||||
initWithContentRect:NSMakeRect(0,0, 1280, 720)
|
||||
styleMask:mask
|
||||
backing:NSBackingStoreBuffered
|
||||
defer:NO];
|
||||
|
||||
[self->w setTitle:@"cocoabasic example"];
|
||||
[self->w makeMainWindow];
|
||||
[self->w makeKeyAndOrderFront:nil];
|
||||
|
||||
NSRect frame = [[self->w contentView] bounds];
|
||||
self->wrapper = [[NSView alloc] initWithFrame:frame];
|
||||
[self->wrapper setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
|
||||
[[self->w contentView] addSubview:self->wrapper];
|
||||
[self->wrapper release];
|
||||
|
||||
NSMenu *m = [[NSMenu alloc] initWithTitle:@"AMainMenu"];
|
||||
NSMenuItem *item = [m addItemWithTitle:@"Apple" action:nil keyEquivalent:@""];
|
||||
NSMenu *sm = [[NSMenu alloc] initWithTitle:@"Apple"];
|
||||
[m setSubmenu:sm forItem:item];
|
||||
[sm addItemWithTitle: @"mpv_command('stop')" action:@selector(mpv_stop) keyEquivalent:@""];
|
||||
[sm addItemWithTitle: @"mpv_command('quit')" action:@selector(mpv_quit) keyEquivalent:@""];
|
||||
[sm addItemWithTitle: @"quit" action:@selector(terminate:) keyEquivalent:@"q"];
|
||||
[NSApp setMenu:m];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}
|
||||
|
||||
- (void) applicationDidFinishLaunching:(NSNotification *)notification {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
atexit_b(^{
|
||||
// Because activation policy has just been set to behave like a real
|
||||
// application, that policy must be reset on exit to prevent, among
|
||||
// other things, the menubar created here from remaining on screen.
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
|
||||
});
|
||||
|
||||
// Read filename
|
||||
NSArray *args = [NSProcessInfo processInfo].arguments;
|
||||
if (args.count < 2) {
|
||||
NSLog(@"Expected filename on command line");
|
||||
exit(1);
|
||||
}
|
||||
NSString *filename = args[1];
|
||||
|
||||
[self createWindow];
|
||||
|
||||
// Deal with MPV in the background.
|
||||
queue = dispatch_queue_create("mpv", DISPATCH_QUEUE_SERIAL);
|
||||
dispatch_async(queue, ^{
|
||||
|
||||
mpv = mpv_create();
|
||||
if (!mpv) {
|
||||
printf("failed creating context\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int64_t wid = (intptr_t) self->wrapper;
|
||||
check_error(mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid));
|
||||
|
||||
// Maybe set some options here, like default key bindings.
|
||||
// NOTE: Interaction with the window seems to be broken for now.
|
||||
check_error(mpv_set_option_string(mpv, "input-default-bindings", "yes"));
|
||||
|
||||
// for testing!
|
||||
check_error(mpv_set_option_string(mpv, "input-media-keys", "yes"));
|
||||
check_error(mpv_set_option_string(mpv, "input-cursor", "no"));
|
||||
check_error(mpv_set_option_string(mpv, "input-vo-keyboard", "yes"));
|
||||
|
||||
// request important errors
|
||||
check_error(mpv_request_log_messages(mpv, "warn"));
|
||||
|
||||
check_error(mpv_initialize(mpv));
|
||||
|
||||
// Register to be woken up whenever mpv generates new events.
|
||||
mpv_set_wakeup_callback(mpv, wakeup, (__bridge void *) self);
|
||||
|
||||
// Load the indicated file
|
||||
const char *cmd[] = {"loadfile", filename.UTF8String, NULL};
|
||||
check_error(mpv_command(mpv, cmd));
|
||||
});
|
||||
}
|
||||
|
||||
- (void) handleEvent:(mpv_event *)event
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_SHUTDOWN: {
|
||||
mpv_detach_destroy(mpv);
|
||||
mpv = NULL;
|
||||
printf("event: shutdown\n");
|
||||
break;
|
||||
}
|
||||
|
||||
case MPV_EVENT_LOG_MESSAGE: {
|
||||
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
|
||||
printf("[%s] %s: %s", msg->prefix, msg->level, msg->text);
|
||||
}
|
||||
|
||||
case MPV_EVENT_VIDEO_RECONFIG: {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSArray *subviews = [self->wrapper subviews];
|
||||
if ([subviews count] > 0) {
|
||||
// mpv's events view
|
||||
NSView *eview = [self->wrapper subviews][0];
|
||||
[self->w makeFirstResponder:eview];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
printf("event: %s\n", mpv_event_name(event->event_id));
|
||||
}
|
||||
}
|
||||
|
||||
- (void) readEvents
|
||||
{
|
||||
dispatch_async(queue, ^{
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
[self handleEvent:event];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void wakeup(void *context) {
|
||||
AppDelegate *a = (__bridge AppDelegate *) context;
|
||||
[a readEvents];
|
||||
}
|
||||
|
||||
// Ostensibly, mpv's window would be hooked up to this.
|
||||
- (BOOL) windowShouldClose:(id)sender
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) mpv_stop
|
||||
{
|
||||
if (mpv) {
|
||||
const char *args[] = {"stop", NULL};
|
||||
mpv_command(mpv, args);
|
||||
}
|
||||
}
|
||||
|
||||
- (void) mpv_quit
|
||||
{
|
||||
if (mpv) {
|
||||
const char *args[] = {"quit", NULL};
|
||||
mpv_command(mpv, args);
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
// Delete this if you already have a main.m.
|
||||
int main(int argc, const char * argv[]) {
|
||||
@autoreleasepool {
|
||||
NSApplication *app = [NSApplication sharedApplication];
|
||||
AppDelegate *delegate = [AppDelegate new];
|
||||
app.delegate = delegate;
|
||||
[app run];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
#include "main.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <clocale>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QOpenGLContext>
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include <QtGui/QOpenGLFramebufferObject>
|
||||
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QtQuick/QQuickView>
|
||||
|
||||
class MpvRenderer : public QQuickFramebufferObject::Renderer
|
||||
{
|
||||
static void *get_proc_address(void *ctx, const char *name) {
|
||||
(void)ctx;
|
||||
QOpenGLContext *glctx = QOpenGLContext::currentContext();
|
||||
if (!glctx)
|
||||
return NULL;
|
||||
return (void *)glctx->getProcAddress(QByteArray(name));
|
||||
}
|
||||
|
||||
mpv::qt::Handle mpv;
|
||||
QQuickWindow *window;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
public:
|
||||
MpvRenderer(const MpvObject *obj)
|
||||
: mpv(obj->mpv), window(obj->window()), mpv_gl(obj->mpv_gl)
|
||||
{
|
||||
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
|
||||
if (r < 0)
|
||||
throw std::runtime_error("could not initialize OpenGL");
|
||||
}
|
||||
|
||||
virtual ~MpvRenderer()
|
||||
{
|
||||
// Until this call is done, we need to make sure the player remains
|
||||
// alive. This is done implicitly with the mpv::qt::Handle instance
|
||||
// in this class.
|
||||
mpv_opengl_cb_uninit_gl(mpv_gl);
|
||||
}
|
||||
|
||||
void render()
|
||||
{
|
||||
QOpenGLFramebufferObject *fbo = framebufferObject();
|
||||
window->resetOpenGLState();
|
||||
mpv_opengl_cb_draw(mpv_gl, fbo->handle(), fbo->width(), fbo->height());
|
||||
window->resetOpenGLState();
|
||||
}
|
||||
};
|
||||
|
||||
MpvObject::MpvObject(QQuickItem * parent)
|
||||
: QQuickFramebufferObject(parent), mpv_gl(0)
|
||||
{
|
||||
mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
|
||||
if (!mpv)
|
||||
throw std::runtime_error("could not create mpv context");
|
||||
|
||||
mpv_set_option_string(mpv, "terminal", "yes");
|
||||
mpv_set_option_string(mpv, "msg-level", "all=v");
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("could not initialize mpv context");
|
||||
|
||||
// Make use of the MPV_SUB_API_OPENGL_CB API.
|
||||
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
|
||||
|
||||
// Request hw decoding, just for testing.
|
||||
mpv::qt::set_option_variant(mpv, "hwdec", "auto");
|
||||
|
||||
// Setup the callback that will make QtQuick update and redraw if there
|
||||
// is a new video frame. Use a queued connection: this makes sure the
|
||||
// doUpdate() function is run on the GUI thread.
|
||||
mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpv_gl)
|
||||
throw std::runtime_error("OpenGL not compiled in");
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, MpvObject::on_update, (void *)this);
|
||||
connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate,
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
MpvObject::~MpvObject()
|
||||
{
|
||||
if (mpv_gl)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
|
||||
}
|
||||
|
||||
void MpvObject::on_update(void *ctx)
|
||||
{
|
||||
MpvObject *self = (MpvObject *)ctx;
|
||||
emit self->onUpdate();
|
||||
}
|
||||
|
||||
// connected to onUpdate(); signal makes sure it runs on the GUI thread
|
||||
void MpvObject::doUpdate()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void MpvObject::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::command_variant(mpv, params);
|
||||
}
|
||||
|
||||
void MpvObject::setProperty(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::set_property_variant(mpv, name, value);
|
||||
}
|
||||
|
||||
QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const
|
||||
{
|
||||
window()->setPersistentOpenGLContext(true);
|
||||
window()->setPersistentSceneGraph(true);
|
||||
return new MpvRenderer(this);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
// Qt sets the locale in the QGuiApplication constructor, but libmpv
|
||||
// requires the LC_NUMERIC category to be set to "C", so change it back.
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
qmlRegisterType<MpvObject>("mpvtest", 1, 0, "MpvObject");
|
||||
|
||||
QQuickView view;
|
||||
view.setResizeMode(QQuickView::SizeRootObjectToView);
|
||||
view.setSource(QUrl("qrc:///mpvtest/main.qml"));
|
||||
view.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef MPVRENDERER_H_
|
||||
#define MPVRENDERER_H_
|
||||
|
||||
#include <QtQuick/QQuickFramebufferObject>
|
||||
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
class MpvRenderer;
|
||||
|
||||
class MpvObject : public QQuickFramebufferObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
|
||||
friend class MpvRenderer;
|
||||
|
||||
public:
|
||||
MpvObject(QQuickItem * parent = 0);
|
||||
virtual ~MpvObject();
|
||||
virtual Renderer *createRenderer() const;
|
||||
public slots:
|
||||
void command(const QVariant& params);
|
||||
void setProperty(const QString& name, const QVariant& value);
|
||||
signals:
|
||||
void onUpdate();
|
||||
private slots:
|
||||
void doUpdate();
|
||||
private:
|
||||
static void on_update(void *ctx);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,71 +0,0 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 1.0
|
||||
|
||||
import mpvtest 1.0
|
||||
|
||||
Item {
|
||||
width: 1280
|
||||
height: 720
|
||||
|
||||
MpvObject {
|
||||
id: renderer
|
||||
anchors.fill: parent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: renderer.command(["loadfile", "../../../test.mkv"])
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: labelFrame
|
||||
anchors.margins: -50
|
||||
radius: 5
|
||||
color: "white"
|
||||
border.color: "black"
|
||||
opacity: 0.8
|
||||
anchors.fill: box
|
||||
}
|
||||
|
||||
Row {
|
||||
id: box
|
||||
anchors.bottom: renderer.bottom
|
||||
anchors.left: renderer.left
|
||||
anchors.right: renderer.right
|
||||
anchors.margins: 100
|
||||
|
||||
Text {
|
||||
anchors.margins: 10
|
||||
wrapMode: Text.WordWrap
|
||||
text: "QtQuick and mpv are both rendering stuff.\n
|
||||
Click to load ../../../test.mkv"
|
||||
}
|
||||
|
||||
// Don't take these controls too seriously. They're for testing.
|
||||
Column {
|
||||
CheckBox {
|
||||
id: checkbox
|
||||
anchors.margins: 10
|
||||
// Heavily filtered means good, right?
|
||||
text: "Make video look like on a Smart TV"
|
||||
onClicked: {
|
||||
if (checkbox.checked) {
|
||||
renderer.command(["vo_cmdline", "sharpen=5.0"])
|
||||
} else {
|
||||
renderer.command(["vo_cmdline", ""])
|
||||
}
|
||||
}
|
||||
}
|
||||
Slider {
|
||||
id: slider
|
||||
anchors.margins: 10
|
||||
anchors.left: checkbox.left
|
||||
anchors.right: checkbox.right
|
||||
minimumValue: -100
|
||||
maximumValue: 100
|
||||
value: 0
|
||||
onValueChanged: renderer.setProperty("gamma", slider.value | 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
QT += qml quick
|
||||
|
||||
HEADERS += main.h
|
||||
SOURCES += main.cpp
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
RESOURCES += mpvtest.qrc
|
||||
|
||||
OTHER_FILES += main.qml
|
||||
@@ -1,5 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/mpvtest">
|
||||
<file>main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -1,159 +0,0 @@
|
||||
#include "main.h"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <clocale>
|
||||
|
||||
#include <QObject>
|
||||
#include <QtGlobal>
|
||||
#include <QOpenGLContext>
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QtQuick/QQuickView>
|
||||
|
||||
static void *get_proc_address(void *ctx, const char *name) {
|
||||
(void)ctx;
|
||||
QOpenGLContext *glctx = QOpenGLContext::currentContext();
|
||||
if (!glctx)
|
||||
return NULL;
|
||||
return (void *)glctx->getProcAddress(QByteArray(name));
|
||||
}
|
||||
|
||||
MpvRenderer::MpvRenderer(mpv::qt::Handle a_mpv, mpv_opengl_cb_context *a_mpv_gl)
|
||||
: mpv(a_mpv), mpv_gl(a_mpv_gl), window(0), size()
|
||||
{
|
||||
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
|
||||
if (r < 0)
|
||||
throw std::runtime_error("could not initialize OpenGL");
|
||||
}
|
||||
|
||||
MpvRenderer::~MpvRenderer()
|
||||
{
|
||||
// Until this call is done, we need to make sure the player remains
|
||||
// alive. This is done implicitly with the mpv::qt::Handle instance
|
||||
// in this class.
|
||||
mpv_opengl_cb_uninit_gl(mpv_gl);
|
||||
}
|
||||
|
||||
void MpvRenderer::paint()
|
||||
{
|
||||
window->resetOpenGLState();
|
||||
|
||||
// This uses 0 as framebuffer, which indicates that mpv will render directly
|
||||
// to the frontbuffer. Note that mpv will always switch framebuffers
|
||||
// explicitly. Some QWindow setups (such as using QQuickWidget) actually
|
||||
// want you to render into a FBO in the beforeRendering() signal, and this
|
||||
// code won't work there.
|
||||
// The negation is used for rendering with OpenGL's flipped coordinates.
|
||||
mpv_opengl_cb_draw(mpv_gl, 0, size.width(), -size.height());
|
||||
|
||||
window->resetOpenGLState();
|
||||
}
|
||||
|
||||
MpvObject::MpvObject(QQuickItem * parent)
|
||||
: QQuickItem(parent), mpv_gl(0), renderer(0)
|
||||
{
|
||||
mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
|
||||
if (!mpv)
|
||||
throw std::runtime_error("could not create mpv context");
|
||||
|
||||
mpv_set_option_string(mpv, "terminal", "yes");
|
||||
mpv_set_option_string(mpv, "msg-level", "all=v");
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("could not initialize mpv context");
|
||||
|
||||
// Make use of the MPV_SUB_API_OPENGL_CB API.
|
||||
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
|
||||
|
||||
// Setup the callback that will make QtQuick update and redraw if there
|
||||
// is a new video frame. Use a queued connection: this makes sure the
|
||||
// doUpdate() function is run on the GUI thread.
|
||||
mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpv_gl)
|
||||
throw std::runtime_error("OpenGL not compiled in");
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, MpvObject::on_update, (void *)this);
|
||||
connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &QQuickItem::windowChanged,
|
||||
this, &MpvObject::handleWindowChanged);
|
||||
}
|
||||
|
||||
MpvObject::~MpvObject()
|
||||
{
|
||||
if (mpv_gl)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
|
||||
}
|
||||
|
||||
void MpvObject::handleWindowChanged(QQuickWindow *win)
|
||||
{
|
||||
if (!win)
|
||||
return;
|
||||
connect(win, &QQuickWindow::beforeSynchronizing,
|
||||
this, &MpvObject::sync, Qt::DirectConnection);
|
||||
connect(win, &QQuickWindow::sceneGraphInvalidated,
|
||||
this, &MpvObject::cleanup, Qt::DirectConnection);
|
||||
connect(win, &QQuickWindow::frameSwapped,
|
||||
this, &MpvObject::swapped, Qt::DirectConnection);
|
||||
win->setClearBeforeRendering(false);
|
||||
}
|
||||
|
||||
void MpvObject::sync()
|
||||
{
|
||||
if (!renderer) {
|
||||
renderer = new MpvRenderer(mpv, mpv_gl);
|
||||
connect(window(), &QQuickWindow::beforeRendering,
|
||||
renderer, &MpvRenderer::paint, Qt::DirectConnection);
|
||||
}
|
||||
renderer->window = window();
|
||||
renderer->size = window()->size() * window()->devicePixelRatio();
|
||||
}
|
||||
|
||||
void MpvObject::swapped()
|
||||
{
|
||||
mpv_opengl_cb_report_flip(mpv_gl, 0);
|
||||
}
|
||||
|
||||
void MpvObject::cleanup()
|
||||
{
|
||||
if (renderer) {
|
||||
delete renderer;
|
||||
renderer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void MpvObject::on_update(void *ctx)
|
||||
{
|
||||
MpvObject *self = (MpvObject *)ctx;
|
||||
emit self->onUpdate();
|
||||
}
|
||||
|
||||
// connected to onUpdate(); signal makes sure it runs on the GUI thread
|
||||
void MpvObject::doUpdate()
|
||||
{
|
||||
window()->update();
|
||||
}
|
||||
|
||||
void MpvObject::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::command_variant(mpv, params);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
// Qt sets the locale in the QGuiApplication constructor, but libmpv
|
||||
// requires the LC_NUMERIC category to be set to "C", so change it back.
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
qmlRegisterType<MpvObject>("mpvtest", 1, 0, "MpvObject");
|
||||
|
||||
QQuickView view;
|
||||
view.setResizeMode(QQuickView::SizeRootObjectToView);
|
||||
view.setSource(QUrl("qrc:///mpvtest/main.qml"));
|
||||
view.show();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#ifndef MPVRENDERER_H_
|
||||
#define MPVRENDERER_H_
|
||||
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
class MpvRenderer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
QQuickWindow *window;
|
||||
QSize size;
|
||||
|
||||
friend class MpvObject;
|
||||
public:
|
||||
MpvRenderer(mpv::qt::Handle a_mpv, mpv_opengl_cb_context *a_mpv_gl);
|
||||
virtual ~MpvRenderer();
|
||||
public slots:
|
||||
void paint();
|
||||
};
|
||||
|
||||
class MpvObject : public QQuickItem
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
MpvRenderer *renderer;
|
||||
|
||||
public:
|
||||
MpvObject(QQuickItem * parent = 0);
|
||||
virtual ~MpvObject();
|
||||
public slots:
|
||||
void command(const QVariant& params);
|
||||
void sync();
|
||||
void swapped();
|
||||
void cleanup();
|
||||
signals:
|
||||
void onUpdate();
|
||||
private slots:
|
||||
void doUpdate();
|
||||
void handleWindowChanged(QQuickWindow *win);
|
||||
private:
|
||||
static void on_update(void *ctx);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,49 +0,0 @@
|
||||
import QtQuick 2.0
|
||||
import QtQuick.Controls 1.0
|
||||
|
||||
import mpvtest 1.0
|
||||
|
||||
Item {
|
||||
width: 1280
|
||||
height: 720
|
||||
|
||||
MpvObject {
|
||||
id: renderer
|
||||
|
||||
// This object isn't real and not visible; it just renders into the
|
||||
// background of the containing Window.
|
||||
width: 0
|
||||
height: 0
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: renderer.command(["loadfile", "../../../test.mkv"])
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: labelFrame
|
||||
anchors.margins: -50
|
||||
radius: 5
|
||||
color: "white"
|
||||
border.color: "black"
|
||||
opacity: 0.8
|
||||
anchors.fill: box
|
||||
}
|
||||
|
||||
Row {
|
||||
id: box
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 100
|
||||
|
||||
Text {
|
||||
anchors.margins: 10
|
||||
wrapMode: Text.WordWrap
|
||||
text: "QtQuick and mpv are both rendering stuff.\n
|
||||
In this example, mpv is always in the background.\n
|
||||
Click to load ../../../test.mkv"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
QT += qml quick
|
||||
|
||||
HEADERS += main.h
|
||||
SOURCES += main.cpp
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
RESOURCES += mpvtest.qrc
|
||||
|
||||
OTHER_FILES += main.qml
|
||||
@@ -1,5 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/mpvtest">
|
||||
<file>main.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@@ -1,225 +0,0 @@
|
||||
// This example can be built with: qmake && make
|
||||
|
||||
#include <clocale>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QFileDialog>
|
||||
#include <QStatusBar>
|
||||
#include <QMenuBar>
|
||||
#include <QMenu>
|
||||
#include <QGridLayout>
|
||||
#include <QApplication>
|
||||
#include <QTextEdit>
|
||||
|
||||
#if QT_VERSION >= 0x050000
|
||||
#include <QJsonDocument>
|
||||
#endif
|
||||
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
#include "qtexample.h"
|
||||
|
||||
static void wakeup(void *ctx)
|
||||
{
|
||||
// This callback is invoked from any mpv thread (but possibly also
|
||||
// recursively from a thread that is calling the mpv API). Just notify
|
||||
// the Qt GUI thread to wake up (so that it can process events with
|
||||
// mpv_wait_event()), and return as quickly as possible.
|
||||
MainWindow *mainwindow = (MainWindow *)ctx;
|
||||
emit mainwindow->mpv_events();
|
||||
}
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
QMainWindow(parent)
|
||||
{
|
||||
setWindowTitle("Qt embedding demo");
|
||||
setMinimumSize(640, 480);
|
||||
|
||||
QMenu *menu = menuBar()->addMenu(tr("&File"));
|
||||
QAction *on_open = new QAction(tr("&Open"), this);
|
||||
on_open->setShortcuts(QKeySequence::Open);
|
||||
on_open->setStatusTip(tr("Open a file"));
|
||||
connect(on_open, &QAction::triggered, this, &MainWindow::on_file_open);
|
||||
menu->addAction(on_open);
|
||||
|
||||
QAction *on_new = new QAction(tr("&New window"), this);
|
||||
connect(on_new, &QAction::triggered, this, &MainWindow::on_new_window);
|
||||
menu->addAction(on_new);
|
||||
|
||||
statusBar();
|
||||
|
||||
QMainWindow *log_window = new QMainWindow(this);
|
||||
log = new QTextEdit(log_window);
|
||||
log->setReadOnly(true);
|
||||
log_window->setCentralWidget(log);
|
||||
log_window->setWindowTitle("mpv log window");
|
||||
log_window->setMinimumSize(500, 50);
|
||||
log_window->show();
|
||||
|
||||
mpv = mpv_create();
|
||||
if (!mpv)
|
||||
throw std::runtime_error("can't create mpv instance");
|
||||
|
||||
// Create a video child window. Force Qt to create a native window, and
|
||||
// pass the window ID to the mpv wid option. Works on: X11, win32, Cocoa
|
||||
mpv_container = new QWidget(this);
|
||||
setCentralWidget(mpv_container);
|
||||
mpv_container->setAttribute(Qt::WA_DontCreateNativeAncestors);
|
||||
mpv_container->setAttribute(Qt::WA_NativeWindow);
|
||||
// If you have a HWND, use: int64_t wid = (intptr_t)hwnd;
|
||||
int64_t wid = mpv_container->winId();
|
||||
mpv_set_option(mpv, "wid", MPV_FORMAT_INT64, &wid);
|
||||
|
||||
// Enable default bindings, because we're lazy. Normally, a player using
|
||||
// mpv as backend would implement its own key bindings.
|
||||
mpv_set_option_string(mpv, "input-default-bindings", "yes");
|
||||
|
||||
// Enable keyboard input on the X11 window. For the messy details, see
|
||||
// --input-vo-keyboard on the manpage.
|
||||
mpv_set_option_string(mpv, "input-vo-keyboard", "yes");
|
||||
|
||||
// Let us receive property change events with MPV_EVENT_PROPERTY_CHANGE if
|
||||
// this property changes.
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
|
||||
mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE);
|
||||
mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE);
|
||||
|
||||
// Request log messages with level "info" or higher.
|
||||
// They are received as MPV_EVENT_LOG_MESSAGE.
|
||||
mpv_request_log_messages(mpv, "info");
|
||||
|
||||
// From this point on, the wakeup function will be called. The callback
|
||||
// can come from any thread, so we use the QueuedConnection mechanism to
|
||||
// relay the wakeup in a thread-safe way.
|
||||
connect(this, &MainWindow::mpv_events, this, &MainWindow::on_mpv_events,
|
||||
Qt::QueuedConnection);
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("mpv failed to initialize");
|
||||
}
|
||||
|
||||
void MainWindow::handle_mpv_event(mpv_event *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_PROPERTY_CHANGE: {
|
||||
mpv_event_property *prop = (mpv_event_property *)event->data;
|
||||
if (strcmp(prop->name, "time-pos") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
std::stringstream ss;
|
||||
ss << "At: " << time;
|
||||
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
||||
} else if (prop->format == MPV_FORMAT_NONE) {
|
||||
// The property is unavailable, which probably means playback
|
||||
// was stopped.
|
||||
statusBar()->showMessage("");
|
||||
}
|
||||
} else if (strcmp(prop->name, "chapter-list") == 0 ||
|
||||
strcmp(prop->name, "track-list") == 0)
|
||||
{
|
||||
// Dump the properties as JSON for demo purposes.
|
||||
#if QT_VERSION >= 0x050000
|
||||
if (prop->format == MPV_FORMAT_NODE) {
|
||||
QVariant v = mpv::qt::node_to_variant((mpv_node *)prop->data);
|
||||
// Abuse JSON support for easily printing the mpv_node contents.
|
||||
QJsonDocument d = QJsonDocument::fromVariant(v);
|
||||
append_log("Change property " + QString(prop->name) + ":\n");
|
||||
append_log(d.toJson().data());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_VIDEO_RECONFIG: {
|
||||
// Retrieve the new video size.
|
||||
int64_t w, h;
|
||||
if (mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) >= 0 &&
|
||||
mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) >= 0 &&
|
||||
w > 0 && h > 0)
|
||||
{
|
||||
// Note that the MPV_EVENT_VIDEO_RECONFIG event doesn't necessarily
|
||||
// imply a resize, and you should check yourself if the video
|
||||
// dimensions really changed.
|
||||
// mpv itself will scale/letter box the video to the container size
|
||||
// if the video doesn't fit.
|
||||
std::stringstream ss;
|
||||
ss << "Reconfig: " << w << " " << h;
|
||||
statusBar()->showMessage(QString::fromStdString(ss.str()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_LOG_MESSAGE: {
|
||||
struct mpv_event_log_message *msg = (struct mpv_event_log_message *)event->data;
|
||||
std::stringstream ss;
|
||||
ss << "[" << msg->prefix << "] " << msg->level << ": " << msg->text;
|
||||
append_log(QString::fromStdString(ss.str()));
|
||||
break;
|
||||
}
|
||||
case MPV_EVENT_SHUTDOWN: {
|
||||
mpv_terminate_destroy(mpv);
|
||||
mpv = NULL;
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
// Ignore uninteresting or unknown events.
|
||||
}
|
||||
}
|
||||
|
||||
// This slot is invoked by wakeup() (through the mpv_events signal).
|
||||
void MainWindow::on_mpv_events()
|
||||
{
|
||||
// Process all events, until the event queue is empty.
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
handle_mpv_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_file_open()
|
||||
{
|
||||
QString filename = QFileDialog::getOpenFileName(this, "Open file");
|
||||
if (mpv) {
|
||||
const QByteArray c_filename = filename.toUtf8();
|
||||
const char *args[] = {"loadfile", c_filename.data(), NULL};
|
||||
mpv_command_async(mpv, 0, args);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::on_new_window()
|
||||
{
|
||||
(new MainWindow())->show();
|
||||
}
|
||||
|
||||
void MainWindow::append_log(const QString &text)
|
||||
{
|
||||
QTextCursor cursor = log->textCursor();
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
cursor.insertText(text);
|
||||
log->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
if (mpv)
|
||||
mpv_terminate_destroy(mpv);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
// Qt sets the locale in the QApplication constructor, but libmpv requires
|
||||
// the LC_NUMERIC category to be set to "C", so change it back.
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#ifndef QTEXAMPLE_H
|
||||
#define QTEXAMPLE_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
class QTextEdit;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
~MainWindow();
|
||||
|
||||
private slots:
|
||||
void on_file_open();
|
||||
void on_new_window();
|
||||
void on_mpv_events();
|
||||
|
||||
signals:
|
||||
void mpv_events();
|
||||
|
||||
private:
|
||||
QWidget *mpv_container;
|
||||
mpv_handle *mpv;
|
||||
QTextEdit *log;
|
||||
|
||||
void append_log(const QString &text);
|
||||
|
||||
void create_player();
|
||||
void handle_mpv_event(mpv_event *event);
|
||||
};
|
||||
|
||||
#endif // QTEXAMPLE_H
|
||||
@@ -1,13 +0,0 @@
|
||||
QT += core gui
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = qtexample
|
||||
TEMPLATE = app
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
SOURCES += qtexample.cpp
|
||||
HEADERS += qtexample.h
|
||||
@@ -1,13 +0,0 @@
|
||||
#include <QApplication>
|
||||
#include "mainwindow.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
// Qt sets the locale in the QApplication constructor, but libmpv requires
|
||||
// the LC_NUMERIC category to be set to "C", so change it back.
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
MainWindow w;
|
||||
w.show();
|
||||
return a.exec();
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "mainwindow.h"
|
||||
#include "mpvwidget.h"
|
||||
#include <QPushButton>
|
||||
#include <QSlider>
|
||||
#include <QLayout>
|
||||
#include <QFileDialog>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
m_mpv = new MpvWidget(this);
|
||||
m_slider = new QSlider();
|
||||
m_slider->setOrientation(Qt::Horizontal);
|
||||
m_openBtn = new QPushButton("Open");
|
||||
m_playBtn = new QPushButton("Pause");
|
||||
QHBoxLayout *hb = new QHBoxLayout();
|
||||
hb->addWidget(m_openBtn);
|
||||
hb->addWidget(m_playBtn);
|
||||
QVBoxLayout *vl = new QVBoxLayout();
|
||||
vl->addWidget(m_mpv);
|
||||
vl->addWidget(m_slider);
|
||||
vl->addLayout(hb);
|
||||
setLayout(vl);
|
||||
connect(m_slider, SIGNAL(sliderMoved(int)), SLOT(seek(int)));
|
||||
connect(m_openBtn, SIGNAL(clicked()), SLOT(openMedia()));
|
||||
connect(m_playBtn, SIGNAL(clicked()), SLOT(pauseResume()));
|
||||
connect(m_mpv, SIGNAL(positionChanged(int)), m_slider, SLOT(setValue(int)));
|
||||
connect(m_mpv, SIGNAL(durationChanged(int)), this, SLOT(setSliderRange(int)));
|
||||
}
|
||||
|
||||
void MainWindow::openMedia()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(0, "Open a video");
|
||||
if (file.isEmpty())
|
||||
return;
|
||||
m_mpv->command(QStringList() << "loadfile" << file);
|
||||
}
|
||||
|
||||
void MainWindow::seek(int pos)
|
||||
{
|
||||
m_mpv->command(QVariantList() << "seek" << pos << "absolute");
|
||||
}
|
||||
|
||||
void MainWindow::pauseResume()
|
||||
{
|
||||
const bool paused = m_mpv->getProperty("pause").toBool();
|
||||
m_mpv->setProperty("pause", !paused);
|
||||
}
|
||||
|
||||
void MainWindow::setSliderRange(int duration)
|
||||
{
|
||||
m_slider->setRange(0, duration);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#ifndef MainWindow_H
|
||||
#define MainWindow_H
|
||||
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class MpvWidget;
|
||||
class QSlider;
|
||||
class QPushButton;
|
||||
class MainWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = 0);
|
||||
public Q_SLOTS:
|
||||
void openMedia();
|
||||
void seek(int pos);
|
||||
void pauseResume();
|
||||
private Q_SLOTS:
|
||||
void setSliderRange(int duration);
|
||||
private:
|
||||
MpvWidget *m_mpv;
|
||||
QSlider *m_slider;
|
||||
QPushButton *m_openBtn;
|
||||
QPushButton *m_playBtn;
|
||||
};
|
||||
|
||||
#endif // MainWindow_H
|
||||
@@ -1,129 +0,0 @@
|
||||
#include "mpvwidget.h"
|
||||
#include <stdexcept>
|
||||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtCore/QMetaObject>
|
||||
|
||||
static void wakeup(void *ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod((MpvWidget*)ctx, "on_mpv_events", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
static void *get_proc_address(void *ctx, const char *name) {
|
||||
Q_UNUSED(ctx);
|
||||
QOpenGLContext *glctx = QOpenGLContext::currentContext();
|
||||
if (!glctx)
|
||||
return NULL;
|
||||
return (void *)glctx->getProcAddress(QByteArray(name));
|
||||
}
|
||||
|
||||
MpvWidget::MpvWidget(QWidget *parent, Qt::WindowFlags f)
|
||||
: QOpenGLWidget(parent, f)
|
||||
{
|
||||
mpv = mpv::qt::Handle::FromRawHandle(mpv_create());
|
||||
if (!mpv)
|
||||
throw std::runtime_error("could not create mpv context");
|
||||
|
||||
mpv_set_option_string(mpv, "terminal", "yes");
|
||||
mpv_set_option_string(mpv, "msg-level", "all=v");
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
throw std::runtime_error("could not initialize mpv context");
|
||||
|
||||
// Make use of the MPV_SUB_API_OPENGL_CB API.
|
||||
mpv::qt::set_option_variant(mpv, "vo", "opengl-cb");
|
||||
|
||||
// Request hw decoding, just for testing.
|
||||
mpv::qt::set_option_variant(mpv, "hwdec", "auto");
|
||||
|
||||
mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpv_gl)
|
||||
throw std::runtime_error("OpenGL not compiled in");
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, MpvWidget::on_update, (void *)this);
|
||||
connect(this, SIGNAL(frameSwapped()), SLOT(swapped()));
|
||||
|
||||
mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
|
||||
mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE);
|
||||
mpv_set_wakeup_callback(mpv, wakeup, this);
|
||||
}
|
||||
|
||||
MpvWidget::~MpvWidget()
|
||||
{
|
||||
makeCurrent();
|
||||
if (mpv_gl)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, NULL, NULL);
|
||||
// Until this call is done, we need to make sure the player remains
|
||||
// alive. This is done implicitly with the mpv::qt::Handle instance
|
||||
// in this class.
|
||||
mpv_opengl_cb_uninit_gl(mpv_gl);
|
||||
}
|
||||
|
||||
void MpvWidget::command(const QVariant& params)
|
||||
{
|
||||
mpv::qt::command_variant(mpv, params);
|
||||
}
|
||||
|
||||
void MpvWidget::setProperty(const QString& name, const QVariant& value)
|
||||
{
|
||||
mpv::qt::set_property_variant(mpv, name, value);
|
||||
}
|
||||
|
||||
QVariant MpvWidget::getProperty(const QString &name) const
|
||||
{
|
||||
return mpv::qt::get_property_variant(mpv, name);
|
||||
}
|
||||
|
||||
void MpvWidget::initializeGL()
|
||||
{
|
||||
int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL);
|
||||
if (r < 0)
|
||||
throw std::runtime_error("could not initialize OpenGL");
|
||||
}
|
||||
|
||||
void MpvWidget::paintGL()
|
||||
{
|
||||
mpv_opengl_cb_draw(mpv_gl, defaultFramebufferObject(), width(), -height());
|
||||
}
|
||||
|
||||
void MpvWidget::swapped()
|
||||
{
|
||||
mpv_opengl_cb_report_flip(mpv_gl, 0);
|
||||
}
|
||||
|
||||
void MpvWidget::on_mpv_events()
|
||||
{
|
||||
// Process all events, until the event queue is empty.
|
||||
while (mpv) {
|
||||
mpv_event *event = mpv_wait_event(mpv, 0);
|
||||
if (event->event_id == MPV_EVENT_NONE) {
|
||||
break;
|
||||
}
|
||||
handle_mpv_event(event);
|
||||
}
|
||||
}
|
||||
|
||||
void MpvWidget::handle_mpv_event(mpv_event *event)
|
||||
{
|
||||
switch (event->event_id) {
|
||||
case MPV_EVENT_PROPERTY_CHANGE: {
|
||||
mpv_event_property *prop = (mpv_event_property *)event->data;
|
||||
if (strcmp(prop->name, "time-pos") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
Q_EMIT positionChanged(time);
|
||||
}
|
||||
} else if (strcmp(prop->name, "duration") == 0) {
|
||||
if (prop->format == MPV_FORMAT_DOUBLE) {
|
||||
double time = *(double *)prop->data;
|
||||
Q_EMIT durationChanged(time);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: ;
|
||||
// Ignore uninteresting or unknown events.
|
||||
}
|
||||
}
|
||||
|
||||
void MpvWidget::on_update(void *ctx)
|
||||
{
|
||||
QMetaObject::invokeMethod((MpvWidget*)ctx, "update");
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#ifndef PLAYERWINDOW_H
|
||||
#define PLAYERWINDOW_H
|
||||
|
||||
#include <QtWidgets/QOpenGLWidget>
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
#include <mpv/qthelper.hpp>
|
||||
|
||||
class MpvWidget Q_DECL_FINAL: public QOpenGLWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MpvWidget(QWidget *parent = 0, Qt::WindowFlags f = 0);
|
||||
~MpvWidget();
|
||||
void command(const QVariant& params);
|
||||
void setProperty(const QString& name, const QVariant& value);
|
||||
QVariant getProperty(const QString& name) const;
|
||||
QSize sizeHint() const { return QSize(480, 270);}
|
||||
Q_SIGNALS:
|
||||
void durationChanged(int value);
|
||||
void positionChanged(int value);
|
||||
protected:
|
||||
void initializeGL() Q_DECL_OVERRIDE;
|
||||
void paintGL() Q_DECL_OVERRIDE;
|
||||
private Q_SLOTS:
|
||||
void swapped();
|
||||
void on_mpv_events();
|
||||
private:
|
||||
void handle_mpv_event(mpv_event *event);
|
||||
static void on_update(void *ctx);
|
||||
|
||||
mpv::qt::Handle mpv;
|
||||
mpv_opengl_cb_context *mpv_gl;
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // PLAYERWINDOW_H
|
||||
@@ -1,13 +0,0 @@
|
||||
CONFIG -= app_bundle
|
||||
QT += widgets
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
HEADERS = \
|
||||
mpvwidget.h \
|
||||
mainwindow.h
|
||||
SOURCES = main.cpp \
|
||||
mpvwidget.cpp \
|
||||
mainwindow.cpp
|
||||
@@ -1,158 +0,0 @@
|
||||
// Build with: gcc -o main main.c `pkg-config --libs --cflags mpv sdl2`
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/opengl_cb.h>
|
||||
|
||||
static Uint32 wakeup_on_mpv_redraw, wakeup_on_mpv_events;
|
||||
|
||||
static void die(const char *msg)
|
||||
{
|
||||
fprintf(stderr, "%s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void *get_proc_address_mpv(void *fn_ctx, const char *name)
|
||||
{
|
||||
return SDL_GL_GetProcAddress(name);
|
||||
}
|
||||
|
||||
static void on_mpv_events(void *ctx)
|
||||
{
|
||||
SDL_Event event = {.type = wakeup_on_mpv_events};
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
static void on_mpv_redraw(void *ctx)
|
||||
{
|
||||
SDL_Event event = {.type = wakeup_on_mpv_redraw};
|
||||
SDL_PushEvent(&event);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2)
|
||||
die("pass a single media file as argument");
|
||||
|
||||
mpv_handle *mpv = mpv_create();
|
||||
if (!mpv)
|
||||
die("context init failed");
|
||||
|
||||
// Some minor options can only be set before mpv_initialize().
|
||||
if (mpv_initialize(mpv) < 0)
|
||||
die("mpv init failed");
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||
die("SDL init failed");
|
||||
|
||||
SDL_Window *window =
|
||||
SDL_CreateWindow("hi", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||
1000, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN |
|
||||
SDL_WINDOW_RESIZABLE);
|
||||
if (!window)
|
||||
die("failed to create SDL window");
|
||||
|
||||
// The OpenGL API is somewhat separate from the normal mpv API. This only
|
||||
// returns NULL if no OpenGL support is compiled.
|
||||
mpv_opengl_cb_context *mpv_gl = mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
|
||||
if (!mpv_gl)
|
||||
die("failed to create mpv GL API handle");
|
||||
|
||||
SDL_GLContext glcontext = SDL_GL_CreateContext(window);
|
||||
if (!glcontext)
|
||||
die("failed to create SDL GL context");
|
||||
|
||||
// This makes mpv use the currently set GL context. It will use the callback
|
||||
// to resolve GL builtin functions, as well as extensions.
|
||||
if (mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address_mpv, NULL) < 0)
|
||||
die("failed to initialize mpv GL context");
|
||||
|
||||
// Actually using the opengl_cb state has to be explicitly requested.
|
||||
// Otherwise, mpv will create a separate platform window.
|
||||
if (mpv_set_option_string(mpv, "vo", "opengl-cb") < 0)
|
||||
die("failed to set VO");
|
||||
|
||||
// We use events for thread-safe notification of the SDL main loop.
|
||||
// Generally, the wakeup callbacks (set further below) should do as least
|
||||
// work as possible, and merely wake up another thread to do actual work.
|
||||
// On SDL, waking up the mainloop is the ideal course of action. SDL's
|
||||
// SDL_PushEvent() is thread-safe, so we use that.
|
||||
wakeup_on_mpv_redraw = SDL_RegisterEvents(1);
|
||||
wakeup_on_mpv_events = SDL_RegisterEvents(1);
|
||||
if (wakeup_on_mpv_redraw == (Uint32)-1 || wakeup_on_mpv_events == (Uint32)-1)
|
||||
die("could not register events");
|
||||
|
||||
// When normal mpv events are available.
|
||||
mpv_set_wakeup_callback(mpv, on_mpv_events, NULL);
|
||||
|
||||
// When a new frame should be drawn with mpv_opengl_cb_draw().
|
||||
// (Separate from the normal event handling mechanism for the sake of
|
||||
// users which run OpenGL on a different thread.)
|
||||
mpv_opengl_cb_set_update_callback(mpv_gl, on_mpv_redraw, NULL);
|
||||
|
||||
// Play this file. Note that this starts playback asynchronously.
|
||||
const char *cmd[] = {"loadfile", argv[1], NULL};
|
||||
mpv_command(mpv, cmd);
|
||||
|
||||
while (1) {
|
||||
SDL_Event event;
|
||||
if (SDL_WaitEvent(&event) != 1)
|
||||
die("event loop error");
|
||||
int redraw = 0;
|
||||
switch (event.type) {
|
||||
case SDL_QUIT:
|
||||
goto done;
|
||||
case SDL_WINDOWEVENT:
|
||||
if (event.window.event == SDL_WINDOWEVENT_EXPOSED)
|
||||
redraw = 1;
|
||||
break;
|
||||
case SDL_KEYDOWN:
|
||||
if (event.key.keysym.sym == SDLK_SPACE)
|
||||
mpv_command_string(mpv, "cycle pause");
|
||||
break;
|
||||
default:
|
||||
// Happens when a new video frame should be rendered, or if the
|
||||
// current frame has to be redrawn e.g. due to OSD changes.
|
||||
if (event.type == wakeup_on_mpv_redraw)
|
||||
redraw = 1;
|
||||
// Happens when at least 1 new event is in the mpv event queue.
|
||||
if (event.type == wakeup_on_mpv_events) {
|
||||
// Handle all remaining mpv events.
|
||||
while (1) {
|
||||
mpv_event *mp_event = mpv_wait_event(mpv, 0);
|
||||
if (mp_event->event_id == MPV_EVENT_NONE)
|
||||
break;
|
||||
printf("event: %s\n", mpv_event_name(mp_event->event_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (redraw) {
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window, &w, &h);
|
||||
// Note:
|
||||
// - The 0 is the FBO to use; 0 is the default framebuffer (i.e.
|
||||
// render to the window directly.
|
||||
// - The negative height tells mpv to flip the coordinate system.
|
||||
// - If you do not want the video to cover the whole screen, or want
|
||||
// to apply any form of fancy transformation, you will have to
|
||||
// render to a FBO.
|
||||
// - See opengl_cb.h on what OpenGL environment mpv expects, and
|
||||
// other API details.
|
||||
mpv_opengl_cb_draw(mpv_gl, 0, w, -h);
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
}
|
||||
done:
|
||||
|
||||
// Destroy the GL renderer and all of the GL objects it allocated. If video
|
||||
// is still running, the video track will be deselected.
|
||||
mpv_opengl_cb_uninit_gl(mpv_gl);
|
||||
|
||||
mpv_terminate_destroy(mpv);
|
||||
return 0;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
// Build with: gcc -o simple simple.c `pkg-config --libs --cflags mpv`
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
static inline void check_error(int status)
|
||||
{
|
||||
if (status < 0) {
|
||||
printf("mpv API error: %s\n", mpv_error_string(status));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
if (argc != 2) {
|
||||
printf("pass a single media file as argument\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
mpv_handle *ctx = mpv_create();
|
||||
if (!ctx) {
|
||||
printf("failed creating context\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Enable default key bindings, so the user can actually interact with
|
||||
// the player (and e.g. close the window).
|
||||
check_error(mpv_set_option_string(ctx, "input-default-bindings", "yes"));
|
||||
mpv_set_option_string(ctx, "input-vo-keyboard", "yes");
|
||||
int val = 1;
|
||||
check_error(mpv_set_option(ctx, "osc", MPV_FORMAT_FLAG, &val));
|
||||
|
||||
// Done setting up options.
|
||||
check_error(mpv_initialize(ctx));
|
||||
|
||||
// Play this file.
|
||||
const char *cmd[] = {"loadfile", argv[1], NULL};
|
||||
check_error(mpv_command(ctx, cmd));
|
||||
|
||||
// Let it play, and wait until the user quits.
|
||||
while (1) {
|
||||
mpv_event *event = mpv_wait_event(ctx, 10000);
|
||||
printf("event: %s\n", mpv_event_name(event->event_id));
|
||||
if (event->event_id == MPV_EVENT_SHUTDOWN)
|
||||
break;
|
||||
}
|
||||
|
||||
mpv_terminate_destroy(ctx);
|
||||
return 0;
|
||||
}
|
||||
+30
-42
@@ -68,7 +68,7 @@ echo "MXE_TARGETS := i686-w64-mingw32.static" >> settings.mk
|
||||
# Build required packages. The following provide a minimum required to build
|
||||
# a reasonable mpv binary (though not an absolute minimum).
|
||||
|
||||
make gcc ffmpeg libass jpeg pthreads lua
|
||||
make gcc ffmpeg libass jpeg lua
|
||||
|
||||
# Add MXE binaries to $PATH
|
||||
export PATH=/opt/mxe/usr/bin/:$PATH
|
||||
@@ -104,22 +104,21 @@ Installing MSYS2
|
||||
It doesn't matter whether the i686 or the x86_64 version is used. Both can
|
||||
build 32-bit and 64-bit binaries when running on a 64-bit version of Windows.
|
||||
|
||||
2. Add ``C:\msys64\mingw64\bin`` to your ``%PATH%``. mpv will depend on several
|
||||
DLLs in this folder.
|
||||
2. Start a MinGW-w64 shell (``mingw64_shell.bat``). Note that this is different
|
||||
from the MSYS2 shell that is started from the final installation dialog.
|
||||
|
||||
3. Start a MinGW-w64 shell (``mingw64_shell.bat``). For a 32-bit build, use
|
||||
``mingw32_shell.bat``.
|
||||
For a 32-bit build, use ``mingw32_shell.bat``.
|
||||
|
||||
Updating MSYS2
|
||||
--------------
|
||||
|
||||
To prevent errors during post-install, ``msys2-runtime`` and ``pacman`` must be
|
||||
updated first.
|
||||
To prevent errors during post-install, the MSYS2 core runtime must be updated
|
||||
separately.
|
||||
|
||||
```bash
|
||||
# Check for updates to msys2-runtime and pacman. If there were updates, restart
|
||||
# MSYS2 before continuing.
|
||||
pacman -Sy --needed msys2-runtime pacman
|
||||
# Check for core updates. If instructed, close the shell window and reopen it
|
||||
# before continuing.
|
||||
update-core
|
||||
|
||||
# Update everything else
|
||||
pacman -Su
|
||||
@@ -130,14 +129,14 @@ Installing mpv dependencies
|
||||
|
||||
```bash
|
||||
# Install MSYS2 build dependencies and a MinGW-w64 compiler
|
||||
pacman -S git pkg-config python3 mingw-w64-x86_64-gcc
|
||||
pacman -S git mingw-w64-x86_64-pkg-config python mingw-w64-x86_64-gcc
|
||||
|
||||
# Install the most important MinGW-w64 dependencies. libass, libbluray and
|
||||
# lcms2 are also pulled in as dependencies of ffmpeg.
|
||||
pacman -S mingw-w64-x86_64-ffmpeg mingw-w64-x86_64-libjpeg-turbo mingw-w64-x86_64-lua51
|
||||
|
||||
# Install additional (optional) dependencies
|
||||
pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess
|
||||
pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess mingw-w64-x86_64-angleproject-git
|
||||
```
|
||||
|
||||
For a 32-bit build, install ``mingw-w64-i686-*`` packages instead.
|
||||
@@ -157,14 +156,14 @@ Finally, compile and install mpv. Binaries will be installed to
|
||||
|
||||
```bash
|
||||
# For a 32-bit build, use --prefix=/mingw32 instead
|
||||
./waf configure CC=gcc --prefix=/mingw64
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --prefix=/mingw64
|
||||
./waf install
|
||||
```
|
||||
|
||||
Or, compile and install both libmpv and mpv:
|
||||
|
||||
```bash
|
||||
./waf configure CC=gcc --enable-libmpv-shared --prefix=/mingw64
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=/mingw64
|
||||
./waf install
|
||||
|
||||
# waf installs libmpv to the wrong directory, so fix it up
|
||||
@@ -174,39 +173,28 @@ sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc
|
||||
rmdir /mingw64/bin/pkgconfig
|
||||
```
|
||||
|
||||
Additional dependencies
|
||||
=======================
|
||||
Running mpv
|
||||
-----------
|
||||
|
||||
pthreads
|
||||
--------
|
||||
|
||||
mpv will use a pthreads wrapper by default. Either pthreads-win32 or
|
||||
winpthreads should work. The latter is packaged with most MinGW-w64
|
||||
environments, including MSYS2, so it shouldn't be a problem. If you don't have
|
||||
a pthreads wrapper or you want to build mpv without one, configure with:
|
||||
If you want to run mpv from the MinGW-w64 shell, you will find the experience
|
||||
much more pleasant if you use the ``winpty`` utility
|
||||
|
||||
```bash
|
||||
./waf configure --enable-win32-internal-pthreads
|
||||
pacman -S winpty
|
||||
winpty mpv.com ToS-4k-1920.mov
|
||||
```
|
||||
|
||||
libwaio
|
||||
-------
|
||||
If you want to move / copy ``mpv.exe`` and ``mpv.com`` to somewhere other than
|
||||
``/mingw64/bin/`` for use outside the MinGW-w64 shell, they will still depend on
|
||||
DLLs in that folder. The simplest solution is to add ``C:\msys64\mingw64\bin``
|
||||
to the windows system ``%PATH%``. Beware though that this can cause problems or
|
||||
confusion in Cygwin if that is also installed on the machine.
|
||||
|
||||
If you want to use ``--input-file``, you need libwaio. It's available from
|
||||
git://midipix.org/waio
|
||||
Use of the ANGLE OpenGL backend requires a copy of ``d3dcompiler_43.dll`` (yes,
|
||||
exactly 43) in the path or in the same folder as mpv. It must be of the same
|
||||
architecture (x86_64 / i686) as the mpv you compiled. You can find a copy in the
|
||||
official mpv builds:
|
||||
|
||||
To compile libwaio in MSYS2, run:
|
||||
https://mpv.srsfckn.biz/mpv-x86_64-20160118.7z
|
||||
|
||||
```bash
|
||||
git clone git://midipix.org/waio && cd waio
|
||||
|
||||
# 32-bit build, run from mingw32_shell.bat
|
||||
./build-mingw-nt32 lib-static CC=gcc AR=ar
|
||||
cp -r include/waio /mingw32/include
|
||||
cp lib32/libwaio.a /mingw32/lib
|
||||
|
||||
# 64-bit build, run from mingw64_shell.bat
|
||||
./build-mingw-nt64 lib-static CC=gcc AR=ar
|
||||
cp -r include/waio /mingw64/include
|
||||
cp lib64/libwaio.a /mingw64/lib
|
||||
```
|
||||
https://mpv.srsfckn.biz/mpv-i686-20160118.7z
|
||||
+11
-9
@@ -14,9 +14,11 @@ Sending patches
|
||||
``git format-patch``. diffs posted as pastebins (especially if the http link
|
||||
returns HTML) just cause extra work for everyone, because they lack commit
|
||||
message and authorship information.
|
||||
- When creating pull requests, be sure to test your changes. If you didn't, please
|
||||
say so in the pull request message.
|
||||
|
||||
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
|
||||
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
|
||||
repository root directory).
|
||||
- When creating pull requests, be sure to test your changes. If you didn't,
|
||||
please say so in the pull request message.
|
||||
- Write informative commit messages. Use present tense to describe the
|
||||
situation with the patch applied, and past tense for the situation before
|
||||
the change.
|
||||
@@ -27,8 +29,8 @@ Sending patches
|
||||
|
||||
For example, you fixed a crash in af_volume.c:
|
||||
|
||||
Bad: ``fixed the bug (wtf?)``
|
||||
Good: ``af_volume: fix crash due to null pointer access``
|
||||
- Bad: ``fixed the bug (wtf?)``
|
||||
- Good: ``af_volume: fix crash due to null pointer access``
|
||||
|
||||
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``.
|
||||
@@ -53,10 +55,10 @@ Sending patches
|
||||
something like reformatting a whole file, and hiding an actual functional
|
||||
change in the same commit.
|
||||
- If you add a new command line option, document it in options.rst. If you
|
||||
add a new input property, document it in input.rst.
|
||||
- All new code must be LGPLv2.1+ licensed, or come with the implicit agreement
|
||||
that it will be relicensed to LGPLv2.1+ later (see ``Copyright`` in the
|
||||
repository root directory).
|
||||
add a new input property, document it in input.rst. Changes to the user
|
||||
interface (options, properties, commands) should be documented in
|
||||
interface-changes.rst. Changes to libmpv should be documented in
|
||||
client-api-changes.rst.
|
||||
|
||||
Code formatting
|
||||
---------------
|
||||
|
||||
+2
-3
@@ -31,9 +31,8 @@ estimated remaining duration of the source file is used.
|
||||
|
||||
Note::
|
||||
|
||||
mpv can't use ordered chapter files in EDL entries. Usage of relative or
|
||||
absolute paths as well as any protocol prefixes is prevented for security
|
||||
reasons.
|
||||
Usage of relative or absolute paths as well as any protocol prefixes may be
|
||||
prevented for security reasons.
|
||||
|
||||
|
||||
Syntax of mpv EDL files
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
mpv provides access to its internal via the following means:
|
||||
mpv provides access to its internals via the following means:
|
||||
|
||||
- options
|
||||
- commands
|
||||
@@ -19,6 +19,36 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.17.0 ---
|
||||
- deprecate "track-list/N/audio-channels" property (use
|
||||
"track-list/N/demux-channel-count" instead)
|
||||
- remove write access to "stream-pos", and change semantics for read access
|
||||
- Lua scripts now don't suspend mpv by default while script code is run
|
||||
- add "cache-speed" property
|
||||
- rename --input-unix-socket to --input-ipc-server, and make it work on
|
||||
Windows too
|
||||
- change the exact behavior of the "video-zoom" property
|
||||
- --video-unscaled no longer disables --video-zoom and --video-aspect
|
||||
To force the old behavior, set --video-zoom=0 and --video-aspect=0
|
||||
--- mpv 0.16.0 ---
|
||||
- change --audio-channels default to stereo (use --audio-channels=auto to
|
||||
get the old default)
|
||||
- add --audio-normalize-downmix
|
||||
- change the default downmix behavior (--audio-normalize-downmix=yes to get
|
||||
the old default)
|
||||
- VO opengl custom shaders must now use "sample_pixel" as function name,
|
||||
instead of "sample"
|
||||
- change VO opengl scaler-resizes-only default to enabled
|
||||
- add VO opengl "interpolation-threshold" suboption (introduces new default
|
||||
behavior, which can change e.g. ``--video-sync=display-vdrop`` to the
|
||||
worse, but is usually what you want)
|
||||
- make "volume" and "mute" properties changeable even if no audio output is
|
||||
active (this gives not-ideal behavior if --softvol=no is used)
|
||||
- add "volume-max" and "mixer-active" properties
|
||||
- ignore --input-cursor option for events injected by input commands like
|
||||
"mouse", "keydown", etc.
|
||||
--- mpv 0.15.0 ---
|
||||
- change "yadif" video filter defaults
|
||||
--- mpv 0.14.0 ---
|
||||
- vo_opengl interpolation now requires --video-sync=display-... to be set
|
||||
- change some vo_opengl defaults (including changing tscale)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
Client API (libmpv) documentation and examples
|
||||
==============================================
|
||||
|
||||
libmpv lets you embed mpv as a library. It is meant to enable development of
|
||||
GUI frontends.
|
||||
|
||||
Since libmpv merely allows access to underlying mechanisms that can control
|
||||
mpv, documentation is spread over a few places:
|
||||
|
||||
- https://github.com/mpv-player/mpv/blob/master/libmpv/client.h
|
||||
- http://mpv.io/manual/master/#options
|
||||
- http://mpv.io/manual/master/#list-of-input-commands
|
||||
- http://mpv.io/manual/master/#properties
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
|
||||
+3
-7
@@ -63,13 +63,9 @@ Available filters are:
|
||||
(If you just want to set defaults for this filter that will be used
|
||||
even by automatically inserted lavrresample instances, you should
|
||||
prefer setting them with ``--af-defaults=lavrresample:...``.)
|
||||
``normalize=<yes|no>``
|
||||
Whether to normalize when remixing channel layouts (default: yes). This
|
||||
is e.g. applied when downmixing surround audio to stereo. The advantage
|
||||
is that this guarantees that no clipping can happen. Unfortunately,
|
||||
this can also lead to too low volume levels. Whether you enable or
|
||||
disable this is essentially a matter of taste, but the default uses
|
||||
the safer choice.
|
||||
``normalize=<yes|no|auto>``
|
||||
Whether to normalize when remixing channel layouts (default: auto).
|
||||
``auto`` uses the value set by ``--audio-normalize-downmix``.
|
||||
``o=<string>``
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
+6
-20
@@ -20,10 +20,8 @@ normal driver parameters.
|
||||
|
||||
See ``--ao=help`` for a list of compiled-in audio output drivers. The
|
||||
driver ``--ao=alsa`` is preferred. ``--ao=pulse`` is preferred on systems
|
||||
where PulseAudio is used. On Windows, ``--ao=wasapi`` is preferred,
|
||||
though it might cause trouble sometimes, in which case ``--ao=dsound``
|
||||
should be used. On BSD systems, ``--ao=oss`` or `--ao=sndio`` may work
|
||||
(the latter being experimental). On OS X systems, use ``--ao=coreaudio``.
|
||||
where PulseAudio is used. On BSD systems, ``--ao=oss`` or ``--ao=sndio``
|
||||
may work (the latter being experimental).
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
@@ -165,7 +163,7 @@ Available audio output drivers are:
|
||||
will actually work. The disadvantage is that it will change the
|
||||
system-wide audio settings. This is equivalent to changing the ``Format``
|
||||
setting in the ``Audio Devices`` dialog in the ``Audio MIDI Setup``
|
||||
utility. Note that this does not effect the selected speaker setup.
|
||||
utility. Note that this does not affect the selected speaker setup.
|
||||
|
||||
``exclusive``
|
||||
Use exclusive mode access. This merely redirects to
|
||||
@@ -205,19 +203,7 @@ Available audio output drivers are:
|
||||
(it used to be required to get good behavior on old PulseAudio versions).
|
||||
|
||||
If you have stuttering video when using pulse, try to enable this
|
||||
option. (Or alternatively, try to update PulseAudio.)
|
||||
|
||||
``dsound`` (Windows only)
|
||||
DirectX DirectSound audio output driver
|
||||
|
||||
.. note:: This driver is for compatibility with old systems.
|
||||
|
||||
``device=<devicenum>``
|
||||
Sets the device number to use. Playing a file with ``-v`` will show a
|
||||
list of available devices.
|
||||
|
||||
``buffersize=<ms>``
|
||||
DirectSound buffer size in milliseconds (default: 200).
|
||||
option. (Or try to update PulseAudio.)
|
||||
|
||||
``sdl``
|
||||
SDL 1.2+ audio output driver. Should work on any platform supported by SDL
|
||||
@@ -256,7 +242,7 @@ Available audio output drivers are:
|
||||
``speed``
|
||||
Simulated audio playback speed as a multiplier. Usually, a real audio
|
||||
device will not go exactly as fast as the system clock. It will deviate
|
||||
just a little, and this option helps simulating this.
|
||||
just a little, and this option helps to simulate this.
|
||||
|
||||
``latency``
|
||||
Simulated device latency. This is additional to EOF.
|
||||
@@ -325,7 +311,7 @@ Available audio output drivers are:
|
||||
String are valid; the GUID string is guaranteed to not change
|
||||
unless the driver is uninstalled.
|
||||
|
||||
Also supports searching active devices by human readable name. If more
|
||||
Also supports searching active devices by human-readable name. If more
|
||||
than one device matches the name, refuses loading it.
|
||||
|
||||
This option is mostly deprecated in favour of the more general
|
||||
|
||||
+4
-4
@@ -93,8 +93,8 @@ You can encode files from one format/codec to another using this facility.
|
||||
Completely empties the options list.
|
||||
|
||||
``--oafirst``
|
||||
Force the audio stream to become the first stream in the output. By default
|
||||
the order is unspecified.
|
||||
Force the audio stream to become the first stream in the output.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ovc=<codec>``
|
||||
Specifies the output video codec. This can be a comma separated list of
|
||||
@@ -134,8 +134,8 @@ You can encode files from one format/codec to another using this facility.
|
||||
Completely empties the options list.
|
||||
|
||||
``--ovfirst``
|
||||
Force the video stream to become the first stream in the output. By default
|
||||
the order is unspecified.
|
||||
Force the video stream to become the first stream in the output.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ocopyts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
|
||||
+166
-25
@@ -418,7 +418,7 @@ List of Input Commands
|
||||
|
||||
<reselect> (default)
|
||||
Select the default audio and subtitle streams, which typically selects
|
||||
external files with highest preference. (The implementation is not
|
||||
external files with the highest preference. (The implementation is not
|
||||
perfect, and could be improved on request.)
|
||||
|
||||
<keep-selection>
|
||||
@@ -546,6 +546,12 @@ Input Commands that are Possibly Subject to Change
|
||||
Always bind a key. (The input section that was made active most recently
|
||||
wins if there are ambiguities.)
|
||||
|
||||
This command can be used to dispatch arbitrary keys to a script or a client
|
||||
API user. If the input section defines ``script-binding`` commands, it is
|
||||
also possible to get separate events on key up/down, and relatively detailed
|
||||
information about the key state. The special key name ``unmapped`` can be
|
||||
used to match any unmapped key.
|
||||
|
||||
``overlay-add <id> <x> <y> "<file>" <offset> "<fmt>" <w> <h> <stride>``
|
||||
Add an OSD overlay sourced from raw data. This might be useful for scripts
|
||||
and applications controlling mpv, and which want to display things on top
|
||||
@@ -647,14 +653,20 @@ Input Commands that are Possibly Subject to Change
|
||||
For completeness, here is how this command works internally. The details
|
||||
could change any time. On any matching key event, ``script_message_to``
|
||||
or ``script_message`` is called (depending on whether the script name is
|
||||
included), where the first argument is the string ``key-binding``, the
|
||||
second argument is the name of the binding, and the third argument is the
|
||||
key state as string. The key state consists of a number of letters. The
|
||||
first letter is 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 binding), ``p`` (key was pressed; happens if up/down can't be
|
||||
tracked). The second letter whether the event originates from the mouse,
|
||||
either ``m`` (mouse button) or ``-`` (something else).
|
||||
included), with the following arguments:
|
||||
|
||||
1. The string ``key-binding``.
|
||||
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).
|
||||
|
||||
The key state consists of 2 letters:
|
||||
|
||||
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
|
||||
binding), ``p`` (key was pressed; happens if up/down can't be tracked).
|
||||
2. Whether the event originates from the mouse, either ``m`` (mouse button)
|
||||
or ``-`` (something else).
|
||||
|
||||
``ab-loop``
|
||||
Cycle through A-B loop states. The first command will set the ``A`` point
|
||||
@@ -682,6 +694,18 @@ Input Commands that are Possibly Subject to Change
|
||||
field is of type MPV_FORMAT_BYTE_ARRAY with the actual image data. The image
|
||||
is freed as soon as the result node is freed.
|
||||
|
||||
``vf-command "<label>" "<cmd>" "<args>"``
|
||||
Send a command to the filter with the given ``<label>``. Use ``all`` to send
|
||||
it to all filters at once. The command and argument string is filter
|
||||
specific. Currently, this only works with the ``lavfi`` filter - see
|
||||
the libavfilter documentation for which commands a filter supports.
|
||||
|
||||
Note that the ``<label>`` is a mpv filter label, not a libavfilter filter
|
||||
name.
|
||||
|
||||
``af-command "<label>" "<cmd>" "<args>"``
|
||||
Same as ``vf-command``, but for audio filters.
|
||||
|
||||
Undocumented commands: ``tv-last-channel`` (TV/DVB only),
|
||||
``ao-reload`` (experimental/internal).
|
||||
|
||||
@@ -741,6 +765,17 @@ The following hooks are currently defined:
|
||||
``file-local-options/<option name>``. The player will wait until all
|
||||
hooks are run.
|
||||
|
||||
``on_preloaded``
|
||||
Called after a file has been opened, and before tracks are selected and
|
||||
decoders are created. This has some usefulness if an API users wants
|
||||
to select tracks manually, based on the set of available tracks. It's
|
||||
also useful to initialize ``--lavfi-complex`` in a specific way by API,
|
||||
without having to "probe" the available streams at first.
|
||||
|
||||
Note that this does not yet apply default track selection. Which operations
|
||||
exactly can be done and not be done, and what information is available and
|
||||
what is not yet available yet, is all subject to change.
|
||||
|
||||
``on_unload``
|
||||
Run before closing a file, and before actually uninitializing
|
||||
everything. It's not possible to resume playback in this state.
|
||||
@@ -782,7 +817,7 @@ Input sections group a set of bindings, and enable or disable them at once.
|
||||
In ``input.conf``, each key binding is assigned to an input section, rather
|
||||
than actually having explicit text sections.
|
||||
|
||||
Also see ``enable_section`` and ``disable_section`` commands.
|
||||
See also: ``enable_section`` and ``disable_section`` commands.
|
||||
|
||||
Predefined bindings:
|
||||
|
||||
@@ -886,8 +921,9 @@ Property list
|
||||
useless. It looks like this can be different from ``path`` only when
|
||||
using e.g. ordered chapters.)
|
||||
|
||||
``stream-pos`` (RW)
|
||||
Raw byte position in source stream.
|
||||
``stream-pos``
|
||||
Raw byte position in source stream. Technically, this returns the position
|
||||
of the most recent packet passed to a decoder.
|
||||
|
||||
``stream-end``
|
||||
Raw end position in bytes in source stream.
|
||||
@@ -961,9 +997,8 @@ Property list
|
||||
``playback-time`` (RW)
|
||||
Position in current file in seconds. Unlike ``time-pos``, the time is
|
||||
clamped to the range of the file. (Inaccurate file durations etc. could
|
||||
make it go out of range. Also helpful when the user attempts to seek
|
||||
outside of the file, as the seek target time is considered the current
|
||||
position during seeking.)
|
||||
make it go out of range. Useful on attempts to seek outside of the file,
|
||||
as the seek target time is considered the current position during seeking.)
|
||||
|
||||
``chapter`` (RW)
|
||||
Current chapter number. The number of the first chapter is 0.
|
||||
@@ -1109,13 +1144,13 @@ Property list
|
||||
|
||||
``vf-metadata/<filter-label>``
|
||||
Metadata added by video filters. Accessed by the filter label,
|
||||
which if not explicitly specified using the ``@filter-label:`` syntax,
|
||||
which, if not explicitly specified using the ``@filter-label:`` syntax,
|
||||
will be ``<filter-name>NN``.
|
||||
|
||||
Works similar to ``metadata`` property. It allows the same access
|
||||
methods (using sub-properties).
|
||||
|
||||
An example of these kind of metadata are the cropping parameters
|
||||
An example of this kind of metadata are the cropping parameters
|
||||
added by ``--vf=lavfi=cropdetect``.
|
||||
|
||||
``af-metadata/<filter-label>``
|
||||
@@ -1142,7 +1177,7 @@ Property list
|
||||
|
||||
``cache-size`` (RW)
|
||||
Network cache size in KB. This is similar to ``--cache``. This allows
|
||||
to set the cache size at runtime. Currently, it's not possible to enable
|
||||
setting the cache size at runtime. Currently, it's not possible to enable
|
||||
or disable the cache at runtime using this property, just to resize an
|
||||
existing cache.
|
||||
|
||||
@@ -1160,6 +1195,11 @@ Property list
|
||||
``cache-used`` (R)
|
||||
Total used cache size in KB.
|
||||
|
||||
``cache-speed`` (R)
|
||||
Current I/O read speed between the cache and the lower layer (like network).
|
||||
This gives the number bytes per seconds over a 1 second window (using
|
||||
the type ``MPV_FORMAT_INT64`` for the client API).
|
||||
|
||||
``cache-idle`` (R)
|
||||
Returns ``yes`` if the cache is idle, which means the cache is filled as
|
||||
much as possible, and is currently not reading more data.
|
||||
@@ -1201,11 +1241,30 @@ Property list
|
||||
``hr-seek`` (RW)
|
||||
See ``--hr-seek``.
|
||||
|
||||
``mixer-active``
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise. This has
|
||||
implications for ``--softvol=no`` mode: if the mixer is inactive, changing
|
||||
``volume`` doesn't actually change anything on the system mixer. If the
|
||||
``--volume`` or ``--mute`` option are used, these might not be applied
|
||||
properly until the mixer becomes active either. (The options, if set, will
|
||||
just overwrite the mixer state at audio initialization.)
|
||||
|
||||
While the behavior with ``mixer-active==yes`` is relatively well-defined,
|
||||
the ``no`` case will provide possibly wrong or insignificant values.
|
||||
|
||||
Note that an active mixer does not necessarily imply active audio output,
|
||||
although this is implied in the current implementation.
|
||||
|
||||
``volume`` (RW)
|
||||
Current volume (see ``--volume`` for details).
|
||||
Current volume (see ``--volume`` for details). Also see ``mixer-active``
|
||||
property.
|
||||
|
||||
``volume-max``
|
||||
Current maximum value the volume property can be set to. (This may depend
|
||||
on the ``--softvol-max`` option.)
|
||||
|
||||
``mute`` (RW)
|
||||
Current mute status (``yes``/``no``).
|
||||
Current mute status (``yes``/``no``). Also see ``mixer-active`` property.
|
||||
|
||||
``audio-delay`` (RW)
|
||||
See ``--audio-delay``.
|
||||
@@ -1531,6 +1590,15 @@ Property list
|
||||
``program`` (W)
|
||||
Switch TS program (write-only).
|
||||
|
||||
``dvb-channel`` (W)
|
||||
Pair of integers: card,channel of current DVB stream.
|
||||
Can be switched to switch to another channel on the same card.
|
||||
|
||||
``dvb-channel-name`` (RW)
|
||||
Name of current DVB program.
|
||||
On write, a channel-switch to the named channel on the same
|
||||
card is performed. Can also be used for channel switching.
|
||||
|
||||
``sid`` (RW)
|
||||
Current subtitle track (similar to ``--sid``).
|
||||
|
||||
@@ -1645,10 +1713,6 @@ Property list
|
||||
``track-list/N/lang``
|
||||
Track language as identified by the file. Not always available.
|
||||
|
||||
``track-list/N/audio-channels``
|
||||
For audio tracks, the number of audio channels in the audio stream.
|
||||
Not always accurate (depends on container hints). Not always available.
|
||||
|
||||
``track-list/N/albumart``
|
||||
``yes`` if this is a video track that consists of a single picture,
|
||||
``no`` or unavailable otherwise. This is used for video tracks that are
|
||||
@@ -1684,6 +1748,29 @@ Property list
|
||||
match even if the default (builtin) demuxer is used, but there is
|
||||
no hard guarantee.
|
||||
|
||||
``track-list/N/decoder-desc``
|
||||
If this track is being decoded, the human-readable decoder name,
|
||||
|
||||
``track-list/N/demux-w``, ``track-list/N/demux-h``
|
||||
Video size hint as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/demux-channel-count``
|
||||
Number of audio channels as indicated by the container. (Not always
|
||||
accurate - in particular, the track could be decoded as a different
|
||||
number of channels.)
|
||||
|
||||
``track-list/N/demux-channels``
|
||||
Channel layout as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/demux-samplerate``
|
||||
Audio sample rate as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/demux-fps``
|
||||
Video FPS as indicated by the container. (Not always accurate.)
|
||||
|
||||
``track-list/N/audio-channels`` (deprecated)
|
||||
Deprecated alias for ``track-list/N/demux-channel-count``.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
@@ -1697,13 +1784,20 @@ Property list
|
||||
"src-id" MPV_FORMAT_INT64
|
||||
"title" MPV_FORMAT_STRING
|
||||
"lang" MPV_FORMAT_STRING
|
||||
"audio-channels" MPV_FORMAT_INT64
|
||||
"albumart" MPV_FORMAT_FLAG
|
||||
"default" MPV_FORMAT_FLAG
|
||||
"forced" MPV_FORMAT_FLAG
|
||||
"external" MPV_FORMAT_FLAG
|
||||
"external-filename" MPV_FORMAT_STRING
|
||||
"codec" MPV_FORMAT_STRING
|
||||
"decoder-desc" MPV_FORMAT_STRING
|
||||
"demux-w" MPV_FORMAT_INT64
|
||||
"demux-h" MPV_FORMAT_INT64
|
||||
"demux-channel-count" MPV_FORMAT_INT64
|
||||
"demux-channels" MPV_FORMAT_STRING
|
||||
"demux-samplerate" MPV_FORMAT_INT64
|
||||
"demux-fps" MPV_FORMAT_DOUBLE
|
||||
"audio-channels" MPV_FORMAT_INT64
|
||||
|
||||
``chapter-list``
|
||||
List of chapters, current entry marked. Currently, the raw property value
|
||||
@@ -1757,6 +1851,9 @@ Property list
|
||||
``video-rotate`` (RW)
|
||||
See ``--video-rotate`` option.
|
||||
|
||||
``video-stereo-mode`` (RW)
|
||||
See ``--video-stereo-mode`` option.
|
||||
|
||||
``seekable``
|
||||
Return whether it's generally possible to seek in the current file.
|
||||
|
||||
@@ -1900,6 +1997,43 @@ Property list
|
||||
In some cases, the protocol will not actually be supported (consider
|
||||
``https`` if ffmpeg is not compiled with TLS support).
|
||||
|
||||
``decoder-list``
|
||||
List of decoders supported. This lists decoders which can be passed to
|
||||
``--vd`` and ``--ad``.
|
||||
|
||||
``family``
|
||||
Decoder driver. Usually ``lavc`` for libavcodec.
|
||||
|
||||
``codec``
|
||||
Canonical codec name, which identifies the format the decoder can
|
||||
handle.
|
||||
|
||||
``driver``
|
||||
The name of the decoder itself. Often, this is the same as ``codec``.
|
||||
Sometimes it can be different. It is used to distinguish multiple
|
||||
decoders for the same codec.
|
||||
|
||||
``description``
|
||||
Human readable description of the decoder and codec.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
|
||||
::
|
||||
|
||||
MPV_FORMAT_NODE_ARRAY
|
||||
MPV_FORMAT_NODE_MAP (for each decoder entry)
|
||||
"family" MPV_FORMAT_STRING
|
||||
"codec" MPV_FORMAT_STRING
|
||||
"driver" MPV_FORMAT_STRING
|
||||
"description" MPV_FORMAT_STRING
|
||||
|
||||
``encoder-list``
|
||||
List of libavcodec encoders. This has the same format as ``decoder-list``.
|
||||
The encoder names (``driver`` entries) can be passed to ``--ovc`` and
|
||||
``--oac`` (without the ``lavc:`` prefix required by ``--vd`` and ``--ad``).
|
||||
|
||||
``mpv-version``
|
||||
Return the mpv version/copyright string. Depending on how the binary was
|
||||
built, it might contain either a release version, or just a git hash.
|
||||
@@ -1908,6 +2042,13 @@ Property list
|
||||
Return the configuration arguments which were passed to the build system
|
||||
(typically the way ``./waf configure ...`` was invoked).
|
||||
|
||||
``ffmpeg-version``
|
||||
Return the contents of the ``av_version_info()`` API call. This is a string
|
||||
which identifies the build in some way, either through a release version
|
||||
number, or a git hash. This applies to Libav as well (the property is
|
||||
still named the same.) This property is unavailable if mpv is linked against
|
||||
older FFmpeg and Libav versions.
|
||||
|
||||
``options/<name>`` (RW)
|
||||
Read-only access to value of option ``--<name>``. Most options can be
|
||||
changed at runtime by writing to this property. Note that many options
|
||||
|
||||
+37
-10
@@ -1,10 +1,10 @@
|
||||
JSON IPC
|
||||
========
|
||||
|
||||
mpv can be controlled by external programs using the JSON-based IPC protocol. It
|
||||
can be enabled by specifying the path to a unix socket using the option
|
||||
``--input-unix-socket``. Clients can connect to this socket and send commands to
|
||||
the player or receive events from it.
|
||||
mpv can be controlled by external programs using the JSON-based IPC protocol.
|
||||
It can be enabled by specifying the path to a unix socket or a named pipe using
|
||||
the option ``--input-ipc-server``. Clients can connect to this socket and send
|
||||
commands to the player or receive events from it.
|
||||
|
||||
.. warning::
|
||||
|
||||
@@ -17,12 +17,12 @@ the player or receive events from it.
|
||||
Socat example
|
||||
-------------
|
||||
|
||||
You can use the ``socat`` tool to send commands (and receive reply) from the
|
||||
You can use the ``socat`` tool to send commands (and receive replies) from the
|
||||
shell. Assuming mpv was started with:
|
||||
|
||||
::
|
||||
|
||||
mpv file.mkv --input-unix-socket=/tmp/mpvsocket
|
||||
mpv file.mkv --input-ipc-server=/tmp/mpvsocket
|
||||
|
||||
Then you can control it using socat:
|
||||
|
||||
@@ -46,6 +46,31 @@ It's also possible to send input.conf style text-only commands:
|
||||
But you won't get a reply over the socket. (This particular command shows the
|
||||
playback time on the player's OSD.)
|
||||
|
||||
Command Prompt example
|
||||
----------------------
|
||||
|
||||
Unfortunately, it's not as easy to test the IPC protocol on Windows, since
|
||||
Windows ports of socat (in Cygwin and MSYS2) don't understand named pipes. In
|
||||
the absence of a simple tool to send and receive from bidirectional pipes, the
|
||||
``echo`` command can be used to send commands, but not receive replies from the
|
||||
command prompt.
|
||||
|
||||
Assuming mpv was started with:
|
||||
|
||||
::
|
||||
|
||||
mpv file.mkv --input-ipc-server=\\.\pipe\mpvsocket
|
||||
|
||||
You can send commands from a command prompt:
|
||||
|
||||
::
|
||||
|
||||
echo show_text ${playback-time} >\\.\pipe\mpvsocket
|
||||
|
||||
To be able to simultaneously read and write from the IPC pipe, like on Linux,
|
||||
it's necessary to write an external program that uses overlapped file I/O (or
|
||||
some wrapper like .NET's NamedPipeClientStream.)
|
||||
|
||||
Protocol
|
||||
--------
|
||||
|
||||
@@ -102,7 +127,7 @@ break character (``\n``).
|
||||
|
||||
If the first character (after skipping whitespace) is not ``{``, the command
|
||||
will be interpreted as non-JSON text command, as they are used in input.conf
|
||||
(or ``mpv_command_string()`` in the client API). Additionally, line starting
|
||||
(or ``mpv_command_string()`` in the client API). Additionally, lines starting
|
||||
with ``#`` and empty lines are ignored.
|
||||
|
||||
Currently, embedded 0 bytes terminate the current line, but you should not
|
||||
@@ -111,7 +136,7 @@ rely on this.
|
||||
Commands
|
||||
--------
|
||||
|
||||
Additionally to the commands described in `List of Input Commands`_, a few
|
||||
In addition to the commands described in `List of Input Commands`_, a few
|
||||
extra commands can also be used as part of the protocol:
|
||||
|
||||
``client_name``
|
||||
@@ -189,7 +214,7 @@ extra commands can also be used as part of the protocol:
|
||||
|
||||
``unobserve_property``
|
||||
Undo ``observe_property`` or ``observe_property_string``. This requires the
|
||||
numeric id passed to the observe command as argument.
|
||||
numeric id passed to the observed command as argument.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -228,7 +253,9 @@ extra commands can also be used as part of the protocol:
|
||||
|
||||
``get_version``
|
||||
Returns the client API version the C API of the remote mpv instance
|
||||
provides. (Also see ``DOCS/client-api-changes.rst``.)
|
||||
provides.
|
||||
|
||||
See also: ``DOCS/client-api-changes.rst``.
|
||||
|
||||
UTF-8
|
||||
-----
|
||||
|
||||
+9
-14
@@ -192,13 +192,14 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
Register callback to be run on a key binding. The binding will be mapped to
|
||||
the given ``key``, which is a string describing the physical key. This uses
|
||||
the same key names as in input.conf, and also allows combinations
|
||||
(e.g. ``ctrl+a``).
|
||||
(e.g. ``ctrl+a``). If the key is empty or ``nil``, no physical key is
|
||||
registered, but the user still can create own bindings (see below).
|
||||
|
||||
After calling this function, key presses will cause the function ``fn`` to
|
||||
be called (unless the user remapped the key with another binding).
|
||||
|
||||
The ``name`` argument should be a short symbolic string. It allows the user
|
||||
to remap the key binding via input.conf using the ``script_message``
|
||||
to remap the key binding via input.conf using the ``script-message``
|
||||
command, and the name of the key binding (see below for
|
||||
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
|
||||
@@ -220,8 +221,8 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
has an ``is_mouse`` entry, which tells whether the event was caused
|
||||
by a mouse button.
|
||||
|
||||
Internally, key bindings are dispatched via the ``script_message_to`` or
|
||||
``script_binding`` input commands and ``mp.register_script_message``.
|
||||
Internally, key bindings are dispatched via the ``script-message-to`` or
|
||||
``script-binding`` input commands and ``mp.register_script_message``.
|
||||
|
||||
Trying to map multiple commands to a key will essentially prefer a random
|
||||
binding, while the other bindings are not called. It is guaranteed that
|
||||
@@ -244,7 +245,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
::
|
||||
|
||||
y script_binding something
|
||||
y script-binding something
|
||||
|
||||
|
||||
This will print the message when the key ``y`` is pressed. (``x`` will
|
||||
@@ -255,7 +256,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
|
||||
::
|
||||
|
||||
y script_binding fooscript.something
|
||||
y script-binding fooscript/something
|
||||
|
||||
``mp.add_forced_key_binding(...)``
|
||||
This works almost the same as ``mp.add_key_binding``, but registers the
|
||||
@@ -389,7 +390,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
``mp.get_script_name()``
|
||||
Return the name of the current script. The name is usually made of the
|
||||
filename of the script, with directory and file extension removed. If
|
||||
there are several script which would have the same name, it's made unique
|
||||
there are several scripts which would have the same name, it's made unique
|
||||
by appending a number.
|
||||
|
||||
.. admonition:: Example
|
||||
@@ -412,7 +413,7 @@ are useful only in special situations.
|
||||
from displaying the next video frame, so that you don't get blocked when
|
||||
trying to access the player.
|
||||
|
||||
This is automatically called by the event handler.
|
||||
Before mpv 0.17.0, this was automatically called by the event handler.
|
||||
|
||||
``mp.resume()``
|
||||
Undo one ``mp.suspend()`` call. ``mp.suspend()`` increments an internal
|
||||
@@ -423,10 +424,6 @@ are useful only in special situations.
|
||||
This resets the internal suspend counter and resumes the player. (It's
|
||||
like calling ``mp.resume()`` until the player is actually resumed.)
|
||||
|
||||
You might want to call this if you're about to do something that takes a
|
||||
long time, but doesn't really need access to the player (like a network
|
||||
operation). Note that you still can access the player at any time.
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
Calls ``mpv_get_wakeup_pipe()`` and returns the read end of the wakeup
|
||||
pipe. (See ``client.h`` for details.)
|
||||
@@ -449,8 +446,6 @@ are useful only in special situations.
|
||||
``mp.get_wakeup_pipe()`` if you're interested in properly working
|
||||
notification of new events and working timers.
|
||||
|
||||
This function calls ``mp.suspend()`` and ``mp.resume_all()`` on its own.
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
Set the minimum log level of which mpv message output to receive. These
|
||||
messages are normally printed to the terminal. By calling this function,
|
||||
|
||||
+64
-20
@@ -37,6 +37,10 @@ LIRC support - configure remotes as input devices instead).
|
||||
|
||||
See the ``--input-`` options for ways to customize it.
|
||||
|
||||
The following listings are not necessarily complete. See ``etc/input.conf`` for
|
||||
a list of default bindings. User ``input.conf`` files and Lua scripts can
|
||||
define additional key bindings.
|
||||
|
||||
Keyboard Control
|
||||
----------------
|
||||
|
||||
@@ -50,7 +54,7 @@ UP and DOWN
|
||||
|
||||
Ctrl+LEFT and Ctrl+RIGHT
|
||||
Seek to the previous/next subtitle. Subject to some restrictions and
|
||||
might not work always; see ``sub_seek`` command.
|
||||
might not always work; see ``sub-seek`` command.
|
||||
|
||||
[ and ]
|
||||
Decrease/increase current playback speed by 10%.
|
||||
@@ -222,7 +226,10 @@ PREVIOUS and NEXT
|
||||
support.)
|
||||
|
||||
h and k
|
||||
Select previous/next channel.
|
||||
Select previous/next tv-channel.
|
||||
|
||||
H and K
|
||||
Select previous/next dvb-channel.
|
||||
|
||||
Mouse Control
|
||||
-------------
|
||||
@@ -378,6 +385,45 @@ file stops playing. If option ``--c`` is changed during playback of
|
||||
``file2.mkv``, it is reset when advancing to ``file3.mkv``. This only affects
|
||||
file-local options. The option ``--a`` is never reset here.
|
||||
|
||||
|
||||
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::
|
||||
|
||||
mpv uses a different default DVD library than MPlayer. MPlayer
|
||||
uses libdvdread by default, and mpv uses libdvdnav by default.
|
||||
Both libraries are developed in parallel, but libdvdnav is
|
||||
intended to support more sophisticated DVD features such as menus
|
||||
and multi-angle playback. mpv uses libdvdnav for files specified
|
||||
as either ``dvd://...`` or ``dvdnav://...``. To use libdvdread,
|
||||
which will produce behavior more like MPlayer, specify
|
||||
``dvdread://...`` instead. Some users have experienced problems
|
||||
when using libdvdnav, in which playback gets stuck in a DVD menu
|
||||
stream. These problems are reported to go away when auto-selecting
|
||||
the title (``dvd://`` rather than ``dvd://1``) or when using
|
||||
libdvdread (e.g. ``dvdread://0``).
|
||||
|
||||
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``.
|
||||
|
||||
|
||||
CONFIGURATION FILES
|
||||
===================
|
||||
|
||||
@@ -520,8 +566,9 @@ TAKING SCREENSHOTS
|
||||
|
||||
Screenshots of the currently played file can be taken using the 'screenshot'
|
||||
input mode command, which is by default bound to the ``s`` key. Files named
|
||||
``shotNNNN.jpg`` will be saved in the working directory, using the first
|
||||
available number - no files will be overwritten.
|
||||
``mpv-shotNNNN.jpg`` will be saved in the working directory, using the first
|
||||
available number - no files will be overwritten. In pseudo-GUI mode, the
|
||||
screenshot will be saved somewhere else. See `PSEUDO GUI MODE`_.
|
||||
|
||||
A screenshot will usually contain the unscaled video contents at the end of the
|
||||
video filter chain and subtitles. By default, ``S`` takes screenshots without
|
||||
@@ -571,7 +618,7 @@ listed.
|
||||
certainty.
|
||||
- Dropped frames, e.g. ``Dropped: 4``. Shows up only if the count is not 0. Can
|
||||
grow if the video framerate is higher than that of the display, or if video
|
||||
rendering is too slow. Also can be incremented on "hiccups" and when the video
|
||||
rendering is too slow. May also be incremented on "hiccups" and when the video
|
||||
frame couldn't be displayed on time. (``vo-drop-frame-count`` property.)
|
||||
If the decoder drops frames, the number of decoder-dropped frames is appended
|
||||
to the display as well, e.g.: ``Dropped: 4/34``. This happens only if
|
||||
@@ -602,7 +649,7 @@ PROTOCOLS
|
||||
Play a path from Samba share.
|
||||
|
||||
``bd://[title][/device]`` ``--bluray-device=PATH``
|
||||
Play a Blu-Ray disc. Currently, this does not accept ISO files. Instead,
|
||||
Play a Blu-ray disc. Currently, this does not accept ISO files. Instead,
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directory directly.
|
||||
|
||||
@@ -654,15 +701,16 @@ PROTOCOLS
|
||||
absolute path.
|
||||
|
||||
``fd://123``
|
||||
Read data from the given UNIX FD (for example 123). This is similar to
|
||||
piping data to stdin via ``-``, but can use an arbitrary file descriptor.
|
||||
Will not work correctly on MS Windows.
|
||||
Read data from the given file descriptor (for example 123). This is similar
|
||||
to piping data to stdin via ``-``, but can use an arbitrary file descriptor.
|
||||
|
||||
``edl://[edl specification as in edl-mpv.rst]``
|
||||
Stitch together parts of multiple files and play them.
|
||||
|
||||
``null://``
|
||||
Simulate an empty file.
|
||||
Simulate an empty file. If opened for writing, it will discard all data.
|
||||
The ``null`` demuxer will specifically pass autoprobing if this protocol
|
||||
is used (while it's not automatically invoked for empty files).
|
||||
|
||||
``memory://data``
|
||||
Use the ``data`` part as source data.
|
||||
@@ -728,7 +776,7 @@ EMBEDDING INTO OTHER PROGRAMS (LIBMPV)
|
||||
======================================
|
||||
|
||||
mpv can be embedded into other programs as video/audio playback backend. The
|
||||
recommended way to to so is using libmpv. See ``libmpv/client.h`` in the mpv
|
||||
recommended way to do so is using libmpv. See ``libmpv/client.h`` in the mpv
|
||||
source code repository. This provides a C API. Bindings for other languages
|
||||
might be available (see wiki).
|
||||
|
||||
@@ -749,9 +797,6 @@ behavior of mpv.
|
||||
If set, XDG-style system configuration directories are used. Otherwise,
|
||||
the UNIX convention (``PREFIX/etc/mpv/``) is used.
|
||||
|
||||
``TERM``
|
||||
Used to determine terminal type.
|
||||
|
||||
``MPV_HOME``
|
||||
Directory where mpv looks for user settings. Overrides ``HOME``, and mpv
|
||||
will try to load the config file as ``$MPV_HOME/mpv.conf``.
|
||||
@@ -762,8 +807,7 @@ behavior of mpv.
|
||||
of ``--v`` options passed to the command line.
|
||||
|
||||
``MPV_LEAK_REPORT``
|
||||
If set to ``1``, enable internal talloc leak reporting. Note that this can
|
||||
cause trouble with multithreading, so only developers should use this.
|
||||
If set to ``1``, enable internal talloc leak reporting.
|
||||
|
||||
``LADSPA_PATH``
|
||||
Specifies the search path for LADSPA plugins. If it is unset, fully
|
||||
@@ -803,7 +847,7 @@ libdvdcss:
|
||||
|
||||
key
|
||||
is the default method. libdvdcss will use a set of calculated
|
||||
player keys to try and get the disc key. This can fail if the drive
|
||||
player keys to try to get the disc key. This can fail if the drive
|
||||
does not recognize any of the player keys.
|
||||
|
||||
disc
|
||||
@@ -919,7 +963,7 @@ locations are different. They are generally located under ``%APPDATA%/mpv/``.
|
||||
For example, the path to mpv.conf is ``%APPDATA%/mpv/mpv.conf``, which maps to
|
||||
a system and user-specific path, for example
|
||||
|
||||
``C:\users\USERNAME\Application Data\mpv\mpv.conf``
|
||||
``C:\users\USERNAME\AppData\Roaming\mpv\mpv.conf``
|
||||
|
||||
You can find the exact path by running ``echo %APPDATA%\mpv\mpv.conf`` in cmd.exe.
|
||||
|
||||
@@ -942,8 +986,8 @@ lower priority. Some config files are loaded only once, which means that
|
||||
e.g. of 2 ``input.conf`` files located in two config directories, only the
|
||||
one from the directory with higher priority will be loaded.
|
||||
|
||||
A third config directory with lowest priority is the directory named ``mpv``
|
||||
in the same directory as ``mpv.exe``. This used to be the directory with
|
||||
A third config directory with the lowest priority is the directory named ``mpv``
|
||||
in the same directory as ``mpv.exe``. This used to be the directory with the
|
||||
highest priority, but is now discouraged to use and might be removed in the
|
||||
future.
|
||||
|
||||
|
||||
+188
-62
@@ -136,7 +136,9 @@ Playback Control
|
||||
|
||||
``--chapter=<start[-end]>``
|
||||
Specify which chapter to start playing at. Optionally specify which
|
||||
chapter to end playing at. Also see ``--start``.
|
||||
chapter to end playing at.
|
||||
|
||||
See also: ``--start``.
|
||||
|
||||
``--playlist-pos=<no|index>``
|
||||
Set which file on the internal playlist to start playback with. The index
|
||||
@@ -321,7 +323,7 @@ Program Behavior
|
||||
Files explicitly requested by command line options, like
|
||||
``--include`` or ``--use-filedir-conf``, will still be loaded.
|
||||
|
||||
Also see ``--config-dir``.
|
||||
See also: ``--config-dir``.
|
||||
|
||||
``--list-options``
|
||||
Prints all available options.
|
||||
@@ -392,8 +394,7 @@ Program Behavior
|
||||
|
||||
``--merge-files``
|
||||
Pretend that all files passed to mpv are concatenated into a single, big
|
||||
file. This uses timeline/EDL support internally. Note that this won't work
|
||||
for ordered chapter files.
|
||||
file. This uses timeline/EDL support internally.
|
||||
|
||||
``--no-resume-playback``
|
||||
Do not restore playback position from the ``watch_later`` configuration
|
||||
@@ -537,7 +538,7 @@ Video
|
||||
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 ``D:`` field. If the decoder is too slow,
|
||||
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.
|
||||
@@ -558,9 +559,13 @@ Video
|
||||
|
||||
``--display-fps=<fps>``
|
||||
Set the display FPS used with the ``--video-sync=display-*`` modes. By
|
||||
default a detected value is used (X11 only, not correct on multi-monitor
|
||||
systems). Keep in mind that setting an incorrect value (even if slightly
|
||||
incorrect) can ruin video playback.
|
||||
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
|
||||
systems, there is a chance that the detected value is from the wrong
|
||||
monitor.
|
||||
|
||||
Set this option only if you have reason to believe the automatically
|
||||
determined value is wrong.
|
||||
|
||||
``--hwdec=<api>``
|
||||
Specify the hardware video decoding API that should be used if possible.
|
||||
@@ -575,7 +580,10 @@ Video
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only)
|
||||
:dxva2: requires ``--vo=opengl:backend=angle`` or
|
||||
``--vo=opengl:backend=dxinterop`` (Windows only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:d3d11va-copy: experimental (Windows only)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
|
||||
``auto`` tries to automatically enable hardware decoding using the first
|
||||
@@ -591,9 +599,14 @@ Video
|
||||
forcing it with ``--vo=opengl:backend=x11``, but the vaapi/GLX interop is
|
||||
said to be slower than ``vaapi-copy``.
|
||||
|
||||
The ``vaapi-copy`` mode allows you to use vaapi with any VO. Because
|
||||
this copies the decoded video back to system RAM, it's likely less efficient
|
||||
than the ``vaapi`` mode.
|
||||
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 ``vaapi-copy`` and ``dxva2-copy`` modes 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 ``vaapi``
|
||||
or ``dxva2`` modes respectively.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -613,7 +626,7 @@ Video
|
||||
|
||||
For ``opengl-cb``, if set, load the interop context as soon as the OpenGL
|
||||
context is created. Since ``opengl-cb`` has no on-demand loading, this
|
||||
allows enabling hardware decoding at runtime at all, without having to
|
||||
allows enabling hardware decoding at runtime at all, without having
|
||||
to temporarily set the ``hwdec`` option just during OpenGL context
|
||||
initialization with ``mpv_opengl_cb_init_gl()``.
|
||||
|
||||
@@ -671,15 +684,12 @@ Video
|
||||
``--video-unscaled``
|
||||
Disable scaling of the video. If the window is larger than the video,
|
||||
black bars are added. Otherwise, the video is cropped. The video still
|
||||
can be influenced by the other ``--video-...`` options. (But not all; for
|
||||
example ``--video-zoom`` does nothing if this option is enabled.)
|
||||
|
||||
The video and monitor aspects aspect will be ignored. Aspect correction
|
||||
would require to scale the video in the X or Y direction, but this option
|
||||
disables scaling, disabling all aspect correction.
|
||||
can be influenced by the other ``--video-...`` options.
|
||||
|
||||
Note that the scaler algorithm may still be used, even if the video isn't
|
||||
scaled. For example, this can influence chroma conversion.
|
||||
scaled. For example, this can influence chroma conversion. The video will
|
||||
also still be scaled in one dimension if the source uses non-square pixels
|
||||
(e.g. anamorphic widescreen DVDs).
|
||||
|
||||
This option is disabled if the ``--no-keepaspect`` option is used.
|
||||
|
||||
@@ -695,7 +705,7 @@ Video
|
||||
|
||||
This option is disabled if the ``--no-keepaspect`` option is used.
|
||||
|
||||
``--video-rotate=<0-360|no>``
|
||||
``--video-rotate=<0-359|no>``
|
||||
Rotate the video clockwise, in degrees. Currently supports 90° steps only.
|
||||
If ``no`` is given, the video is never rotated, even if the file has
|
||||
rotation metadata. (The rotation value is added to the rotation metadata,
|
||||
@@ -719,12 +729,10 @@ Video
|
||||
appear in files, but can't be handled properly by mpv.
|
||||
|
||||
``--video-zoom=<value>``
|
||||
Adjust the video display scale factor by the given value. The unit is in
|
||||
fractions of the (scaled) window video size.
|
||||
|
||||
For example, given a 1280x720 video shown in a 1280x720 window,
|
||||
``--video-zoom=-0.1`` would make the video by 128 pixels smaller in
|
||||
X direction, and 72 pixels in Y direction.
|
||||
Adjust the video display scale factor by the given value. The parameter is
|
||||
given log 2. For example, ``--video-zoom=0`` is unscaled,
|
||||
``--video-zoom=1`` is twice the size, ``--video-zoom=-2`` is one fourth of
|
||||
the size, and so on.
|
||||
|
||||
This option is disabled if the ``--no-keepaspect`` option is used.
|
||||
|
||||
@@ -820,7 +828,7 @@ Video
|
||||
You can get the list of allowed codecs with ``mpv --vd=help``. Remove the
|
||||
prefix, e.g. instead of ``lavc:h264`` use ``h264``.
|
||||
|
||||
By default this is set to ``h264,vc1,wmv3,hevc,mpeg2video``. Note that the
|
||||
By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video``. Note that the
|
||||
hardware acceleration special codecs like ``h264_vdpau`` are not relevant
|
||||
anymore, and in fact have been removed from Libav in this form.
|
||||
|
||||
@@ -973,7 +981,7 @@ Audio
|
||||
|
||||
There is not much reason to use this. HDMI supports uncompressed
|
||||
multichannel PCM, and mpv supports lossless DTS-HD decoding via
|
||||
FFmpeg's libdcadec wrapper.
|
||||
FFmpeg's new DCA decoder (based on libdcadec).
|
||||
|
||||
``--ad=<[+|-]family1:(*|decoder1),[+|-]family2:(*|decoder2),...[-]>``
|
||||
Specify a priority list of audio decoders to be used, according to their
|
||||
@@ -1027,7 +1035,9 @@ Audio
|
||||
|
||||
``--mute=<auto|yes|no>``
|
||||
Set startup audio mute status. ``auto`` (default) will not change the mute
|
||||
status. Also see ``--volume``.
|
||||
status.
|
||||
|
||||
See also: ``--volume``.
|
||||
|
||||
``--softvol=<mode>``
|
||||
Control whether to use the volume controls of the audio output driver or
|
||||
@@ -1085,8 +1095,8 @@ Audio
|
||||
This and enabling passthrough via ``--ad`` are deprecated in favor of
|
||||
using ``--audio-spdif=dts-hd``.
|
||||
|
||||
``--audio-channels=<number|layout>``
|
||||
Request a channel layout for audio output (default: auto). This will ask
|
||||
``--audio-channels=<auto|number|layout>``
|
||||
Request a channel layout for audio output (default: stereo). This will ask
|
||||
the AO to open a device with the given channel layout. It's up to the AO
|
||||
to accept this layout, or to pick a fallback or to error out if the
|
||||
requested layout is not supported.
|
||||
@@ -1099,9 +1109,10 @@ Audio
|
||||
lists speaker names, which can be used to express arbitrary channel
|
||||
layouts (e.g. ``fl-fr-lfe`` is 2.1).
|
||||
|
||||
The default is ``--audio-channels=auto``, which tries to play audio using
|
||||
the input file's channel layout. (Or more precisely, the output of the
|
||||
audio filter chain.) (``empty`` is an accepted obsolete alias for ``auto``.)
|
||||
``--audio-channels=auto`` tries to play audio using the input file's
|
||||
channel layout. There is no guarantee that the audio API handles this
|
||||
correctly. See the HDMI warning below.
|
||||
(``empty`` is an accepted obsolete alias for ``auto``.)
|
||||
|
||||
This will also request the channel layout from the decoder. If the decoder
|
||||
does not support the layout, it will fall back to its native channel layout.
|
||||
@@ -1120,6 +1131,16 @@ Audio
|
||||
channel layout, random things can happen, such as dropping the
|
||||
additional channels, or adding noise.
|
||||
|
||||
``--audio-normalize-downmix=<yes|no>``
|
||||
Enable/disable normalization if surround audio is downmixed to stereo
|
||||
(default: no). If this is disabled, downmix can cause clipping. If it's
|
||||
enabled, the output might be too silent. It depends on the source audio.
|
||||
|
||||
Technically, this changes the ``normalize`` suboption of the
|
||||
``lavrresample`` audio filter, which performs the downmixing.
|
||||
|
||||
If downmix happens outside of mpv for some reason, this has no effect.
|
||||
|
||||
``--audio-display=<no|attachment>``
|
||||
Setting this option to ``attachment`` (default) will display image
|
||||
attachments (e.g. album cover art) when playing audio files. It will
|
||||
@@ -1152,8 +1173,8 @@ Audio
|
||||
point of file change. Default: ``weak``.
|
||||
|
||||
:no: Disable gapless audio.
|
||||
:yes: The audio device is opened using parameters chosen according to the
|
||||
first file played and is then kept open for gapless playback. This
|
||||
:yes: The audio device is opened using parameters chosen for the first
|
||||
file played and is then kept open for gapless playback. This
|
||||
means that if the first file for example has a low sample rate, then
|
||||
the following files may get resampled to the same low sample rate,
|
||||
resulting in reduced sound quality. If you play files with different
|
||||
@@ -1200,7 +1221,11 @@ Audio
|
||||
:no: Don't automatically load external audio files.
|
||||
:exact: Load the media filename with audio file extension (default).
|
||||
:fuzzy: Load all audio files containing media filename.
|
||||
:all: Load all audio files in the current directory.
|
||||
:all: Load all aufio files in the current and ``--audio-file-paths``
|
||||
directories.
|
||||
|
||||
``--audio-file-paths=<path1:path2:...>``
|
||||
Equivalent to ``--sub-paths`` option, but for auto-loaded audio files.
|
||||
|
||||
``--audio-client-name=<name>``
|
||||
The application name the player reports to the audio API. Can be useful
|
||||
@@ -1237,6 +1262,14 @@ Audio
|
||||
Subtitles
|
||||
---------
|
||||
|
||||
.. note::
|
||||
|
||||
Changing styling and position does not work with all subtitles. Image-based
|
||||
subtitles (DVD, Bluray/PGS, DVB) can not changed for fundamental reasons.
|
||||
Subtitles in ASS format are normally not changed intentionally, but
|
||||
overriding them can be controlled with ``--ass-style-override``.
|
||||
|
||||
|
||||
``--no-sub``
|
||||
Do not select any subtitle when the file is loaded.
|
||||
|
||||
@@ -1365,7 +1398,7 @@ Subtitles
|
||||
.. admonition:: Warning
|
||||
|
||||
Enabling hinting can lead to mispositioned text (in situations it's
|
||||
supposed to match up with video background), or reduce the smoothness
|
||||
supposed to match up video background), or reduce the smoothness
|
||||
of animations with some badly authored ASS scripts. It is recommended
|
||||
to not use this option, unless really needed.
|
||||
|
||||
@@ -1600,10 +1633,10 @@ Subtitles
|
||||
``<rate>`` > video fps speeds the subtitles up for frame-based
|
||||
subtitle files and slows them down for time-based ones.
|
||||
|
||||
Also see ``--sub-speed`` option.
|
||||
See also: ``--sub-speed``.
|
||||
|
||||
``--sub-gauss=<0.0-3.0>``
|
||||
Apply Gaussian blur to image subtitles (default: 0). This can help making
|
||||
Apply Gaussian blur to image subtitles (default: 0). This can help to make
|
||||
pixelated DVD/Vobsubs look nicer. A value other than 0 also switches to
|
||||
software subtitle scaling. Might be slow.
|
||||
|
||||
@@ -1612,7 +1645,7 @@ Subtitles
|
||||
Never applied to text subtitles.
|
||||
|
||||
``--sub-gray``
|
||||
Convert image subtitles to grayscale. Can help making yellow DVD/Vobsubs
|
||||
Convert image subtitles to grayscale. Can help to make yellow DVD/Vobsubs
|
||||
look nicer.
|
||||
|
||||
.. note::
|
||||
@@ -1984,8 +2017,8 @@ Window
|
||||
always re-enabled when the player is paused.
|
||||
|
||||
This is not supported on all video outputs or platforms. Sometimes it is
|
||||
implemented, but does not work (happens often on GNOME). You might be able
|
||||
to to work this around using ``--heartbeat-cmd`` instead.
|
||||
implemented, but does not work (known to happen with GNOME). You might be
|
||||
able to work around this using ``--heartbeat-cmd`` instead.
|
||||
|
||||
``--wid=<ID>``
|
||||
This tells mpv to attach to an existing window. If a VO is selected that
|
||||
@@ -2004,7 +2037,7 @@ Window
|
||||
parent, like with X11.
|
||||
|
||||
On OSX/Cocoa, the ID is interpreted as ``NSView*``. Pass it as value cast
|
||||
to ``intptr_t``. mpv will creates its own sub-view. Because OSX does not
|
||||
to ``intptr_t``. mpv will create its own sub-view. Because OSX does not
|
||||
support window embedding of foreign processes, this works only with libmpv,
|
||||
and will crash when used from the command line.
|
||||
|
||||
@@ -2036,8 +2069,8 @@ Window
|
||||
This option might be removed in the future.
|
||||
|
||||
``--x11-bypass-compositor=<yes|no>``
|
||||
If set to ``yes`` (default), then ask the compositor to unredirect the
|
||||
mpv window. This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
If set to ``yes``, then ask the compositor to unredirect the mpv window
|
||||
(default: no). This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
|
||||
|
||||
Disc Devices
|
||||
@@ -2213,7 +2246,7 @@ Demuxer
|
||||
Encryption key the demuxer should use. This is the raw binary data of
|
||||
the key converted to a hexadecimal string.
|
||||
|
||||
``--demuxer-mkv-subtitle-preroll``, ``--mkv-subtitle-preroll``
|
||||
``--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
|
||||
some container file formats are designed. The subtitles appear only if
|
||||
@@ -2245,7 +2278,11 @@ Demuxer
|
||||
overlap with a seek target. In these cases, mpv will reduce the amount
|
||||
of data read to a minimum. (Although it will still read *all* data between
|
||||
the cluster that contains the first wanted subtitle packet, and the seek
|
||||
target.)
|
||||
target.) If the ``index`` choice (which is the default) is specified, then
|
||||
prerolling will be done only if this information is actually available. If
|
||||
this method is used, the maximum amount of data to skip can be additionally
|
||||
controlled by ``--demuxer-mkv-subtitle-preroll-secs-index`` (it still uses
|
||||
the value of the option without ``-index`` if that is higher).
|
||||
|
||||
See also ``--hr-seek-demuxer-offset`` option. This option can achieve a
|
||||
similar effect, but only if hr-seek is active. It works with any demuxer,
|
||||
@@ -2257,6 +2294,9 @@ Demuxer
|
||||
``--demuxer-mkv-subtitle-preroll-secs=<value>``
|
||||
See ``--demuxer-mkv-subtitle-preroll``.
|
||||
|
||||
``--demuxer-mkv-subtitle-preroll-secs-index=<value>``
|
||||
See ``--demuxer-mkv-subtitle-preroll``.
|
||||
|
||||
``--demuxer-mkv-probe-video-duration=<yes|no|full>``
|
||||
When opening the file, seek to the end of it, and check what timestamp the
|
||||
last video packet has, and report that as file duration. This is strictly
|
||||
@@ -2381,7 +2421,7 @@ Input
|
||||
|
||||
``--input-key-fifo-size=<2-65000>``
|
||||
Specify the size of the FIFO that buffers key events (default: 7). If it
|
||||
is too small some events may be lost. The main disadvantage of setting it
|
||||
is too small, some events may be lost. The main disadvantage of setting it
|
||||
to a very large value is that if you hold down a key triggering some
|
||||
particularly slow command then the player may be unresponsive while it
|
||||
processes all the queued commands.
|
||||
@@ -2396,7 +2436,7 @@ Input
|
||||
``--input-file=<filename>``
|
||||
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-unix-socket`` for something
|
||||
get replies or events. Use ``--input-ipc-server`` for something
|
||||
bi-directional. On MS Windows, JSON commands are not available.
|
||||
|
||||
This can also specify a direct file descriptor with ``fd://N`` (UNIX only).
|
||||
@@ -2416,12 +2456,18 @@ Input
|
||||
or intend to read from stdin later on via the loadfile or loadlist slave
|
||||
commands.
|
||||
|
||||
``--input-unix-socket=<filename>``
|
||||
``--input-ipc-server=<filename>``
|
||||
Enable the IPC support and create the listening socket at the given path.
|
||||
|
||||
See `JSON IPC`_ for details.
|
||||
On Linux and Unix, the given path is a regular filesystem path. On Windows,
|
||||
named pipes are used, so the path refers to the pipe namespace
|
||||
(``\\.\pipe\<name>``). If the ``\\.\pipe\`` prefix is missing, mpv will add
|
||||
it automatically before creating the pipe, so
|
||||
``--input-ipc-server=/tmp/mpv-socket`` and
|
||||
``--input-ipc-server=\\.\pipe\tmp\mpv-socket`` are equivalent for IPC on
|
||||
Windows.
|
||||
|
||||
Not available on MS Windows.
|
||||
See `JSON IPC`_ for details.
|
||||
|
||||
``--input-appleremote=<yes|no>``
|
||||
(OS X only)
|
||||
@@ -2448,7 +2494,7 @@ Input
|
||||
|
||||
On X11, a sub-window with input enabled grabs all keyboard input as long
|
||||
as it is 1. a child of a focused window, and 2. the mouse is inside of
|
||||
the sub-window. The can steal away all keyboard input from the
|
||||
the sub-window. It can steal away all keyboard input from the
|
||||
application embedding the mpv window, and on the other hand, the mpv
|
||||
window will receive no input if the mouse is outside of the mpv window,
|
||||
even though mpv has focus. Modern toolkits work around this weird X11
|
||||
@@ -2576,6 +2622,9 @@ OSD
|
||||
``--osd-bold=<yes|no>``, ``--sub-text-bold=<yes|no>``
|
||||
Format text on bold.
|
||||
|
||||
``--osd-italic=<yes|no>``, ``--sub-text-italic=<yes|no>``
|
||||
Format text on italic.
|
||||
|
||||
``--osd-border-color=<color>``, ``--sub-text-border-color=<color>``
|
||||
See ``--osd-color``. Color used for the OSD/sub font border.
|
||||
|
||||
@@ -2718,7 +2767,7 @@ Screenshot
|
||||
Specify the filename template used to save screenshots. The template
|
||||
specifies the filename without file extension, and can contain format
|
||||
specifiers, which will be substituted when taking a screenshot.
|
||||
By default the template is ``mpv-shot%n``, which results in filenames like
|
||||
By default, the template is ``mpv-shot%n``, which results in filenames like
|
||||
``mpv-shot0012.png`` for example.
|
||||
|
||||
The template can start with a relative or absolute path, in order to
|
||||
@@ -2874,7 +2923,7 @@ Terminal
|
||||
Particularly useful on slow terminals or broken ones which do not properly
|
||||
handle carriage return (i.e. ``\r``).
|
||||
|
||||
Also see ``--really-quiet`` and ``--msg-level``.
|
||||
See also: ``--really-quiet`` and ``--msg-level``.
|
||||
|
||||
``--really-quiet``
|
||||
Display even less output and status messages than with ``--quiet``.
|
||||
@@ -2916,6 +2965,22 @@ Terminal
|
||||
:debug: debug messages
|
||||
:trace: very noisy debug messages
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
::
|
||||
|
||||
mpv --msg-level=ao/sndio=no
|
||||
|
||||
Completely silences the output of ao_sndio, which uses the log
|
||||
prefix ``[ao/sndio]``.
|
||||
|
||||
::
|
||||
|
||||
mpv --msg-level=all=warn,ao/alsa=error
|
||||
|
||||
Only show warnings or worse, and let the ao_alsa output show errors
|
||||
only.
|
||||
|
||||
``--term-osd, --no-term-osd``, ``--term-osd=force``
|
||||
Display OSD messages on the console when no video output is available.
|
||||
Enabled by default.
|
||||
@@ -3007,8 +3072,9 @@ TV
|
||||
maximum size of the capture buffer in megabytes (default: dynamical)
|
||||
|
||||
``--tv-norm=<value>``
|
||||
See the console output for a list of all available norms, also see the
|
||||
``normid`` option below.
|
||||
See the console output for a list of all available norms.
|
||||
|
||||
See also: ``--tv-normid``.
|
||||
|
||||
``--tv-normid=<value> (v4l2 only)``
|
||||
Sets the TV norm to the given numeric ID. The TV norm depends on the
|
||||
@@ -3218,7 +3284,7 @@ Cache
|
||||
multiple cache streams, and using the same file for them obviously
|
||||
clashes.
|
||||
|
||||
Also see ``--cache-file-size``.
|
||||
See also: ``--cache-file-size``.
|
||||
|
||||
``--cache-file-size=<kBytes>``
|
||||
Maximum size of the file created with ``--cache-file``. For read accesses
|
||||
@@ -3346,8 +3412,13 @@ DVB
|
||||
|
||||
``--dvbin-full-transponder=<yes|no>``
|
||||
Apply no filters on program PIDs, only tune to frequency and pass full
|
||||
transponder to demuxer. This is useful to record multiple programs
|
||||
on a single transponder, or to work around issues in the ``channels.conf``.
|
||||
transponder to demuxer.
|
||||
The player frontend selects the streams from the full TS in this case,
|
||||
so the program which is shown initially may not match the chosen channel.
|
||||
Switching between the programs is possible by cycling the ``program``
|
||||
property.
|
||||
This is useful to record multiple programs on a single transponder,
|
||||
or to work around issues in the ``channels.conf``.
|
||||
It is also recommended to use this for channels which switch PIDs
|
||||
on-the-fly, e.g. for regional news.
|
||||
|
||||
@@ -3380,7 +3451,7 @@ Miscellaneous
|
||||
implement a perfect audio delay measurement. With this value, if large A/V
|
||||
sync offsets occur, they will only take about 1 or 2 seconds to settle
|
||||
out. This delay in reaction time to sudden A/V offsets should be the only
|
||||
side-effect of turning this option on, for all sound drivers.
|
||||
side effect of turning this option on, for all sound drivers.
|
||||
|
||||
``--video-sync=<audio|...>``
|
||||
How the player synchronizes audio and video.
|
||||
@@ -3509,3 +3580,58 @@ Miscellaneous
|
||||
Force the contents of the ``media-title`` property to this value. Useful
|
||||
for scripts which want to set a title, without overriding the user's
|
||||
setting in ``--title``.
|
||||
|
||||
``--external-file=<filename>``
|
||||
Add all tracks from the given file. Unlike ``--sub-file`` and
|
||||
``--audio-file``, this includes all tracks, and does not cause default
|
||||
stream selection over the "proper" file.
|
||||
|
||||
``--lavfi-complex=<string>``
|
||||
Set a "complex" libavfilter filter, which means a single filter graph can
|
||||
take input from multiple source audio and video tracks. The graph can result
|
||||
in a single audio or video output (or both).
|
||||
|
||||
Currently, the filter graph labels are used to select the participating
|
||||
input tracks and audio/video output. The following rules apply:
|
||||
|
||||
- A label of the form ``aidN`` selects audio track N as input (e.g.
|
||||
``aid1``).
|
||||
- A label of the form ``vidN`` selects video track N as input.
|
||||
- A label named ``ao`` will be connected to the audio input.
|
||||
- A label named ``vo`` will be connected to the video output.
|
||||
|
||||
Each label can be used only once. If you want to use e.g. an audio stream
|
||||
for multiple filters, you need to use the ``asplit`` filter. Multiple
|
||||
video or audio outputs are not possible, but you can use filters to merge
|
||||
them into one.
|
||||
|
||||
The complex filter can not be changed yet during playback. It's also not
|
||||
possible to change the tracks connected to the filter at runtime. Other
|
||||
tracks, as long as they're not connected to the filter, and the
|
||||
corresponding output is not connected to the filter, can still be freely
|
||||
changed.
|
||||
|
||||
Note that the normal filter chains (``--af``, ``--vf``) are applied between
|
||||
the complex graphs (e.g. ``ao`` label) and the actual output.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--lavfi-complex='[aid1] asplit [ao] [t] ; [t] aphasemeter [vo]'``
|
||||
Play audio track 1, and visualize it as video using the ``aphasemeter``
|
||||
filter.
|
||||
- ``--lavfi-complex='[aid1] [aid2] amix [ao]'``
|
||||
Play audio track 1 and 2 at the same time.
|
||||
- ``--lavfi-complex='[vid1] [vid2] vstack [vo]'``
|
||||
Stack video track 1 and 2 and play them at the same time. Note that
|
||||
both tracks need to have the same width, or filter initialization
|
||||
will fail (you can add ``scale`` filters before the ``vstack`` filter
|
||||
to fix the size).
|
||||
- ``--lavfi-complex='[aid1] asplit [ao] [t] ; [t] aphasemeter [t2] ; [vid1] [t2] overlay [vo]'``
|
||||
Play audio track 1, and overlay its visualization over video track 1.
|
||||
- ``--lavfi-complex='[aid1] asplit [t1] [ao] ; [t1] showvolume [t2] ; [vid1] [t2] overlay [vo]'``
|
||||
Play audio track 1, and overlay the measured volume for each speaker
|
||||
over video track 1.
|
||||
- ``null:// --lavfi-complex='life [vo]'``
|
||||
Conways' Life Game.
|
||||
|
||||
See the FFmpeg libavfilter documentation for details on the filter.
|
||||
+13
-12
@@ -130,7 +130,7 @@ these keys. In case of collision, the function needs to be bound to a
|
||||
different key. See the `Script Commands`_ section.
|
||||
|
||||
============= ================================================
|
||||
del Hide the OSC permanently until mpv is restarted.
|
||||
del Cycles visibility between never / auto (mouse-move) / always
|
||||
============= ================================================
|
||||
|
||||
Configuration
|
||||
@@ -206,8 +206,8 @@ Configurable Options
|
||||
|
||||
``hidetimeout``
|
||||
| Default: 500
|
||||
| Duration in ms until the OSC hides if no mouse movement, negative value
|
||||
disables auto-hide
|
||||
| Duration in ms until the OSC hides if no mouse movement, must not be
|
||||
negative
|
||||
|
||||
``fadeduration``
|
||||
| Default: 200
|
||||
@@ -243,28 +243,29 @@ Configurable Options
|
||||
| Default: no
|
||||
| Display timecodes with milliseconds
|
||||
|
||||
``visibility``
|
||||
| Default: auto (auto hide/show on mouse move)
|
||||
| Also supports ``never`` and ``always``
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The OSC script listens to certain script commands. These commands can bound
|
||||
in ``input.conf``, or sent by other scripts.
|
||||
|
||||
``enable-osc``
|
||||
Undoes ``disable-osc`` or the effect of the ``del`` key.
|
||||
|
||||
``disable-osc``
|
||||
Hide the OSC permanently. This is also what the ``del`` key does.
|
||||
|
||||
``osc-message``
|
||||
Show a message on screen using the OSC. First argument is the message,
|
||||
second the duration in seconds.
|
||||
|
||||
``osc-visibility``
|
||||
Controls visibility mode ``never`` / ``auto`` (on mouse move) / ``always``
|
||||
and also ``cycle`` to cycle between the modes
|
||||
|
||||
Example
|
||||
|
||||
You could put this into ``input.conf`` to hide the OSC with the ``a`` key and
|
||||
to unhide it with ``b``::
|
||||
to set auto mode (the default) with ``b``::
|
||||
|
||||
a script_message disable-osc
|
||||
b script_message enable-osc
|
||||
a script_message osc-visibility never
|
||||
b script_message osc-visibility auto
|
||||
|
||||
+34
-35
@@ -151,15 +151,15 @@ Available filters are:
|
||||
:yes: Enable accurate rounding.
|
||||
|
||||
``dsize[=w:h:aspect-method:r:aspect]``
|
||||
Changes the intended display size/aspect at an arbitrary point in the
|
||||
Changes the intended display aspect at an arbitrary point in the
|
||||
filter chain. Aspect can be given as a fraction (4/3) or floating point
|
||||
number (1.33). Alternatively, you may specify the exact display width and
|
||||
height desired. Note that this filter does *not* do any scaling itself; it
|
||||
number (1.33). Note that this filter does *not* do any scaling itself; it
|
||||
just affects what later scalers (software or hardware) will do when
|
||||
auto-scaling to the correct aspect.
|
||||
|
||||
``<w>,<h>``
|
||||
New display width and height.
|
||||
New aspect ratio given by a display width and height. Unlike older mpv
|
||||
versions or MPlayer, this does not set the display size.
|
||||
|
||||
Can also be these special values:
|
||||
|
||||
@@ -289,27 +289,27 @@ Available filters are:
|
||||
:prophoto: ProPhoto RGB (ROMM)
|
||||
:cie1931: CIE 1931 RGB
|
||||
|
||||
``<gamma>``
|
||||
Gamma function the source file was encoded with. Normally this should be set
|
||||
in the file header, but when playing broken or mistagged files this can be
|
||||
used to override the setting.
|
||||
``<gamma>``
|
||||
Gamma function the source file was encoded with. Normally this should be set
|
||||
in the file header, but when playing broken or mistagged files this can be
|
||||
used to override the setting.
|
||||
|
||||
This option only affects video output drivers that perform color management.
|
||||
This option only affects video output drivers that perform color management.
|
||||
|
||||
If this option is set to ``auto`` (which is the default), the gamma will
|
||||
be set to BT.1886 for YCbCr content, sRGB for RGB content and Linear for
|
||||
XYZ content.
|
||||
If this option is set to ``auto`` (which is the default), the gamma will
|
||||
be set to BT.1886 for YCbCr content, sRGB for RGB content and Linear for
|
||||
XYZ content.
|
||||
|
||||
Available gamma functions are:
|
||||
Available gamma functions are:
|
||||
|
||||
:auto: automatic selection (default)
|
||||
:bt.1886: ITU-R BT.1886 (approximation of BT.601/BT.709/BT.2020 curve)
|
||||
:srgb: IEC 61966-2-4 (sRGB)
|
||||
:linear: Linear light
|
||||
:gamma1.8: Pure power curve (gamma 1.8)
|
||||
:gamma2.2: Pure power curve (gamma 2.2)
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:auto: automatic selection (default)
|
||||
:bt.1886: ITU-R BT.1886 (approximation of BT.601/BT.709/BT.2020 curve)
|
||||
:srgb: IEC 61966-2-4 (sRGB)
|
||||
:linear: Linear light
|
||||
:gamma1.8: Pure power curve (gamma 1.8)
|
||||
:gamma2.2: Pure power curve (gamma 2.2)
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
|
||||
``<stereo-in>``
|
||||
Set the stereo mode the video is assumed to be encoded in. Takes the
|
||||
@@ -438,9 +438,8 @@ Available filters are:
|
||||
generating an occasional mismatched frame, but it may also cause an
|
||||
excessive number of frames to be dropped during high motion sequences.
|
||||
Conversely, setting it to -1 will make ``pullup`` match fields more
|
||||
easily. This may help processing of video where there is slight
|
||||
blurring between the fields, but may also cause there to be interlaced
|
||||
frames in the output.
|
||||
easily. This may help process video with slight blurring between the
|
||||
fields, but may also cause interlaced frames in the output.
|
||||
|
||||
``mp`` (metric plane)
|
||||
This option may be set to ``u`` or ``v`` to use a chroma plane instead of the
|
||||
@@ -455,16 +454,15 @@ Available filters are:
|
||||
|
||||
``<mode>``
|
||||
:frame: Output 1 frame for each frame.
|
||||
:field: Output 1 frame for each field.
|
||||
:field: Output 1 frame for each field (default).
|
||||
:frame-nospatial: Like ``frame`` but skips spatial interlacing check.
|
||||
:field-nospatial: Like ``field`` but skips spatial interlacing check.
|
||||
|
||||
``<interlaced-only>``
|
||||
:no: Deinterlace all frames (default).
|
||||
:yes: Only deinterlace frames marked as interlaced (default if this
|
||||
filter is inserted via ``deinterlace`` property).
|
||||
:no: Deinterlace all frames.
|
||||
:yes: Only deinterlace frames marked as interlaced (default).
|
||||
|
||||
This filter, is automatically inserted when using the ``d`` key (or any
|
||||
This filter is automatically inserted when using the ``d`` key (or any
|
||||
other key that toggles the ``deinterlace`` property or when using the
|
||||
``--deinterlace`` switch), assuming the video output does not have native
|
||||
deinterlacing support.
|
||||
@@ -473,7 +471,7 @@ Available filters are:
|
||||
into ``--vf-defaults`` instead, and enable deinterlacing with ``d`` or
|
||||
``--deinterlace``.
|
||||
|
||||
Also note that the ``d`` key is stupid enough to insert a deinterlacer twice
|
||||
Also, note that the ``d`` key is stupid enough to insert a deinterlacer twice
|
||||
when inserting yadif with ``--vf``, so using the above methods is
|
||||
recommended.
|
||||
|
||||
@@ -645,10 +643,11 @@ Available filters are:
|
||||
``buffered-frames``
|
||||
Maximum number of decoded video frames that should be buffered before
|
||||
the filter (default: 4). This specifies the maximum number of frames
|
||||
the script can requests backwards. E.g. if ``buffered-frames=5``, and
|
||||
the script just requested frame 15, it can still request frame 10, but
|
||||
frame 9 is not available anymore. If it requests frame 30, mpv will
|
||||
decode 15 more frames, and keep only frames 25-30.
|
||||
the script can request in reverse direction.
|
||||
E.g. if ``buffered-frames=5``, and the script just requested frame 15,
|
||||
it can still request frame 10, but frame 9 is not available anymore.
|
||||
If it requests frame 30, mpv will decode 15 more frames, and keep only
|
||||
frames 25-30.
|
||||
|
||||
The actual number of buffered frames also depends on the value of the
|
||||
``concurrent-frames`` option. Currently, both option values are
|
||||
@@ -803,6 +802,6 @@ Available filters are:
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
useless, except for debugging. (Note that this won't help smoothing out
|
||||
useless, except for debugging. (Note that this won't help to smooth out
|
||||
latencies with decoding, because the filter will never output a frame if
|
||||
the buffer isn't full, except on EOF.)
|
||||
+48
-29
@@ -444,13 +444,15 @@ Available video output drivers are:
|
||||
Disable the scaler if the video image is not resized. In that case,
|
||||
``bilinear`` is used instead whatever is set with ``scale``. Bilinear
|
||||
will reproduce the source image perfectly if no scaling is performed.
|
||||
Note that this option never affects ``cscale``.
|
||||
Enabled by default. Note that this option never affects ``cscale``.
|
||||
|
||||
``pbo``
|
||||
Enable use of PBOs. This is slightly faster, but can sometimes lead to
|
||||
sporadic and temporary image corruption (in theory, because reupload
|
||||
is not retried when it fails), and perhaps actually triggers slower
|
||||
paths with drivers that don't support PBOs properly.
|
||||
Enable use of PBOs. On some drivers this can be faster, especially if
|
||||
the source video size is huge (e.g. so called "4K" video). On other
|
||||
drivers it might be slower or cause latency issues.
|
||||
|
||||
In theory, this can sometimes lead to sporadic and temporary image
|
||||
corruption (because reupload is not retried when it fails).
|
||||
|
||||
``dither-depth=<N|no|auto>``
|
||||
Set dither target depth to N. Default: no.
|
||||
@@ -481,7 +483,7 @@ Available video output drivers are:
|
||||
|
||||
``temporal-dither``
|
||||
Enable temporal dithering. (Only active if dithering is enabled in
|
||||
general.) This changes between 8 different dithering pattern on each
|
||||
general.) This changes between 8 different dithering patterns on each
|
||||
frame by changing the orientation of the tiled dithering matrix.
|
||||
Unfortunately, this can lead to flicker on LCD displays, since these
|
||||
have a high reaction time.
|
||||
@@ -492,7 +494,7 @@ Available video output drivers are:
|
||||
video frame, 2 on every other frame, etc.
|
||||
|
||||
``debug``
|
||||
Check for OpenGL errors, i.e. call ``glGetError()``. Also request a
|
||||
Check for OpenGL errors, i.e. call ``glGetError()``. Also, request a
|
||||
debug OpenGL context (which does nothing with current graphics drivers
|
||||
as of this writing).
|
||||
|
||||
@@ -547,6 +549,20 @@ Available video output drivers are:
|
||||
manifest themselves as short flashes or fringes of black, mostly
|
||||
around moving edges) in exchange for potentially adding more blur.
|
||||
|
||||
``interpolation-threshold=<0..1,-1>``
|
||||
Threshold below which frame ratio interpolation gets disabled (default:
|
||||
``0.0001``). This is calculated as ``abs(disphz/vfps - 1) < threshold``,
|
||||
where ``vfps`` is the speed-adjusted display FPS, and ``disphz`` the
|
||||
display refresh rate.
|
||||
|
||||
The default is intended to almost always enable interpolation if the
|
||||
playback rate is even slightly different from the display refresh rate.
|
||||
But note that if you use e.g. ``--video-sync=display-vdrop``, small
|
||||
deviations in the rate can disable interpolation and introduce a
|
||||
discontinuity every other minute.
|
||||
|
||||
Set this to ``-1`` to disable this logic.
|
||||
|
||||
``dscale-radius``, ``cscale-radius``, ``tscale-radius``, etc.
|
||||
Set filter parameters for ``dscale``, ``cscale`` and ``tscale``,
|
||||
respectively.
|
||||
@@ -566,12 +582,10 @@ Available video output drivers are:
|
||||
better than without it) since it will extend the size to match only the
|
||||
milder of the scale factors between the axes.
|
||||
|
||||
``prescale=<filter>``
|
||||
This option provides non-convolution-based filters for upscaling. These
|
||||
filters resize the video to multiple of the original size (all currently
|
||||
supported prescalers can only perform image doubling in a single pass).
|
||||
Generally another convolution based filter (the main scaler) will be
|
||||
applied after prescaler to match the target display size.
|
||||
``prescale-luma=<filter>``
|
||||
Apply additional pre-scaling (image doubling) on the luma plane
|
||||
(if present). As the name implies, these will run before the main
|
||||
upscaling pass.
|
||||
|
||||
``none``
|
||||
Disable all prescalers. This is the default.
|
||||
@@ -589,10 +603,6 @@ Available video output drivers are:
|
||||
Extremely slow and requires a recent mid or high end graphics card
|
||||
to work smoothly (as of 2015).
|
||||
|
||||
Note that all the filters above are designed (or implemented) to process
|
||||
luma plane only and probably won't work as intended for video in RGB
|
||||
format.
|
||||
|
||||
``prescale-passes=<1..5>``
|
||||
The number of passes to apply the prescaler (defaults to be 1). Setting
|
||||
it to 2 will perform a 4x upscaling.
|
||||
@@ -655,7 +665,11 @@ Available video output drivers are:
|
||||
|
||||
These files must define a function with the following signature::
|
||||
|
||||
vec4 sample(sampler2D tex, vec2 pos, vec2 tex_size)
|
||||
vec4 sample_pixel(sampler2D tex, vec2 pos, vec2 tex_size)
|
||||
|
||||
(If there is no string ``sample_pixel`` in the shader script, it will
|
||||
use ``sample`` instead. This is a compatibility hack for older shader
|
||||
scripts, and is deprecated.)
|
||||
|
||||
The meanings of the parameters are as follows:
|
||||
|
||||
@@ -741,7 +755,7 @@ Available video output drivers are:
|
||||
|
||||
``glfinish``
|
||||
Call ``glFinish()`` before and after swapping buffers (default: disabled).
|
||||
Slower, but might help getting better results when doing framedropping.
|
||||
Slower, but might improve results when doing framedropping.
|
||||
Can completely ruin performance. The details depend entirely on the
|
||||
OpenGL driver.
|
||||
|
||||
@@ -770,9 +784,9 @@ Available video output drivers are:
|
||||
The value ``auto`` will try to determine whether the compositor is
|
||||
active, and calls ``DwmFlush`` only if it seems to be.
|
||||
|
||||
This may help getting more consistent frame intervals, especially with
|
||||
high-fps clips - which might also reduce dropped frames. Typically a
|
||||
value of ``windowed`` should be enough since full screen may bypass the
|
||||
This may help to get more consistent frame intervals, especially with
|
||||
high-fps clips - which might also reduce dropped frames. Typically, a
|
||||
value of ``windowed`` should be enough, since full screen may bypass the
|
||||
DWM.
|
||||
|
||||
Windows only.
|
||||
@@ -794,10 +808,11 @@ Available video output drivers are:
|
||||
angle
|
||||
Direct3D11 through the OpenGL ES translation layer ANGLE. This
|
||||
supports almost everything the ``win`` backend does, except ICC
|
||||
profiles, high bit depth video input, and the ``nnedi3`` prescaler.
|
||||
profiles, and the ``nnedi3`` prescaler.
|
||||
dxinterop (experimental)
|
||||
Win32, using WGL for rendering and Direct3D 9Ex for presentation.
|
||||
Works on Nvidia and AMD only.
|
||||
Works on Nvidia and AMD. Newer Intel chips with the latest drivers
|
||||
may also work.
|
||||
x11
|
||||
X11/GLX
|
||||
wayland
|
||||
@@ -822,8 +837,8 @@ Available video output drivers are:
|
||||
influence performance and quality of the video output.
|
||||
``fmt`` can be one of: rgb, rgba, rgb8, rgb10, rgb10_a2, rgb16, rgb16f,
|
||||
rgb32f, rgba12, rgba16, rgba16f, rgba32f.
|
||||
Default: ``auto``, which maps to rgba16 on desktop GL, and rgb10_a2 on
|
||||
GLES (e.g. ANGLE).
|
||||
Default: ``auto``, which maps to rgba16 on desktop GL, and rgba16f or
|
||||
rgb10_a2 on GLES (e.g. ANGLE).
|
||||
|
||||
``gamma=<0.1..2.0>``
|
||||
Set a gamma value (default: 1.0). If gamma is adjusted in other ways
|
||||
@@ -872,6 +887,8 @@ Available video output drivers are:
|
||||
ProPhoto RGB (ROMM)
|
||||
cie1931
|
||||
CIE 1931 RGB (not to be confused with CIE XYZ)
|
||||
dci-p3
|
||||
DCI-P3 (Digital Cinema Colorspace), SMPTE RP431-2
|
||||
|
||||
``target-trc=<value>``
|
||||
Specifies the transfer characteristics (gamma) of the display. Video
|
||||
@@ -955,9 +972,11 @@ Available video output drivers are:
|
||||
things like softsubbed ASS signs to match the video colors,
|
||||
but may cause SRT subtitles or similar to look slightly off.
|
||||
|
||||
``alpha=<blend|yes|no>``
|
||||
Decides what to do if the input has an alpha component (default: blend).
|
||||
``alpha=<blend-tiles|blend|yes|no>``
|
||||
Decides what to do if the input has an alpha component.
|
||||
|
||||
blend-tiles
|
||||
Blend the frame against a 16x16 gray/white tiles background (default).
|
||||
blend
|
||||
Blend the frame against a black background.
|
||||
yes
|
||||
@@ -984,7 +1003,7 @@ Available video output drivers are:
|
||||
|
||||
This is equivalent to::
|
||||
|
||||
--vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:correct-downscaling:sigmoid-upscaling:pbo:deband:es=no
|
||||
--vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:correct-downscaling:sigmoid-upscaling:deband:es=no
|
||||
|
||||
Note that some cheaper LCDs do dithering that gravely interferes with
|
||||
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.
|
||||
|
||||
@@ -111,10 +111,6 @@ the separately available build wrapper ([mpv-build][mpv-build]) that first compi
|
||||
libraries and libass, and then compiles the player statically linked against
|
||||
those.
|
||||
|
||||
If you are running Mac OSX and using homebrew we provide [homebrew-mpv][homebrew-mpv], an up
|
||||
to date formula that compiles mpv with sensible dependencies and defaults for
|
||||
OSX.
|
||||
|
||||
If you want to build a Windows binary, you either have to use MSYS2 and MinGW,
|
||||
or cross-compile from Linux with MinGW. See [Windows compilation][windows_compilation].
|
||||
|
||||
@@ -127,20 +123,28 @@ of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work
|
||||
with FFmpeg only (subtitle formats in particular).
|
||||
|
||||
|
||||
## Preferred FFmpeg version
|
||||
|
||||
|
||||
Using the latest FFmpeg release (or FFmpeg git master) is strongly recommended.
|
||||
Older versions are unsupported, even if the build system still happens to
|
||||
accept them. The main reason mpv still builds with older FFmpeg versions is to
|
||||
evade arguing with people (users, distros) who insist on using older FFmpeg
|
||||
versions for no rational reason.
|
||||
|
||||
If you want to use a stable FFmpeg release, use the latest release, which has
|
||||
most likely the best maintenance out of all stable releases. Older releases
|
||||
are for distros, and at best receive basic changes like fixing critical security
|
||||
issues or build fixes, and at worst are completely abandoned.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
a 0.X.0 version number. No further maintenance is done.
|
||||
|
||||
Every few months, a new release is cut off of the master branch and is assigned
|
||||
a 0.X.0 version number.
|
||||
|
||||
As part of the maintenance process, minor releases are made, which are assigned
|
||||
0.X.Y version numbers. Minor releases contain bug fixes only. They never merge
|
||||
the master branch, and no features are added to it. Only the latest release is
|
||||
maintained.
|
||||
|
||||
The goal of releases is to provide stability and an unchanged base for the sake
|
||||
of Linux distributions. If you want the newest features, just use the master
|
||||
branch, which is stable most of the time, except sometimes, when it's not.
|
||||
The goal of releases is to make Linux distributions happy. Linux distributions
|
||||
are also expected to apply their own patches in case of bugs and security
|
||||
issues.
|
||||
|
||||
Releases other than the latest release are unsupported and unmaintained.
|
||||
|
||||
|
||||
+278
@@ -1,3 +1,281 @@
|
||||
Release 0.17.0
|
||||
==============
|
||||
|
||||
Note: The client API examples have moved to https://github.com/mpv-player/mpv-examples
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- install symbolic SVG icon
|
||||
- build: allow plain-gl build on OSX (#2980)
|
||||
- build: disable encoding mode by default (uses deprecated FFmpeg APIs)
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- csputils: add DCI-P3 colorspace
|
||||
- d3d11va hwdec
|
||||
- demux: add null demuxer
|
||||
- ipc: add Windows implementation with named pipes
|
||||
- mediacodec decoder hwdec wrapper
|
||||
- vo_opengl: add dxva2 interop to angle backend
|
||||
- vo_opengl: generate 3DLUT against source and use full BT.1886 (#2815)
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- command: add cache-speed property
|
||||
- command: add keepaspect property
|
||||
- command: add video-stereo-mode property (#2994)
|
||||
- command: export canonical ffmpeg version identifier (ffmpeg-version)
|
||||
- command: export lists of all codecs (decoder-list and encoder-list)
|
||||
- osd: add italic font for osd (#3031)
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- aspect: make video-zoom logarithmic (#3004)
|
||||
- command: export more information under track-list
|
||||
- ipc: rename --input-unix-socket to --input-ipc-server
|
||||
- vo_opengl: decrease default superxbr-edge-strength
|
||||
- vo_opengl: rename prescale to prescale-luma
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- TOOLS/zsh.pl: don't complete URLs by default unless no files match (#2892)
|
||||
- ad_lavc, vd_lavc: support new Libav decoding API
|
||||
- cache: disable useless "Cache is not responding" warning (#3019)
|
||||
- demux: delay bitrate calculation on packets with unknown timestamps (#2903)
|
||||
- demux_timeline: set correct seekable flags (#2898)
|
||||
- input: accept plain text for drag and drop (#2945)
|
||||
- input: do not force double-click emulation for artificial commands (#2899)
|
||||
- lavc_conv: fix Libav srt subtitles (#2888)
|
||||
- player: add missing audio reconfig events (#2929, #2920)
|
||||
- player: add wv to list of external audio file extensions
|
||||
- player: fix --stream-dump exit code (#2848)
|
||||
- player: fix breakage when combining 3D and rotate auto-filters
|
||||
- playlist: improve shuffle algorithm (better uniformity)
|
||||
- sub: interpret "text" subtitles as srt
|
||||
- vo_opengl, osd: allow osc.lua to react faster on resizes
|
||||
- vo_opengl: GLX: try to create 3.3 core profile context (#2938)
|
||||
- vo_opengl: draw transparency checkerboard after upscaling
|
||||
- vo_opengl: fix operation without GL_ARB_texture_rg
|
||||
- vo_opengl: improve superxbr algorithm
|
||||
- vo_opengl: only open one OpenGL/DX interop handle when using dxva2 (fixes interop with AMD drivers)
|
||||
- wayland: don't set fs mode on every configure (#2817)
|
||||
- x11: do not set _NET_WM_BYPASS_COMPOSITOR by default (#2997)
|
||||
- ytdl_hook: handle optional format_note
|
||||
|
||||
|
||||
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.16.0..v0.17.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.16.0...v0.17.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.16.0..v0.17.0
|
||||
|
||||
|
||||
|
||||
Release 0.16.0
|
||||
==============
|
||||
|
||||
This release changes the license of some non-MPlayer source files to LGPL 2.1 or later.
|
||||
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- build: enable vaapi under drm-only as well (issue #2808)
|
||||
- build: enable vo_opengl_cb if GL headers are present
|
||||
- build: make libavfilter mandatory
|
||||
- build: make posix_spawn optional
|
||||
- wscript: don’t install the encoding profiles with encoding disabled
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- Initial Android support
|
||||
- ao: initial OpenSL ES support
|
||||
- dxva2: support HEVC Main 10
|
||||
- osc: add always-on mode and unify visibility mode (always/never/auto)
|
||||
- player: add complex filter graph support
|
||||
- rpi: add mpeg-4, vc-1 decoding support
|
||||
- stream_dvb: support frontends with multiple delivery systems (e.g. DVB-C/DVB-T combo cards)
|
||||
- vo_opengl: 10 bit support with ANGLE
|
||||
- vo_opengl: add KMS/DRM VAAPI hardware decoding interop
|
||||
- vo_opengl: dxinterop: add dxva2 passthrough
|
||||
- vo_rpi: add geometry handling (--geometry, --autofit, fullscreen switching, etc.)
|
||||
- vo_x11: add 16bpp support
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- --lavfi-complex option for complex filter graphs
|
||||
- audio: change downmix behavior, add --audio-normalize-downmix
|
||||
- command: add vf-command and af-command commands
|
||||
- player: add --external-file option
|
||||
- vo_opengl: add interpolation-threshold sub-option
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- audio: change --audio-channels default back to stereo
|
||||
- audio: remove default preference for libdcadec (decoder was merged with FFmpeg)
|
||||
- command: always allow setting volume/mute properties
|
||||
- command: show original aspect in video-aspect property too
|
||||
- input: ignore --input-cursor for events injected by input commands (issue #2750)
|
||||
- options: set fs=yes by default on RPI, and change RPI defaults handling
|
||||
- sub: implement "sub-seek 0" (issue #2791)
|
||||
- vo_opengl: default scaler-resizes-only sub-option to yes
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- OS X/cocoa: fix charcode retrieving for accented characters
|
||||
- TOOLS/lua/ao-null-reload.lua: send ao-reload on audio-device-list change (issue #2738)
|
||||
- TOOLS/lua/autoload.lua: remove the extension prior to sort
|
||||
- Windows: fix dropping URIs (issue #2782)
|
||||
- af_lavrresample: prevent channels from being dropped, e.g. when going 7.1 -> 7.1(wide) and similar cases
|
||||
- ao_coreaudio: fix 7.1(rear) channel mapping
|
||||
- ao_openal: wipe out global context on init error (PR #2719)
|
||||
- ao_wasapi: avoid under-run cascade in exclusive mode
|
||||
- ao_wasapi: set buffer size to device period in exclusive mode
|
||||
- audio: fix spdif PCM fallback
|
||||
- build: add special openbsd case for iconv check (issue #2710)
|
||||
- command: fix NULL pointer deref in "video-codec" property (issue #2729)
|
||||
- command: fix track cycling logic (issue #2784)
|
||||
- demux: disable stream cache if no tracks are selected (issue #2692)
|
||||
- demux_mkv: add hack to fix opus gapless behavior
|
||||
- demux_mkv: support channel layout in VfW muxed PCM (issue #2820)
|
||||
- osc: fix runtime enable_osc(true/false)
|
||||
- player: fix initial audio sync in certain cases (issue #2770)
|
||||
- player: honor --force-window if video is selected, but inactive
|
||||
- player: never show "DS: (unavailable)"
|
||||
- player: restore old/correct --force-window behavior (issue #2825)
|
||||
- player: rewrite timeline/ordered chapter support
|
||||
- vaapi: fix compilation on older FFmpeg/Libav (issue #2737)
|
||||
- vdpau: force driver to report preemption early
|
||||
- video: don't wait for last video frame in the normal case (issue #2745)
|
||||
- video: fix coverart switching
|
||||
- video: slightly improve video stream switching
|
||||
- vo_opengl: add precision qualifier to usampler2D on ANGLE (issue #2761)
|
||||
- vo_opengl: default to rgba16f FBOs on ANGLE
|
||||
- vo_opengl: don't use normalized coords for debanding rectangle textures (issue #2831)
|
||||
- vo_opengl: dxinterop: fix compatibility issue with Vista
|
||||
- vo_opengl: pass the correct target to deband functions with Apple hwdec interop
|
||||
- vo_opengl: rename custom shader entrypoint from sample to sample_pixel (issue #2733)
|
||||
- x11: get *current* XRandR screen configuration instead of polling for new screens, too
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API. A complete changelog can be seen by running
|
||||
`git log v0.15.0..v0.16.0` in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.15.0...v0.16.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.15.0..v0.16.0
|
||||
|
||||
|
||||
|
||||
Release 0.15.0
|
||||
==============
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- OS X bundle: remove git sha from the Info.plist version (issue #2677)
|
||||
- add "lua51" ("51obsd") to list of possible lua names
|
||||
- add option to customize config files system path (issue #2704)
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- vo_opengl: implement support for transparent video display on OS X (alpha=yes suboption)
|
||||
- vo_opengl: use a checkerboard pattern as background for transparent video by default
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- add --audio-file-paths (issue #2632)
|
||||
- player, stream_dvb: implement dvb-channel-name property, add switch binding
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- vf_stereo3d: add alternating modes
|
||||
- vo_opengl: disable pbo by default for opengl-hq due to driver problems
|
||||
- vf_yadif: change defaults (issue #2539)
|
||||
- command: change heuristic for files with 1 chapter (issue #2550)
|
||||
- demux_mkv: adjust subtitle preroll defaults
|
||||
- exclude 360 from --video-rotate range (issue #2647)
|
||||
- osd: make osd-width/height properties watchable
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- ao_pulse: check for sample rate bounds, attempt fallback (issue #2654)
|
||||
- ao_wasapi: remove volume "restore" on exit
|
||||
- demux_cue: better error resilience
|
||||
- mixer: fix volume initialization with --af=volume
|
||||
- mpv.desktop: add audio/mp4 mime type
|
||||
- player: detect audio PTS jumps, make video PTS heuristic less aggressive
|
||||
- player: make watch later/resume work when "playing" directories
|
||||
- player: reset playback abort when reloading a file (issue #2568)
|
||||
- recognize frame sequenced 3D Matroska video
|
||||
- stream_dvb: fix channel switching
|
||||
- vaapi: add VP9 profile (requires VA-API 0.38.1 or newer)
|
||||
- vo_opengl: dxinterop: prevent crash after lost device
|
||||
- vo_opengl: enable brightness/contrast controls for RGB
|
||||
- vo_opengl: fix operation on GLES 2.0
|
||||
- vo_opengl: fix operation on GLSL versions earlier than 1.30
|
||||
- vo_opengl: flip screenshot image if backend uses flipped rendering (issue #2635)
|
||||
- vo_opengl: reset nnedi3 weights properly (issue #2661)
|
||||
- vo_rpi: handle rotation
|
||||
- vo_rpi: work around firmware oddness leading to incorrect video rect
|
||||
- windows: fix fd://
|
||||
- ytdl: Include Referer header as well
|
||||
- TOOLS/zsh.pl: add .opus extension in zsh completions
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API. A complete changelog can be seen by running
|
||||
`git log v0.14.0..v0.15.0` in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.14.0...v0.15.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.14.0..v0.15.0
|
||||
|
||||
|
||||
|
||||
Release 0.14.0
|
||||
==============
|
||||
|
||||
|
||||
+7
-1
@@ -11,4 +11,10 @@ to mpv's command line.
|
||||
Where appropriate, they may also be placed in ~/.config/mpv/scripts/ from
|
||||
where they will be automatically loaded when mpv starts.
|
||||
|
||||
Some of these are just for testing mpv internals.
|
||||
This is only a small selection of internally maintained scripts. Some of them
|
||||
are just for testing mpv internals, or serve as examples. An extensive
|
||||
user-edited list of 3rd party scripts is available here:
|
||||
|
||||
https://github.com/mpv-player/mpv/wiki/User-Scripts
|
||||
|
||||
(Anyone can add their own scripts to that list.)
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Handles the edge case where previous attempts to init audio have failed, but
|
||||
-- might start working due to a newly added device. This is required in
|
||||
-- particular for ao=wasapi, since the internal IMMNotificationClient code that
|
||||
-- normally triggers ao-reload will not be running in this case.
|
||||
|
||||
function do_reload()
|
||||
mp.command("ao-reload")
|
||||
reloading = nil
|
||||
end
|
||||
|
||||
function on_audio_device_list_change()
|
||||
if mp.get_property("current-ao") == "null" and not reloading then
|
||||
mp.msg.verbose("audio-device-list changed: reloading audio")
|
||||
-- avoid calling ao-reload too often
|
||||
reloading = mp.add_timeout(0.5, do_reload)
|
||||
end
|
||||
end
|
||||
|
||||
mp.set_property("options/audio-fallback-to-null", "yes")
|
||||
mp.observe_property("audio-device-list", "native", on_audio_device_list_change)
|
||||
@@ -72,6 +72,11 @@ function find_and_add_entries()
|
||||
return EXTENSIONS[string.lower(ext)]
|
||||
end)
|
||||
table.sort(files, function (a, b)
|
||||
local len = string.len(a) - string.len(b)
|
||||
if len ~= 0 then -- case for ordering filename ending with such as X.Y.Z
|
||||
local ext = string.len(get_extension(a)) + 1
|
||||
return string.sub(a, 1, -ext) < string.sub(b, 1, -ext)
|
||||
end
|
||||
return string.lower(a) < string.lower(b)
|
||||
end)
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
-- zones.lua: mpv script for handling commands depending on where the mouse pointer is at,
|
||||
-- mostly for mouse wheel handling, by configuring it via input.conf, e.g.:
|
||||
--
|
||||
-- Ported from avih's ( https://github.com/avih ) zones.js
|
||||
--
|
||||
-- Vertical positions can be top, middle, bottom or "*" to represent the whole column.
|
||||
-- Horizontal positions can be left, middle, bottom or "*" to represent the whole row.
|
||||
-- "default" will be the fallback command to be used if no command is assigned to that area.
|
||||
--
|
||||
-- input.conf example of use:
|
||||
-- # wheel up/down with mouse
|
||||
-- MOUSE_BTN3 script_message_to zones commands "middle-right: add brightness 1" "*-left: add volume 5" "default: seek 10"
|
||||
-- MOUSE_BTN4 script_message_to zones commands "middle-right: add brightness -1" "*-left: add volume -5" "default: seek -10"
|
||||
|
||||
local ZONE_THRESH_PERCENTAGE = 20;
|
||||
-- sides get 20% each, mid gets 60%, same vertically
|
||||
|
||||
local msg = mp.msg
|
||||
|
||||
function getMouseZone()
|
||||
-- returns the mouse zone as two strings [top/middle/bottom], [left/middle/right], e.g. "middle", "right"
|
||||
|
||||
local screenW, screenH = mp.get_osd_resolution()
|
||||
local mouseX, mouseY = mp.get_mouse_pos()
|
||||
|
||||
local threshY = screenH * ZONE_THRESH_PERCENTAGE / 100
|
||||
local threshX = screenW * ZONE_THRESH_PERCENTAGE / 100
|
||||
|
||||
local yZone = (mouseY < threshY) and "top" or (mouseY < (screenH - threshY)) and "middle" or "bottom"
|
||||
local xZone = (mouseX < threshX) and "left" or (mouseX < (screenW - threshX)) and "middle" or "right"
|
||||
|
||||
return yZone, xZone
|
||||
end
|
||||
|
||||
function main (...)
|
||||
local arg={...}
|
||||
msg.debug('commands: \n\t'..table.concat(arg,'\n\t'))
|
||||
|
||||
local keyY, keyX = getMouseZone()
|
||||
msg.debug("mouse at: " .. keyY .. '-' .. keyX)
|
||||
|
||||
local fallback = nil
|
||||
|
||||
for i, v in ipairs(arg) do
|
||||
cmdY = v:match("^([%w%*]+)%-?[%w%*]*:")
|
||||
cmdX = v:match("^[%w%*]*%-([%w%*]+)%s*:")
|
||||
cmd = v:match("^[%S]-%s*:%s*(.+)")
|
||||
msg.debug('cmdY: '..tostring(cmdY))
|
||||
msg.debug('cmdX: '..tostring(cmdX))
|
||||
msg.debug('cmd : '..tostring(cmd))
|
||||
|
||||
if (cmdY == keyY and cmdX == keyX) then
|
||||
msg.verbose("running cmd: "..cmd)
|
||||
mp.command(cmd)
|
||||
return
|
||||
elseif (cmdY == "*" and cmdX == keyX) or
|
||||
(cmdY == keyY and cmdX == "*") then
|
||||
msg.verbose("running cmd: "..cmd)
|
||||
mp.command(cmd)
|
||||
return
|
||||
elseif cmdY == "default" then
|
||||
fallback = cmd
|
||||
end
|
||||
end
|
||||
if fallback ~= nil then
|
||||
msg.verbose("running cmd: "..fallback)
|
||||
mp.command(fallback)
|
||||
return
|
||||
else
|
||||
msg.debug("no command assigned for "..keyY .. '-' .. keyX)
|
||||
return
|
||||
end
|
||||
end
|
||||
mp.register_script_message("commands", main)
|
||||
-1013
File diff suppressed because it is too large.
Load diff
@@ -1,488 +0,0 @@
|
||||
# MPlayer Makefile
|
||||
#
|
||||
# copyright (c) 2008 Diego Biurrun
|
||||
# Rewritten entirely from a set of Makefiles written by Arpi and many others.
|
||||
#
|
||||
# This file is part of MPlayer.
|
||||
#
|
||||
# MPlayer is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# MPlayer is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
include old_build/config.mak
|
||||
|
||||
###### variable declarations #######
|
||||
|
||||
SOURCES_AUDIO_INPUT-$(ALSA) += stream/ai_alsa1x.c
|
||||
SOURCES_AUDIO_INPUT-$(OSS_AUDIO)+= stream/ai_oss.c
|
||||
SOURCES_AUDIO_INPUT-$(SNDIO) += stream/ai_sndio.c
|
||||
SOURCES-$(AUDIO_INPUT) += $(SOURCES_AUDIO_INPUT-yes)
|
||||
SOURCES-$(CDDA) += stream/stream_cdda.c
|
||||
SOURCES-$(DVBIN) += stream/dvb_tune.c \
|
||||
stream/stream_dvb.c
|
||||
SOURCES-$(DVDREAD) += stream/stream_dvd.c \
|
||||
stream/stream_dvd_common.c
|
||||
SOURCES-$(DVDNAV) += stream/stream_dvdnav.c \
|
||||
stream/stream_dvd_common.c
|
||||
|
||||
SOURCES-$(RUBBERBAND) += audio/filter/af_rubberband.c
|
||||
SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c
|
||||
|
||||
SOURCES-$(LIBBLURAY) += stream/stream_bluray.c
|
||||
|
||||
SOURCES-$(LIBSMBCLIENT) += stream/stream_smb.c
|
||||
|
||||
SOURCES-$(TV) += stream/stream_tv.c stream/tv.c \
|
||||
stream/frequencies.c stream/tvi_dummy.c \
|
||||
demux/demux_tv.c
|
||||
|
||||
SOURCES-$(TV_V4L2) += stream/tvi_v4l2.c stream/audio_in.c
|
||||
SOURCES-$(DUMMY_OSD) += sub/osd_dummy.c
|
||||
SOURCES-$(LIBASS_OSD) += sub/osd_libass.c
|
||||
|
||||
SOURCES-$(ALSA) += audio/out/ao_alsa.c
|
||||
SOURCES-$(CACA) += video/out/vo_caca.c
|
||||
SOURCES-$(SDL2) += audio/out/ao_sdl.c video/out/vo_sdl.c
|
||||
SOURCES-$(GL) += video/out/opengl/common.c \
|
||||
video/out/opengl/osd.c \
|
||||
video/out/opengl/lcms.c \
|
||||
video/out/opengl/video.c \
|
||||
video/out/opengl/video_shaders.c \
|
||||
video/out/dither.c \
|
||||
video/out/opengl/hwdec.c \
|
||||
video/out/opengl/utils.c \
|
||||
video/out/opengl/superxbr.c \
|
||||
video/out/opengl/nnedi3.c \
|
||||
video/out/vo_opengl.c \
|
||||
video/out/vo_opengl_cb.c
|
||||
|
||||
SOURCES-$(ENCODING) += video/out/vo_lavc.c audio/out/ao_lavc.c \
|
||||
common/encode_lavc.c
|
||||
|
||||
SOURCES-$(GL_X11) += video/out/x11_common.c video/out/opengl/x11.c
|
||||
SOURCES-$(EGL_X11) += video/out/x11_common.c video/out/opengl/x11egl.c
|
||||
SOURCES-$(GL_WAYLAND) += video/out/wayland_common.c \
|
||||
video/out/opengl/wayland.c
|
||||
|
||||
SOURCES-$(JACK) += audio/out/ao_jack.c
|
||||
SOURCES-$(OPENAL) += audio/out/ao_openal.c
|
||||
SOURCES-$(OSS_AUDIO) += audio/out/ao_oss.c
|
||||
SOURCES-$(PULSE) += audio/out/ao_pulse.c
|
||||
SOURCES-$(RSOUND) += audio/out/ao_rsound.c
|
||||
SOURCES-$(SNDIO) += audio/out/ao_sndio.c
|
||||
SOURCES-$(VDPAU) += video/vdpau.c video/vdpau_mixer.c \
|
||||
video/out/vo_vdpau.c video/decode/vdpau.c \
|
||||
video/filter/vf_vdpaupp.c \
|
||||
video/filter/vf_vdpaurb.c
|
||||
SOURCES-$(VDPAU_GL_X11) += video/out/opengl/hwdec_vdpau.c
|
||||
SOURCES-$(VAAPI) += video/out/vo_vaapi.c \
|
||||
video/decode/vaapi.c \
|
||||
video/filter/vf_vavpp.c \
|
||||
video/vaapi.c
|
||||
SOURCES-$(VAAPI_GLX) += video/out/opengl/hwdec_vaglx.c
|
||||
SOURCES-$(VAAPI_X_EGL) += video/out/opengl/hwdec_vaegl.c
|
||||
|
||||
SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c
|
||||
SOURCES-$(XV) += video/out/vo_xv.c
|
||||
SOURCES-$(WAYLAND) += video/out/vo_wayland.c \
|
||||
video/out/wayland_common.c \
|
||||
video/out/wayland/buffer.c \
|
||||
video/out/wayland/memfile.c
|
||||
|
||||
SOURCES-$(LIBAVFILTER) += video/filter/vf_lavfi.c \
|
||||
video/filter/vf_gradfun.c \
|
||||
video/filter/vf_pullup.c \
|
||||
video/filter/vf_rotate.c \
|
||||
video/filter/vf_yadif.c \
|
||||
audio/filter/af_lavfi.c
|
||||
|
||||
SOURCES-$(LUA) += player/lua.c
|
||||
SOURCES-$(VAPOURSYNTH_CORE) += video/filter/vf_vapoursynth.c
|
||||
SOURCES-$(LIBARCHIVE) += demux/demux_libarchive.c \
|
||||
stream/stream_libarchive.c
|
||||
SOURCES-$(DLOPEN) += video/filter/vf_dlopen.c
|
||||
|
||||
SOURCES = audio/audio.c \
|
||||
audio/audio_buffer.c \
|
||||
audio/chmap.c \
|
||||
audio/chmap_sel.c \
|
||||
audio/fmt-conversion.c \
|
||||
audio/format.c \
|
||||
audio/mixer.c \
|
||||
audio/decode/ad_lavc.c \
|
||||
audio/decode/ad_spdif.c \
|
||||
audio/decode/dec_audio.c \
|
||||
audio/filter/af.c \
|
||||
audio/filter/af_channels.c \
|
||||
audio/filter/af_delay.c \
|
||||
audio/filter/af_equalizer.c \
|
||||
audio/filter/af_format.c \
|
||||
audio/filter/af_lavcac3enc.c \
|
||||
audio/filter/af_lavrresample.c \
|
||||
audio/filter/af_pan.c \
|
||||
audio/filter/af_scaletempo.c \
|
||||
audio/filter/af_drc.c \
|
||||
audio/filter/af_volume.c \
|
||||
audio/filter/tools.c \
|
||||
audio/out/ao.c \
|
||||
audio/out/ao_null.c \
|
||||
audio/out/ao_pcm.c \
|
||||
audio/out/pull.c \
|
||||
audio/out/push.c \
|
||||
common/av_common.c \
|
||||
common/av_log.c \
|
||||
common/codecs.c \
|
||||
common/common.c \
|
||||
common/msg.c \
|
||||
common/playlist.c \
|
||||
common/tags.c \
|
||||
common/version.c \
|
||||
demux/codec_tags.c \
|
||||
demux/cue.c \
|
||||
demux/demux.c \
|
||||
demux/demux_edl.c \
|
||||
demux/demux_cue.c \
|
||||
demux/demux_disc.c \
|
||||
demux/demux_lavf.c \
|
||||
demux/demux_mf.c \
|
||||
demux/demux_mkv.c \
|
||||
demux/demux_mkv_timeline.c \
|
||||
demux/demux_playlist.c \
|
||||
demux/demux_rar.c \
|
||||
demux/demux_raw.c \
|
||||
demux/ebml.c \
|
||||
demux/packet.c \
|
||||
demux/timeline.c \
|
||||
input/cmd_list.c \
|
||||
input/cmd_parse.c \
|
||||
input/event.c \
|
||||
input/input.c \
|
||||
input/ipc.c \
|
||||
input/keycodes.c \
|
||||
misc/bstr.c \
|
||||
misc/charset_conv.c \
|
||||
misc/dispatch.c \
|
||||
misc/json.c \
|
||||
misc/rendezvous.c \
|
||||
misc/ring.c \
|
||||
options/m_config.c \
|
||||
options/m_option.c \
|
||||
options/m_property.c \
|
||||
options/options.c \
|
||||
options/parse_commandline.c \
|
||||
options/parse_configfile.c \
|
||||
options/path.c \
|
||||
osdep/io.c \
|
||||
osdep/path-unix.c \
|
||||
osdep/semaphore_osx.c \
|
||||
osdep/subprocess.c \
|
||||
osdep/subprocess-posix.c \
|
||||
osdep/terminal-unix.c \
|
||||
osdep/timer.c \
|
||||
osdep/timer-linux.c \
|
||||
osdep/threads.c \
|
||||
player/audio.c \
|
||||
player/client.c \
|
||||
player/configfiles.c \
|
||||
player/command.c \
|
||||
player/external_files.c \
|
||||
player/loadfile.c \
|
||||
player/main.c \
|
||||
player/misc.c \
|
||||
player/osd.c \
|
||||
player/playloop.c \
|
||||
player/screenshot.c \
|
||||
player/scripting.c \
|
||||
player/sub.c \
|
||||
player/video.c \
|
||||
stream/cache.c \
|
||||
stream/cache_file.c \
|
||||
stream/cookies.c \
|
||||
stream/rar.c \
|
||||
stream/stream.c \
|
||||
stream/stream_avdevice.c \
|
||||
stream/stream_edl.c \
|
||||
stream/stream_file.c \
|
||||
stream/stream_lavf.c \
|
||||
stream/stream_memory.c \
|
||||
stream/stream_mf.c \
|
||||
stream/stream_null.c \
|
||||
stream/stream_rar.c \
|
||||
sub/dec_sub.c \
|
||||
sub/draw_bmp.c \
|
||||
sub/img_convert.c \
|
||||
sub/osd.c \
|
||||
sub/sd_lavc.c \
|
||||
sub/sd_lavc_conv.c \
|
||||
sub/sd_lavf_srt.c \
|
||||
sub/sd_microdvd.c \
|
||||
sub/sd_movtext.c \
|
||||
sub/sd_srt.c \
|
||||
ta/ta.c \
|
||||
ta/ta_utils.c \
|
||||
ta/ta_talloc.c \
|
||||
video/csputils.c \
|
||||
video/fmt-conversion.c \
|
||||
video/gpu_memcpy.c \
|
||||
video/image_writer.c \
|
||||
video/img_format.c \
|
||||
video/mp_image.c \
|
||||
video/mp_image_pool.c \
|
||||
video/sws_utils.c \
|
||||
video/decode/dec_video.c \
|
||||
video/decode/vd_lavc.c \
|
||||
video/filter/vf.c \
|
||||
video/filter/vf_buffer.c \
|
||||
video/filter/vf_crop.c \
|
||||
video/filter/vf_dsize.c \
|
||||
video/filter/vf_eq.c \
|
||||
video/filter/vf_expand.c \
|
||||
video/filter/vf_flip.c \
|
||||
video/filter/vf_format.c \
|
||||
video/filter/vf_mirror.c \
|
||||
video/filter/vf_noformat.c \
|
||||
video/filter/vf_scale.c \
|
||||
video/filter/vf_stereo3d.c \
|
||||
video/filter/vf_sub.c \
|
||||
video/out/bitmap_packer.c \
|
||||
video/out/aspect.c \
|
||||
video/out/filter_kernels.c \
|
||||
video/out/vo.c \
|
||||
video/out/vo_null.c \
|
||||
video/out/vo_image.c \
|
||||
video/out/win_state.c \
|
||||
$(SOURCES-yes)
|
||||
|
||||
OBJECTS += $(addsuffix .o, $(basename $(SOURCES)))
|
||||
OBJECTS += $(OBJECTS-yes)
|
||||
|
||||
DEP_FILES = $(patsubst %.S,%.d,$(patsubst %.cpp,%.d,$(patsubst %.c,%.d,$(SOURCES:.m=.d) $(SOURCES:.m=.d))))
|
||||
|
||||
ALL_TARGETS += mpv
|
||||
|
||||
INSTALL_BIN += install-mpv
|
||||
INSTALL_BIN_STRIP += install-mpv-strip
|
||||
INSTALL_MAN =
|
||||
|
||||
ifeq ($(BUILD_MAN),yes)
|
||||
INSTALL_MAN += install-mpv-man
|
||||
ALL_TARGETS += DOCS/man/mpv.1
|
||||
endif
|
||||
|
||||
DIRS = . \
|
||||
audio \
|
||||
audio/decode \
|
||||
audio/filter \
|
||||
audio/out \
|
||||
common \
|
||||
compat \
|
||||
input \
|
||||
player/timeline \
|
||||
demux \
|
||||
misc \
|
||||
options \
|
||||
osdep \
|
||||
osdep/ar \
|
||||
player \
|
||||
stream \
|
||||
sub \
|
||||
ta \
|
||||
video \
|
||||
video/decode \
|
||||
video/filter \
|
||||
video/out
|
||||
|
||||
|
||||
ADDSUFFIXES = $(foreach suf,$(1),$(addsuffix $(suf),$(2)))
|
||||
ADD_ALL_DIRS = $(call ADDSUFFIXES,$(1),$(DIRS))
|
||||
|
||||
###### brief build output #######
|
||||
|
||||
ifndef V
|
||||
$(eval override CC = @printf "CC\t$$@\n"; $(CC))
|
||||
$(eval override RM = @$(RM))
|
||||
endif
|
||||
|
||||
###### generic rules #######
|
||||
|
||||
all: $(ALL_TARGETS)
|
||||
|
||||
%.1: %.rst
|
||||
$(RST2MAN) $< $@
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
mpv: $(OBJECTS) osdep/main-fn-unix.o
|
||||
$(CC) -o $@ $^ $(EXTRALIBS)
|
||||
|
||||
input/input.c: input/input_conf.h
|
||||
input/input_conf.h: TOOLS/file2string.pl etc/input.conf
|
||||
./$^ >$@
|
||||
|
||||
MKVLIB_DEPS = TOOLS/lib/Parse/Matroska.pm \
|
||||
TOOLS/lib/Parse/Matroska/Definitions.pm \
|
||||
TOOLS/lib/Parse/Matroska/Element.pm \
|
||||
TOOLS/lib/Parse/Matroska/Reader.pm \
|
||||
TOOLS/lib/Parse/Matroska/Utils.pm \
|
||||
|
||||
demux/ebml.c demux/demux_mkv.c: demux/ebml_types.h
|
||||
demux/ebml_types.h: TOOLS/matroska.pl $(MKVLIB_DEPS)
|
||||
./$< --generate-header > $@
|
||||
|
||||
demux/ebml.c: demux/ebml_defs.c
|
||||
demux/ebml_defs.c: TOOLS/matroska.pl $(MKVLIB_DEPS)
|
||||
./$< --generate-definitions > $@
|
||||
|
||||
video/out/x11_common.c: video/out/x11_icon.inc
|
||||
video/out/x11_icon.inc: TOOLS/file2string.pl video/out/x11_icon.bin
|
||||
./$^ >$@
|
||||
|
||||
video/out/opengl/nnedi3.c: video/out/opengl/nnedi3_weights.inc
|
||||
video/out/opengl/nnedi3_weights.inc: TOOLS/file2string.pl video/out/opengl/nnedi3_weights.bin
|
||||
./$^ >$@
|
||||
|
||||
sub/osd_libass.c: sub/osd_font.h
|
||||
sub/osd_font.h: TOOLS/file2string.pl sub/osd_font.otf
|
||||
./$^ >$@
|
||||
|
||||
player/lua/%.inc: TOOLS/file2string.pl player/lua/%.lua
|
||||
./$^ >$@
|
||||
|
||||
player/lua.c: player/lua/defaults.inc \
|
||||
player/lua/assdraw.inc \
|
||||
player/lua/osc.inc \
|
||||
player/lua/ytdl_hook.inc \
|
||||
player/lua/options.inc
|
||||
|
||||
etc/_mpv: TOOLS/zsh.pl ./mpv
|
||||
./$< > $@
|
||||
|
||||
# ./configure must be rerun if it changed
|
||||
config.mak: configure
|
||||
@echo "############################################################"
|
||||
@echo "####### Please run ./configure again - it's changed! #######"
|
||||
@echo "############################################################"
|
||||
|
||||
old_build/version.h .version: version.sh
|
||||
./version.sh --versionh=old_build/version.h
|
||||
|
||||
# Force version.sh to run to potentially regenerate version.h
|
||||
-include .version
|
||||
|
||||
%: %.c
|
||||
$(CC) $(CFLAGS) -o $@ $^
|
||||
|
||||
|
||||
###### dependency declarations / specific CFLAGS ######
|
||||
|
||||
common/version.c: old_build/version.h
|
||||
|
||||
DOCS/man/mpv.1: DOCS/man/af.rst \
|
||||
DOCS/man/ao.rst \
|
||||
DOCS/man/changes.rst \
|
||||
DOCS/man/encode.rst \
|
||||
DOCS/man/input.rst \
|
||||
DOCS/man/options.rst \
|
||||
DOCS/man/vf.rst \
|
||||
DOCS/man/vo.rst
|
||||
|
||||
###### installation / clean / generic rules #######
|
||||
|
||||
install: $(INSTALL_BIN) install-data $(INSTALL_MAN)
|
||||
install-no-man: $(INSTALL_BIN) install-data
|
||||
install-strip: $(INSTALL_BIN_STRIP) install-data $(INSTALL_MAN)
|
||||
install-strip-no-man: $(INSTALL_BIN_STRIP) install-data
|
||||
|
||||
install-dirs:
|
||||
if test ! -d $(BINDIR) ; then $(INSTALL) -d $(BINDIR) ; fi
|
||||
|
||||
install-%: % install-dirs
|
||||
$(INSTALL) -m 755 $< $(BINDIR)
|
||||
|
||||
install-%-strip: % install-dirs
|
||||
$(INSTALL) -m 755 -s $< $(BINDIR)
|
||||
|
||||
install-mpv-man: install-mpv-man-en
|
||||
|
||||
install-mpv-man-en: DOCS/man/mpv.1
|
||||
if test ! -d $(MANDIR)/man1 ; then $(INSTALL) -d $(MANDIR)/man1 ; fi
|
||||
$(INSTALL) -m 644 DOCS/man/mpv.1 $(MANDIR)/man1/
|
||||
|
||||
ICONSIZES = 16x16 32x32 64x64
|
||||
|
||||
define ICON_INSTALL_RULE
|
||||
install-mpv-icon-$(size): etc/mpv-icon-8bit-$(size).png
|
||||
$(INSTALL) -d $(prefix)/share/icons/hicolor/$(size)/apps
|
||||
$(INSTALL) -m 644 etc/mpv-icon-8bit-$(size).png $(prefix)/share/icons/hicolor/$(size)/apps/mpv.png
|
||||
endef
|
||||
|
||||
$(foreach size,$(ICONSIZES),$(eval $(ICON_INSTALL_RULE)))
|
||||
|
||||
install-mpv-icons: $(foreach size,$(ICONSIZES),install-mpv-icon-$(size))
|
||||
|
||||
install-mpv-desktop: etc/mpv.desktop
|
||||
$(INSTALL) -d $(prefix)/share/applications
|
||||
$(INSTALL) -m 644 etc/mpv.desktop $(prefix)/share/applications/
|
||||
|
||||
install-mpv-config: etc/encoding-profiles.conf
|
||||
$(INSTALL) -d $(CONFDIR)
|
||||
$(INSTALL) -m 644 etc/encoding-profiles.conf $(CONFDIR)
|
||||
|
||||
install-mpv-zsh: etc/_mpv
|
||||
$(INSTALL) -d $(prefix)/share/zsh/vendor-completions
|
||||
$(INSTALL) -m 644 etc/_mpv $(prefix)/share/zsh/vendor-completions/
|
||||
|
||||
install-data: install-mpv-icons install-mpv-desktop install-mpv-config install-mpv-zsh
|
||||
|
||||
uninstall:
|
||||
$(RM) $(BINDIR)/mpv
|
||||
$(RM) $(MANDIR)/man1/mpv.1 $(MANDIR)/man1/mpv.1
|
||||
$(RM) $(prefix)/share/applications/mpv.desktop
|
||||
$(RM) $(prefix)/share/zsh/vendor-completions/_mpv
|
||||
$(RM) $(foreach size,$(ICONSIZES),$(prefix)/share/icons/hicolor/$(size)/apps/mpv.png)
|
||||
|
||||
clean:
|
||||
-$(RM) $(call ADD_ALL_DIRS,/*.o /*.d /*.a /*.ho /*~)
|
||||
-$(RM) $(call ADD_ALL_DIRS,/*.o /*.a /*.ho /*~)
|
||||
-$(RM) mpv
|
||||
-$(RM) DOCS/man/*/mpv.1
|
||||
-$(RM) old_build/version.h version.h
|
||||
-$(RM) input/input_conf.h
|
||||
-$(RM) video/out/vdpau_template.c
|
||||
-$(RM) demux/ebml_types.h demux/ebml_defs.c
|
||||
-$(RM) video/out/x11_icon.inc
|
||||
-$(RM) sub/osd_font.h
|
||||
-$(RM) player/lua/defaults.inc
|
||||
-$(RM) player/lua/assdraw.inc
|
||||
-$(RM) player/lua/osc.inc
|
||||
-$(RM) player/lua/ytdl_hook.inc
|
||||
-$(RM) player/lua/options.inc
|
||||
|
||||
distclean: clean
|
||||
-$(RM) config.log old_build/config.h old_build/config.mak Makefile
|
||||
-rmdir old_build/
|
||||
|
||||
-include $(DEP_FILES)
|
||||
|
||||
.PHONY: all *install* *clean .version
|
||||
|
||||
# Disable suffix rules. Most of the builtin rules are suffix rules,
|
||||
# so this saves some time on slow systems.
|
||||
.SUFFIXES:
|
||||
|
||||
# If a command returns failure but changed its target file, delete the
|
||||
# (presumably malformed) file. Otherwise the file would be considered to
|
||||
# be up to date if make is restarted.
|
||||
|
||||
.DELETE_ON_ERROR:
|
||||
+10
-1
@@ -39,8 +39,17 @@ def apply_plist_template(plist_file, version):
|
||||
for line in fileinput.input(plist_file, inplace=1):
|
||||
print (line.rstrip().replace('${VERSION}', version))
|
||||
|
||||
def bundle_version():
|
||||
if os.path.exists('VERSION'):
|
||||
x = open('VERSION')
|
||||
version = x.read()
|
||||
x.close()
|
||||
else:
|
||||
version = sh("./version.sh").strip()
|
||||
return version
|
||||
|
||||
def main():
|
||||
version = sh("./version.sh").strip()
|
||||
version = bundle_version().rstrip()
|
||||
|
||||
usage = "usage: %prog [options] arg"
|
||||
parser = OptionParser(usage)
|
||||
|
||||
+9
-3
@@ -40,7 +40,7 @@ class G:
|
||||
events = {}
|
||||
start = None
|
||||
markers = ["o", "s", "t", "d"]
|
||||
curveno = 0
|
||||
curveno = {}
|
||||
|
||||
def find_marker():
|
||||
if len(G.markers) == 0:
|
||||
@@ -66,6 +66,10 @@ def get_event(event, evtype):
|
||||
G.events[event] = e
|
||||
return G.events[event]
|
||||
|
||||
colors = [(0.0, 0.5, 0.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.75, 0.75, 0), (0.0, 0.75, 0.75), (0.75, 0, 0.75)]
|
||||
def mkColor(t):
|
||||
return pg.mkColor(int(t[0] * 255), int(t[1] * 255), int(t[2] * 255))
|
||||
|
||||
SCALE = 1e6 # microseconds to seconds
|
||||
|
||||
for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
|
||||
@@ -152,8 +156,10 @@ for e in G.sevents:
|
||||
args['symbol'] = e.marker
|
||||
args['pen'] = None
|
||||
else:
|
||||
args['pen'] = pg.mkPen(pg.intColor(G.curveno), width=0)
|
||||
G.curveno += 1
|
||||
if not cur in G.curveno:
|
||||
G.curveno[cur] = 0
|
||||
args['pen'] = pg.mkPen(mkColor(colors[G.curveno[cur] % len(colors)]), width=0)
|
||||
G.curveno[cur] += 1
|
||||
n = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], **args)
|
||||
|
||||
QtGui.QApplication.instance().exec_()
|
||||
+8
-2
@@ -69,11 +69,17 @@ chomp $profile_comp;
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
|
||||
# mpv zsh completion
|
||||
# For customization, see:
|
||||
# https://github.com/mpv-player/mpv/wiki/Zsh-completion-customization
|
||||
|
||||
local curcontext="\$curcontext" state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
# By default, don't complete URLs unless no files match
|
||||
local -a tag_order
|
||||
zstyle -a ":completion:*:*:\$service:*" tag-order tag_order || \
|
||||
zstyle ":completion:*:*:\$service:*" tag-order '!urls'
|
||||
|
||||
local rc=1
|
||||
|
||||
_arguments -C -S \\
|
||||
@@ -120,7 +126,7 @@ $profile_comp
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|qt|rm|ts|vob|wav|webm|wma|wmv)(-.)" && rc=0
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv|wv)(-.)" && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
|
||||
+109
-1
@@ -22,9 +22,10 @@
|
||||
|
||||
#include <libavutil/buffer.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/version.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/common.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "audio.h"
|
||||
@@ -251,8 +252,43 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
data->planes[n] = (uint8_t *)data->planes[n] + samples * data->sstride;
|
||||
|
||||
data->samples -= samples;
|
||||
|
||||
if (data->pts != MP_NOPTS_VALUE)
|
||||
data->pts += samples / (double)data->rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
return;
|
||||
double f_end = f->pts + f->samples / (double)f->rate;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
f->samples = 0;
|
||||
} else {
|
||||
int new = (end - f->pts) * f->rate;
|
||||
f->samples = MPCLAMP(new, 0, f->samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start != MP_NOPTS_VALUE) {
|
||||
if (f->pts < start) {
|
||||
if (f_end <= start) {
|
||||
f->samples = 0;
|
||||
f->pts = f_end;
|
||||
} else {
|
||||
int skip = (start - f->pts) * f->rate;
|
||||
skip = MPCLAMP(skip, 0, f->samples);
|
||||
mp_audio_skip_samples(f, skip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return false if the frame data is shared, true otherwise.
|
||||
// Will return true for non-refcounted frames.
|
||||
bool mp_audio_is_writeable(struct mp_audio *data)
|
||||
@@ -350,6 +386,78 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Returns NULL on failure. The input is always unreffed.
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
{
|
||||
struct AVFrame *avframe = av_frame_alloc();
|
||||
if (!avframe)
|
||||
goto fail;
|
||||
|
||||
avframe->nb_samples = frame->samples;
|
||||
avframe->format = af_to_avformat(frame->format);
|
||||
if (avframe->format == AV_SAMPLE_FMT_NONE)
|
||||
goto fail;
|
||||
|
||||
avframe->channel_layout = mp_chmap_to_lavc(&frame->channels);
|
||||
if (!avframe->channel_layout)
|
||||
goto fail;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS (but I respect it)
|
||||
avframe->channels = frame->channels.num;
|
||||
#endif
|
||||
avframe->sample_rate = frame->rate;
|
||||
|
||||
if (frame->num_planes > AV_NUM_DATA_POINTERS) {
|
||||
avframe->extended_data =
|
||||
av_mallocz_array(frame->num_planes, sizeof(avframe->extended_data[0]));
|
||||
int extbufs = frame->num_planes - AV_NUM_DATA_POINTERS;
|
||||
avframe->extended_buf =
|
||||
av_mallocz_array(extbufs, sizeof(avframe->extended_buf[0]));
|
||||
if (!avframe->extended_data || !avframe->extended_buf)
|
||||
goto fail;
|
||||
avframe->nb_extended_buf = extbufs;
|
||||
}
|
||||
|
||||
for (int p = 0; p < frame->num_planes; p++)
|
||||
avframe->extended_data[p] = frame->planes[p];
|
||||
avframe->linesize[0] = frame->samples * frame->sstride;
|
||||
|
||||
for (int p = 0; p < AV_NUM_DATA_POINTERS; p++)
|
||||
avframe->data[p] = avframe->extended_data[p];
|
||||
|
||||
for (int p = 0; p < frame->num_planes; p++) {
|
||||
if (!frame->allocated[p])
|
||||
break;
|
||||
AVBufferRef *nref = av_buffer_ref(frame->allocated[p]);
|
||||
if (!nref)
|
||||
goto fail;
|
||||
if (p < AV_NUM_DATA_POINTERS) {
|
||||
avframe->buf[p] = nref;
|
||||
} else {
|
||||
avframe->extended_buf[p - AV_NUM_DATA_POINTERS] = nref;
|
||||
}
|
||||
}
|
||||
|
||||
// Force refcounted frame.
|
||||
if (!avframe->buf[0]) {
|
||||
AVFrame *tmp = av_frame_alloc();
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
if (av_frame_ref(tmp, avframe) < 0)
|
||||
goto fail;
|
||||
av_frame_free(&avframe);
|
||||
avframe = tmp;
|
||||
}
|
||||
|
||||
talloc_free(frame);
|
||||
return avframe;
|
||||
|
||||
fail:
|
||||
av_frame_free(&avframe);
|
||||
talloc_free(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_audio_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
|
||||
@@ -72,12 +72,14 @@ void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length);
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src);
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples);
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end);
|
||||
|
||||
bool mp_audio_is_writeable(struct mp_audio *data);
|
||||
int mp_audio_make_writeable(struct mp_audio *data);
|
||||
|
||||
struct AVFrame;
|
||||
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame);
|
||||
|
||||
struct mp_audio_pool;
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <stddef.h>
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_AUDIO_BUFFER_H
|
||||
|
||||
+7
-7
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <stdlib.h>
|
||||
|
||||
+7
-7
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_CHMAP_H
|
||||
|
||||
+22
-16
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <stdlib.h>
|
||||
@@ -274,19 +274,25 @@ static bool mp_chmap_is_better(struct mp_chmap *req, struct mp_chmap *old,
|
||||
if (new_lost_r != old_lost_r)
|
||||
return new_lost_r < old_lost_r;
|
||||
|
||||
int old_lost = mp_chmap_diffn(req, old);
|
||||
int new_lost = mp_chmap_diffn(req, new);
|
||||
|
||||
// If the situation is equal with replaced speakers, but one of them loses
|
||||
// less if no replacements are performed, pick the better one, even if it
|
||||
// means an upmix. This prefers exact supersets over inexact equivalents.
|
||||
if (new_lost != old_lost)
|
||||
return new_lost < old_lost;
|
||||
|
||||
struct mp_chmap old_p = *old, new_p = *new;
|
||||
mp_chmap_remove_na(&old_p);
|
||||
mp_chmap_remove_na(&new_p);
|
||||
|
||||
// If the situation is equal with replaced speakers, but the replacement is
|
||||
// perfect for only one of them, let the better one win. This prefers
|
||||
// inexact equivalents over exact supersets.
|
||||
bool perfect_r_new = !new_lost_r && new_p.num <= old_p.num;
|
||||
bool perfect_r_old = !old_lost_r && old_p.num <= new_p.num;
|
||||
if (perfect_r_new != perfect_r_old)
|
||||
return perfect_r_new;
|
||||
|
||||
int old_lost = mp_chmap_diffn(req, old);
|
||||
int new_lost = mp_chmap_diffn(req, new);
|
||||
// If the situation is equal with replaced speakers, pick the better one,
|
||||
// even if it means an upmix.
|
||||
if (new_lost != old_lost)
|
||||
return new_lost < old_lost;
|
||||
|
||||
// Some kind of upmix. If it's perfect, prefer the smaller one. Even if not,
|
||||
// both have equal loss, so also prefer the smaller one.
|
||||
// Drop padding channels (NA) for the sake of this check, as the number of
|
||||
|
||||
+7
-7
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_CHMAP_SEL_H
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ struct ad_functions {
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
int (*decode_packet)(struct dec_audio *da, struct mp_audio **out);
|
||||
int (*decode_packet)(struct dec_audio *da, struct demux_packet *pkt,
|
||||
struct mp_audio **out);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
|
||||
+76
-53
@@ -26,7 +26,7 @@
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "common/av_common.h"
|
||||
@@ -42,8 +42,10 @@ struct priv {
|
||||
AVFrame *avframe;
|
||||
struct mp_audio frame;
|
||||
bool force_channel_map;
|
||||
struct demux_packet *packet;
|
||||
uint32_t skip_samples;
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
AVRational codec_timebase;
|
||||
};
|
||||
|
||||
static void uninit(struct dec_audio *da);
|
||||
@@ -60,7 +62,7 @@ const struct m_sub_options ad_lavc_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_FLOATRANGE("ac3drc", ac3drc, 0, 0, 6),
|
||||
OPT_FLAG("downmix", downmix, 0),
|
||||
OPT_INTRANGE("threads", threads, 0, 1, 16),
|
||||
OPT_INTRANGE("threads", threads, 0, 0, 16),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
@@ -78,13 +80,14 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
struct ad_lavc_params *opts = mpopts->ad_lavc_params;
|
||||
AVCodecContext *lavc_context;
|
||||
AVCodec *lavc_codec;
|
||||
struct sh_stream *sh = da->header;
|
||||
struct sh_audio *sh_audio = sh->audio;
|
||||
struct mp_codec_params *c = da->codec;
|
||||
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
ctx->force_channel_map = sh_audio->force_channels;
|
||||
ctx->codec_timebase = (AVRational){0};
|
||||
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
if (!lavc_codec) {
|
||||
@@ -116,20 +119,19 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
lavc_context->codec_tag = sh->codec_tag;
|
||||
lavc_context->sample_rate = sh_audio->samplerate;
|
||||
lavc_context->bit_rate = sh_audio->bitrate;
|
||||
lavc_context->block_align = sh_audio->block_align;
|
||||
lavc_context->bits_per_coded_sample = sh_audio->bits_per_coded_sample;
|
||||
lavc_context->channels = sh_audio->channels.num;
|
||||
if (!mp_chmap_is_unknown(&sh_audio->channels))
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&sh_audio->channels);
|
||||
lavc_context->codec_tag = c->codec_tag;
|
||||
lavc_context->sample_rate = c->samplerate;
|
||||
lavc_context->bit_rate = c->bitrate;
|
||||
lavc_context->block_align = c->block_align;
|
||||
lavc_context->bits_per_coded_sample = c->bits_per_coded_sample;
|
||||
lavc_context->channels = c->channels.num;
|
||||
if (!mp_chmap_is_unknown(&c->channels))
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&c->channels);
|
||||
|
||||
// demux_mkv
|
||||
mp_lavc_set_extradata(lavc_context, sh->extradata, sh->extradata_size);
|
||||
mp_lavc_set_extradata(lavc_context, c->extradata, c->extradata_size);
|
||||
|
||||
if (sh->lav_headers)
|
||||
mp_copy_lav_codec_headers(lavc_context, sh->lav_headers);
|
||||
mp_set_lav_codec_headers(lavc_context, c);
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
|
||||
@@ -140,6 +142,8 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -165,34 +169,41 @@ static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
talloc_free(ctx->packet);
|
||||
ctx->packet = NULL;
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct mp_audio **out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
struct demux_packet *mpkt = priv->packet;
|
||||
if (!mpkt) {
|
||||
if (demux_read_packet_async(da->header, &mpkt) == 0)
|
||||
return AD_WAIT;
|
||||
}
|
||||
|
||||
priv->packet = talloc_steal(priv, mpkt);
|
||||
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
mp_set_av_packet(&pkt, mpkt, &priv->codec_timebase);
|
||||
|
||||
int got_frame = 0;
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int ret = avcodec_send_packet(avctx, &pkt);
|
||||
if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
if (ret >= 0 && mpkt)
|
||||
mpkt->len = 0;
|
||||
ret = avcodec_receive_frame(avctx, priv->avframe);
|
||||
if (ret >= 0)
|
||||
got_frame = 1;
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
ret = 0;
|
||||
}
|
||||
#else
|
||||
int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt);
|
||||
if (mpkt) {
|
||||
// At least "shorten" decodes sub-frames, instead of the whole packet.
|
||||
@@ -203,58 +214,70 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
mpkt->len -= ret;
|
||||
mpkt->pts = MP_NOPTS_VALUE; // don't reset PTS next time
|
||||
}
|
||||
if (mpkt->len == 0 || ret < 0) {
|
||||
talloc_free(mpkt);
|
||||
priv->packet = NULL;
|
||||
}
|
||||
// LATM may need many packets to find mux info
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
return AD_OK;
|
||||
if (ret == AVERROR(EAGAIN)) {
|
||||
mpkt->len = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (ret < 0) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return AD_ERR;
|
||||
return -1;
|
||||
}
|
||||
if (!got_frame)
|
||||
return mpkt ? AD_OK : AD_EOF;
|
||||
return 0;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL);
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
|
||||
if (!mpframe)
|
||||
return AD_ERR;
|
||||
return -1;
|
||||
|
||||
struct mp_chmap lavc_chmap = mpframe->channels;
|
||||
if (lavc_chmap.num != avctx->channels)
|
||||
mp_chmap_from_channels(&lavc_chmap, avctx->channels);
|
||||
if (priv->force_channel_map) {
|
||||
struct sh_audio *sh_audio = da->header->audio;
|
||||
if (lavc_chmap.num == sh_audio->channels.num)
|
||||
lavc_chmap = sh_audio->channels;
|
||||
if (lavc_chmap.num == da->codec->channels.num)
|
||||
lavc_chmap = da->codec->channels;
|
||||
}
|
||||
mp_audio_set_channels(mpframe, &lavc_chmap);
|
||||
|
||||
mpframe->pts = out_pts;
|
||||
|
||||
if (mpframe->pts == MP_NOPTS_VALUE)
|
||||
mpframe->pts = priv->next_pts;
|
||||
if (mpframe->pts != MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpframe->pts + mpframe->samples / (double)mpframe->rate;
|
||||
|
||||
#if HAVE_AVFRAME_SKIP_SAMPLES
|
||||
AVFrameSideData *sd =
|
||||
av_frame_get_side_data(priv->avframe, AV_FRAME_DATA_SKIP_SAMPLES);
|
||||
if (sd && sd->size >= 10) {
|
||||
char *d = sd->data;
|
||||
priv->skip_samples += AV_RL32(d + 0);
|
||||
uint32_t pad = AV_RL32(d + 4);
|
||||
uint32_t skip = MPMIN(priv->skip_samples, mpframe->samples);
|
||||
if (skip) {
|
||||
mp_audio_skip_samples(mpframe, skip);
|
||||
if (mpframe->pts != MP_NOPTS_VALUE)
|
||||
mpframe->pts += skip / (double)mpframe->rate;
|
||||
priv->skip_samples -= skip;
|
||||
}
|
||||
if (pad <= mpframe->samples)
|
||||
mpframe->samples -= pad;
|
||||
priv->trim_samples += AV_RL32(d + 4);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!priv->preroll_done) {
|
||||
// Skip only if this isn't already handled by AV_FRAME_DATA_SKIP_SAMPLES.
|
||||
if (!priv->skip_samples)
|
||||
priv->skip_samples = avctx->delay;
|
||||
priv->preroll_done = true;
|
||||
}
|
||||
|
||||
uint32_t skip = MPMIN(priv->skip_samples, mpframe->samples);
|
||||
if (skip) {
|
||||
mp_audio_skip_samples(mpframe, skip);
|
||||
priv->skip_samples -= skip;
|
||||
}
|
||||
uint32_t trim = MPMIN(priv->trim_samples, mpframe->samples);
|
||||
if (trim) {
|
||||
mpframe->samples -= trim;
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
*out = mpframe;
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
+16
-12
@@ -41,6 +41,7 @@ struct spdifContext {
|
||||
bool need_close;
|
||||
bool use_dts_hd;
|
||||
struct mp_audio fmt;
|
||||
struct mp_audio_pool *pool;
|
||||
};
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
@@ -79,6 +80,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
da->priv = spdif_ctx;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->use_dts_hd = da->opts->dtshd;
|
||||
spdif_ctx->pool = mp_audio_pool_create(spdif_ctx);
|
||||
|
||||
if (strcmp(decoder, "dts-hd") == 0) {
|
||||
decoder = "dts";
|
||||
@@ -169,7 +171,11 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
if (!stream)
|
||||
goto fail;
|
||||
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
stream->codecpar->codec_id = spdif_ctx->codec_id;
|
||||
#else
|
||||
stream->codec->codec_id = spdif_ctx->codec_id;
|
||||
#endif
|
||||
|
||||
AVDictionary *format_opts = NULL;
|
||||
|
||||
@@ -189,7 +195,8 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
break;
|
||||
case AV_CODEC_ID_DTS: {
|
||||
bool is_hd = profile == FF_PROFILE_DTS_HD_HRA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA;
|
||||
profile == FF_PROFILE_DTS_HD_MA ||
|
||||
profile == FF_PROFILE_UNKNOWN;
|
||||
if (spdif_ctx->use_dts_hd && is_hd) {
|
||||
av_dict_set(&format_opts, "dtshd_rate", "768000", 0); // 4*192000
|
||||
sample_format = AF_FORMAT_S_DTSHD;
|
||||
@@ -240,38 +247,35 @@ fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct mp_audio **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
|
||||
struct demux_packet *mpkt;
|
||||
if (demux_read_packet_async(da->header, &mpkt) == 0)
|
||||
return AD_WAIT;
|
||||
|
||||
if (!mpkt)
|
||||
return AD_EOF;
|
||||
return 0;
|
||||
|
||||
double pts = mpkt->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
mpkt->len = 0; // will be fully consumed
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
return AD_ERR;
|
||||
return -1;
|
||||
}
|
||||
int ret = av_write_frame(spdif_ctx->lavf_ctx, &pkt);
|
||||
talloc_free(mpkt);
|
||||
avio_flush(spdif_ctx->lavf_ctx->pb);
|
||||
if (ret < 0)
|
||||
return AD_ERR;
|
||||
return -1;
|
||||
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->fmt.sstride;
|
||||
*out = mp_audio_pool_get(da->pool, &spdif_ctx->fmt, samples);
|
||||
*out = mp_audio_pool_get(spdif_ctx->pool, &spdif_ctx->fmt, samples);
|
||||
if (!*out)
|
||||
return AD_ERR;
|
||||
return -1;
|
||||
|
||||
memcpy((*out)->planes[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
(*out)->pts = pts;
|
||||
|
||||
+121
-116
@@ -61,8 +61,6 @@ static void uninit_decoder(struct dec_audio *d_audio)
|
||||
d_audio->ad_driver = NULL;
|
||||
talloc_free(d_audio->priv);
|
||||
d_audio->priv = NULL;
|
||||
d_audio->afilter->initialized = -1;
|
||||
d_audio->decode_format = (struct mp_audio){0};
|
||||
}
|
||||
|
||||
static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
|
||||
@@ -88,12 +86,12 @@ struct mp_decoder_list *audio_decoder_list(void)
|
||||
static struct mp_decoder_list *audio_select_decoders(struct dec_audio *d_audio)
|
||||
{
|
||||
struct MPOpts *opts = d_audio->opts;
|
||||
const char *codec = d_audio->header->codec;
|
||||
const char *codec = d_audio->codec->codec;
|
||||
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new =
|
||||
mp_select_decoders(list, codec, opts->audio_decoders);
|
||||
if (d_audio->spdif_passthrough) {
|
||||
if (d_audio->try_spdif) {
|
||||
struct mp_decoder_list *spdif =
|
||||
mp_select_decoder_list(list, codec, "spdif", opts->audio_spdif);
|
||||
mp_append_decoders(spdif, new);
|
||||
@@ -146,7 +144,7 @@ int audio_init_best_codec(struct dec_audio *d_audio)
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
d_audio->header->codec ? d_audio->header->codec : "<unknown>");
|
||||
d_audio->codec->codec);
|
||||
}
|
||||
|
||||
talloc_free(list);
|
||||
@@ -157,125 +155,132 @@ void audio_uninit(struct dec_audio *d_audio)
|
||||
{
|
||||
if (!d_audio)
|
||||
return;
|
||||
MP_VERBOSE(d_audio, "Uninit audio filters...\n");
|
||||
uninit_decoder(d_audio);
|
||||
af_destroy(d_audio->afilter);
|
||||
talloc_free(d_audio->waiting);
|
||||
talloc_free(d_audio);
|
||||
}
|
||||
|
||||
static int decode_new_frame(struct dec_audio *da)
|
||||
{
|
||||
while (!da->waiting) {
|
||||
int ret = da->ad_driver->decode_packet(da, &da->waiting);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (da->waiting) {
|
||||
if (da->waiting->pts != MP_NOPTS_VALUE) {
|
||||
if (da->pts != MP_NOPTS_VALUE) {
|
||||
da->pts += da->pts_offset / (double)da->waiting->rate;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (da->pts == MP_NOPTS_VALUE || da->pts_offset != 0 ||
|
||||
fabs(da->pts - da->waiting->pts) > 0.001)
|
||||
{
|
||||
da->pts = da->waiting->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
}
|
||||
da->pts_offset += da->waiting->samples;
|
||||
da->decode_format = *da->waiting;
|
||||
mp_audio_set_null_data(&da->decode_format);
|
||||
}
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
da->pts = 0;
|
||||
}
|
||||
return mp_audio_config_valid(da->waiting) ? AD_OK : AD_ERR;
|
||||
}
|
||||
|
||||
/* Decode packets until we know the audio format. Then reinit the buffer.
|
||||
* Returns AD_OK on success, negative AD_* code otherwise.
|
||||
* Also returns AD_OK if already initialized (and does nothing).
|
||||
*/
|
||||
int initial_audio_decode(struct dec_audio *da)
|
||||
{
|
||||
return decode_new_frame(da);
|
||||
}
|
||||
|
||||
static bool copy_output(struct af_stream *afs, struct mp_audio_buffer *outbuf,
|
||||
int minsamples, bool eof)
|
||||
{
|
||||
while (mp_audio_buffer_samples(outbuf) < minsamples) {
|
||||
if (af_output_frame(afs, eof) < 0)
|
||||
return true; // error, stop doing stuff
|
||||
struct mp_audio *mpa = af_read_output_frame(afs);
|
||||
if (!mpa)
|
||||
return false; // out of data
|
||||
mp_audio_buffer_append(outbuf, mpa);
|
||||
talloc_free(mpa);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Try to get at least minsamples decoded+filtered samples in outbuf
|
||||
* (total length including possible existing data).
|
||||
* Return 0 on success, or negative AD_* error code.
|
||||
* In the former case outbuf has at least minsamples buffered on return.
|
||||
* In case of EOF/error it might or might not be. */
|
||||
int audio_decode(struct dec_audio *da, struct mp_audio_buffer *outbuf,
|
||||
int minsamples)
|
||||
{
|
||||
struct af_stream *afs = da->afilter;
|
||||
if (afs->initialized < 1)
|
||||
return AD_ERR;
|
||||
|
||||
MP_STATS(da, "start audio");
|
||||
|
||||
int res;
|
||||
while (1) {
|
||||
res = 0;
|
||||
|
||||
if (copy_output(afs, outbuf, minsamples, false))
|
||||
break;
|
||||
|
||||
res = decode_new_frame(da);
|
||||
if (res < 0) {
|
||||
// drain filters first (especially for true EOF case)
|
||||
copy_output(afs, outbuf, minsamples, true);
|
||||
break;
|
||||
}
|
||||
|
||||
// On format change, make sure to drain the filter chain.
|
||||
if (!mp_audio_config_equals(&afs->input, da->waiting)) {
|
||||
copy_output(afs, outbuf, minsamples, true);
|
||||
res = AD_NEW_FMT;
|
||||
break;
|
||||
}
|
||||
|
||||
struct mp_audio *mpa = da->waiting;
|
||||
da->waiting = NULL;
|
||||
if (af_filter_frame(afs, mpa) < 0)
|
||||
return AD_ERR;
|
||||
}
|
||||
|
||||
MP_STATS(da, "end audio");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio)
|
||||
{
|
||||
if (d_audio->ad_driver)
|
||||
d_audio->ad_driver->control(d_audio, ADCTRL_RESET, NULL);
|
||||
af_seek_reset(d_audio->afilter);
|
||||
d_audio->pts = MP_NOPTS_VALUE;
|
||||
d_audio->pts_offset = 0;
|
||||
if (d_audio->waiting) {
|
||||
talloc_free(d_audio->waiting);
|
||||
d_audio->waiting = NULL;
|
||||
talloc_free(d_audio->current_frame);
|
||||
d_audio->current_frame = NULL;
|
||||
talloc_free(d_audio->packet);
|
||||
d_audio->packet = NULL;
|
||||
talloc_free(d_audio->new_segment);
|
||||
d_audio->new_segment = NULL;
|
||||
d_audio->start = d_audio->end = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
static void fix_audio_pts(struct dec_audio *da)
|
||||
{
|
||||
if (!da->current_frame)
|
||||
return;
|
||||
|
||||
if (da->current_frame->pts != MP_NOPTS_VALUE) {
|
||||
double newpts = da->current_frame->pts;
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (da->pts == MP_NOPTS_VALUE || fabs(da->pts - newpts) > 0.001)
|
||||
da->pts = da->current_frame->pts;
|
||||
}
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
da->pts = 0;
|
||||
|
||||
da->current_frame->pts = da->pts;
|
||||
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
da->pts += da->current_frame->samples / (double)da->current_frame->rate;
|
||||
}
|
||||
|
||||
void audio_work(struct dec_audio *da)
|
||||
{
|
||||
if (da->current_frame)
|
||||
return;
|
||||
|
||||
if (!da->packet && demux_read_packet_async(da->header, &da->packet) == 0) {
|
||||
da->current_state = DATA_WAIT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (da->packet && da->packet->new_segment) {
|
||||
assert(!da->new_segment);
|
||||
da->new_segment = da->packet;
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool had_packet = da->packet || da->new_segment;
|
||||
|
||||
int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
|
||||
if (ret < 0 || (da->packet && da->packet->len == 0)) {
|
||||
talloc_free(da->packet);
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
if (da->current_frame && !mp_audio_config_valid(da->current_frame)) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
|
||||
da->current_state = DATA_OK;
|
||||
if (!da->current_frame) {
|
||||
da->current_state = DATA_EOF;
|
||||
if (had_packet)
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = true;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_audio_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
if (da->current_frame->pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = da->current_frame->pts >= da->start;
|
||||
if (da->current_frame->samples == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a new segment, start it as soon as we're drained/finished.
|
||||
if (segment_end && da->new_segment) {
|
||||
struct demux_packet *new_segment = da->new_segment;
|
||||
da->new_segment = NULL;
|
||||
|
||||
// Could avoid decoder reinit; would still need flush.
|
||||
da->codec = new_segment->codec;
|
||||
if (da->ad_driver)
|
||||
da->ad_driver->uninit(da);
|
||||
da->ad_driver = NULL;
|
||||
audio_init_best_codec(da);
|
||||
|
||||
da->start = new_segment->start;
|
||||
da->end = new_segment->end;
|
||||
|
||||
new_segment->new_segment = false;
|
||||
|
||||
da->packet = new_segment;
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch an audio frame decoded with audio_work(). Returns one of:
|
||||
// DATA_OK: *out_frame is set to a new image
|
||||
// DATA_WAIT: waiting for demuxer; will receive a wakeup signal
|
||||
// DATA_EOF: end of file, no more frames to be expected
|
||||
// DATA_AGAIN: dropped frame or something similar
|
||||
int audio_get_frame(struct dec_audio *da, struct mp_audio **out_frame)
|
||||
{
|
||||
*out_frame = NULL;
|
||||
if (da->current_frame) {
|
||||
*out_frame = da->current_frame;
|
||||
da->current_frame = NULL;
|
||||
return DATA_OK;
|
||||
}
|
||||
if (da->current_state == DATA_OK)
|
||||
return DATA_AGAIN;
|
||||
return da->current_state;
|
||||
}
|
||||
+17
-23
@@ -30,39 +30,33 @@ struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
bool spdif_passthrough, spdif_failed;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct af_stream *afilter;
|
||||
struct mp_codec_params *codec;
|
||||
char *decoder_desc;
|
||||
int init_retries;
|
||||
struct mp_audio_pool *pool;
|
||||
struct mp_audio decode_format;
|
||||
struct mp_audio *waiting; // used on format-change
|
||||
// set by decoder
|
||||
int bitrate; // input bitrate, can change with VBR sources
|
||||
// last known pts value in output from decoder
|
||||
double pts;
|
||||
// number of samples output by decoder after last known pts
|
||||
int pts_offset;
|
||||
|
||||
bool try_spdif;
|
||||
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
};
|
||||
|
||||
enum {
|
||||
AD_OK = 0,
|
||||
AD_ERR = -1,
|
||||
AD_EOF = -2,
|
||||
AD_NEW_FMT = -3,
|
||||
AD_WAIT = -4,
|
||||
// Strictly internal (dec_audio.c).
|
||||
|
||||
double pts; // endpts of previous frame
|
||||
double start, end;
|
||||
struct demux_packet *packet;
|
||||
struct demux_packet *new_segment;
|
||||
struct mp_audio *current_frame;
|
||||
int current_state;
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio);
|
||||
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
|
||||
int minsamples);
|
||||
int initial_audio_decode(struct dec_audio *d_audio);
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
void audio_work(struct dec_audio *d_audio);
|
||||
int audio_get_frame(struct dec_audio *d_audio, struct mp_audio **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
#endif /* MPLAYER_DEC_AUDIO_H */
|
||||
+12
-2
@@ -59,9 +59,7 @@ static const struct af_info *const filter_list[] = {
|
||||
&af_info_rubberband,
|
||||
#endif
|
||||
&af_info_scaletempo,
|
||||
#if HAVE_LIBAVFILTER
|
||||
&af_info_lavfi,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -166,6 +164,7 @@ static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
.info = desc.p,
|
||||
.data = talloc_zero(af, struct mp_audio),
|
||||
.log = mp_log_new(af, s->log, name),
|
||||
.opts = s->opts,
|
||||
.replaygain_data = s->replaygain_data,
|
||||
.out_pool = mp_audio_pool_create(af),
|
||||
};
|
||||
@@ -695,6 +694,17 @@ int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label)
|
||||
}
|
||||
}
|
||||
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg)
|
||||
{
|
||||
char *args[2] = {cmd, arg};
|
||||
if (strcmp(label, "all") == 0) {
|
||||
af_control_all(s, AF_CONTROL_COMMAND, args);
|
||||
return 0;
|
||||
} else {
|
||||
return af_control_by_label(s, AF_CONTROL_COMMAND, args, bstr0(label));
|
||||
}
|
||||
}
|
||||
|
||||
// Used by filters to add a filtered frame to the output queue.
|
||||
// Ownership of frame is transferred from caller to the filter chain.
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame)
|
||||
|
||||
@@ -53,6 +53,7 @@ struct af_info {
|
||||
struct af_instance {
|
||||
const struct af_info *info;
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct replaygain_data *replaygain_data;
|
||||
int (*control)(struct af_instance *af, int cmd, void *arg);
|
||||
void (*uninit)(struct af_instance *af);
|
||||
@@ -123,6 +124,7 @@ enum af_control {
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
|
||||
AF_CONTROL_GET_METADATA,
|
||||
AF_CONTROL_COMMAND,
|
||||
};
|
||||
|
||||
// Argument for AF_CONTROL_SET_PAN_LEVEL
|
||||
@@ -143,6 +145,7 @@ struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg);
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg);
|
||||
int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label);
|
||||
void af_seek_reset(struct af_stream *s);
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg);
|
||||
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame);
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
|
||||
+19
-27
@@ -3,18 +3,18 @@
|
||||
*
|
||||
* Filter graph creation code taken from FFmpeg ffplay.c (LGPL 2.1 or later)
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <stdlib.h>
|
||||
@@ -50,6 +50,7 @@
|
||||
#if LIBAVFILTER_VERSION_MICRO < 100
|
||||
#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
|
||||
avfilter_graph_parse(graph, filters, inputs, outputs, log_ctx)
|
||||
#define avfilter_graph_send_command(a, b, c, d, e, f, g) -1
|
||||
#else
|
||||
#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
|
||||
avfilter_graph_parse_ptr(graph, filters, &(inputs), &(outputs), log_ctx)
|
||||
@@ -222,6 +223,14 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_COMMAND: {
|
||||
if (!p->graph)
|
||||
break;
|
||||
char **args = arg;
|
||||
return avfilter_graph_send_command(p->graph, "all",
|
||||
args[0], args[1], &(char){0}, 0, 0)
|
||||
>= 0 ? CONTROL_OK : CONTROL_ERROR;
|
||||
}
|
||||
case AF_CONTROL_GET_METADATA:
|
||||
if (p->metadata) {
|
||||
*(struct mp_tags *)arg = *p->metadata;
|
||||
@@ -257,32 +266,15 @@ static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
AVFilterLink *l_in = p->in->outputs[0];
|
||||
|
||||
if (data) {
|
||||
frame = av_frame_alloc();
|
||||
frame = mp_audio_to_avframe_and_unref(data);
|
||||
data = NULL;
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
frame->nb_samples = data->samples;
|
||||
frame->format = l_in->format;
|
||||
|
||||
// Timebase is 1/sample_rate
|
||||
frame->pts = p->samples_in;
|
||||
|
||||
frame->channel_layout = l_in->channel_layout;
|
||||
frame->sample_rate = l_in->sample_rate;
|
||||
#if LIBAVFILTER_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS
|
||||
frame->channels = l_in->channels;
|
||||
#endif
|
||||
|
||||
frame->extended_data = frame->data;
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
frame->data[n] = data->planes[n];
|
||||
frame->linesize[0] = frame->nb_samples * data->sstride;
|
||||
|
||||
p->samples_in += data->samples;
|
||||
p->samples_in += frame->nb_samples;
|
||||
}
|
||||
|
||||
if (av_buffersrc_add_frame(p->in, frame) < 0)
|
||||
|
||||
@@ -179,6 +179,37 @@ bool af_lavrresample_test_conversion(int src_format, int dst_format)
|
||||
check_output_conversion(dst_format) != AV_SAMPLE_FMT_NONE;
|
||||
}
|
||||
|
||||
static struct mp_chmap fudge_pairs[][2] = {
|
||||
{MP_CHMAP2(BL, BR), MP_CHMAP2(SL, SR)},
|
||||
{MP_CHMAP2(SL, SR), MP_CHMAP2(BL, BR)},
|
||||
{MP_CHMAP2(SDL, SDR), MP_CHMAP2(SL, SR)},
|
||||
{MP_CHMAP2(SL, SR), MP_CHMAP2(SDL, SDR)},
|
||||
};
|
||||
|
||||
// Modify out_layout and return the new value. The intention is reducing the
|
||||
// loss libswresample's rematrixing will cause by exchanging similar, but
|
||||
// strictly speaking incompatible channel pairs. For example, 7.1 should be
|
||||
// changed to 7.1(wide) without dropping the SL/SR channels. (We still leave
|
||||
// it to libswresample to create the remix matrix.)
|
||||
static uint64_t fudge_layout_conversion(struct af_instance *af,
|
||||
uint64_t in, uint64_t out)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(fudge_pairs); n++) {
|
||||
uint64_t a = mp_chmap_to_lavc(&fudge_pairs[n][0]);
|
||||
uint64_t b = mp_chmap_to_lavc(&fudge_pairs[n][1]);
|
||||
if ((in & a) == a && (in & b) == 0 &&
|
||||
(out & a) == 0 && (out & b) == b)
|
||||
{
|
||||
out = (out & ~b) | a;
|
||||
|
||||
MP_VERBOSE(af, "Fudge: %s -> %s\n",
|
||||
mp_chmap_to_str(&fudge_pairs[n][0]),
|
||||
mp_chmap_to_str(&fudge_pairs[n][1]));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// mp_chmap_get_reorder() performs:
|
||||
// to->speaker[n] = from->speaker[src[n]]
|
||||
// but libavresample does:
|
||||
@@ -230,10 +261,13 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
|
||||
av_opt_set_double(s->avrctx, "cutoff", s->opts.cutoff, 0);
|
||||
|
||||
int normalize = s->opts.normalize;
|
||||
if (normalize < 0)
|
||||
normalize = af->opts->audio_normalize;
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
av_opt_set_double(s->avrctx, "rematrix_maxval", s->opts.normalize ? 1 : 1000, 0);
|
||||
av_opt_set_double(s->avrctx, "rematrix_maxval", normalize ? 1 : 1000, 0);
|
||||
#else
|
||||
av_opt_set_int(s->avrctx, "normalize_mix_level", s->opts.normalize, 0);
|
||||
av_opt_set_int(s->avrctx, "normalize_mix_level", !!normalize, 0);
|
||||
#endif
|
||||
|
||||
if (mp_set_avopts(af->log, s->avrctx, s->avopts) < 0)
|
||||
@@ -297,6 +331,8 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
if (map_out.num > out_lavc.num)
|
||||
mp_audio_set_channels(&s->pool_fmt, &map_out);
|
||||
|
||||
out_ch_layout = fudge_layout_conversion(af, in_ch_layout, out_ch_layout);
|
||||
|
||||
// Real conversion; output is input to avrctx_out.
|
||||
av_opt_set_int(s->avrctx, "in_channel_layout", in_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx, "out_channel_layout", out_ch_layout, 0);
|
||||
@@ -583,7 +619,7 @@ const struct af_info af_info_lavrresample = {
|
||||
.filter_size = 16,
|
||||
.cutoff = 0.0,
|
||||
.phase_shift = 10,
|
||||
.normalize = 1,
|
||||
.normalize = -1,
|
||||
},
|
||||
.playback_speed = 1.0,
|
||||
.allow_detach = 1,
|
||||
@@ -594,7 +630,8 @@ const struct af_info af_info_lavrresample = {
|
||||
OPT_FLAG("linear", opts.linear, 0),
|
||||
OPT_DOUBLE("cutoff", opts.cutoff, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLAG("detach", allow_detach, 0),
|
||||
OPT_FLAG("normalize", opts.normalize, 0),
|
||||
OPT_CHOICE("normalize", opts.normalize, 0,
|
||||
({"no", 0}, {"yes", 1}, {"auto", -1})),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <stdlib.h>
|
||||
|
||||
@@ -245,6 +245,37 @@ void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]
|
||||
out_formats[num] = 0;
|
||||
}
|
||||
|
||||
// Return the best match to src_samplerate from the list provided in the array
|
||||
// *available, which must be terminated by 0, or itself NULL. If *available is
|
||||
// empty or NULL, return a negative value. Exact match to src_samplerate is
|
||||
// most preferred, followed by the lowest integer multiple, followed by the
|
||||
// maximum of *available.
|
||||
int af_select_best_samplerate(int src_samplerate, const int *available)
|
||||
{
|
||||
if (!available)
|
||||
return -1;
|
||||
|
||||
int min_mult_rate = INT_MAX;
|
||||
int max_rate = INT_MIN;
|
||||
for (int i = 0; available[i]; i++) {
|
||||
if (available[i] == src_samplerate)
|
||||
return available[i];
|
||||
|
||||
if (!(available[i] % src_samplerate))
|
||||
min_mult_rate = MPMIN(min_mult_rate, available[i]);
|
||||
|
||||
max_rate = MPMAX(max_rate, available[i]);
|
||||
}
|
||||
|
||||
if (min_mult_rate < INT_MAX)
|
||||
return min_mult_rate;
|
||||
|
||||
if (max_rate > INT_MIN)
|
||||
return max_rate;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Return the number of samples that make up one frame in this format.
|
||||
// You get the byte size by multiplying them with sample size and channel count.
|
||||
int af_format_sample_alignment(int format)
|
||||
|
||||
@@ -76,6 +76,7 @@ int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int sampler
|
||||
void af_fill_silence(void *dst, size_t bytes, int format);
|
||||
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]);
|
||||
int af_select_best_samplerate(int src_sampelrate, const int *available);
|
||||
|
||||
int af_format_sample_alignment(int format);
|
||||
|
||||
|
||||
+11
-5
@@ -27,7 +27,7 @@
|
||||
#include "audio/filter/af.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "mixer.h"
|
||||
|
||||
struct mixer {
|
||||
@@ -120,8 +120,11 @@ void mixer_getvolume(struct mixer *mixer, float *l, float *r)
|
||||
*r = mixer->vol_r;
|
||||
}
|
||||
|
||||
static void setvolume_internal(struct mixer *mixer, float l, float r)
|
||||
static void setvolume_internal(struct mixer *mixer)
|
||||
{
|
||||
float l = mixer->vol_l, r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
if (!mixer->softvol) {
|
||||
MP_DBG(mixer, "Setting volume on AO.\n");
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
@@ -147,8 +150,8 @@ void mixer_setvolume(struct mixer *mixer, float l, float r)
|
||||
float max = mixer_getmaxvolume(mixer);
|
||||
mixer->vol_l = MPCLAMP(l, 0, max);
|
||||
mixer->vol_r = MPCLAMP(r, 0, max);
|
||||
if (mixer->ao && !(mixer->emulate_mute && mixer->muted))
|
||||
setvolume_internal(mixer, mixer->vol_l, mixer->vol_r);
|
||||
if (mixer->ao)
|
||||
setvolume_internal(mixer);
|
||||
}
|
||||
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b)
|
||||
@@ -167,7 +170,7 @@ void mixer_setmute(struct mixer *mixer, bool mute)
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
if (mixer->emulate_mute) {
|
||||
setvolume_internal(mixer, mixer->vol_l*!mute, mixer->vol_r*!mute);
|
||||
setvolume_internal(mixer);
|
||||
} else {
|
||||
ao_control(mixer->ao, AOCONTROL_SET_MUTE, &mute);
|
||||
}
|
||||
@@ -293,6 +296,8 @@ static void restore_volume(struct mixer *mixer)
|
||||
|
||||
const char *prev_driver = mixer->driver;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao_get_name(ao);
|
||||
if (!prev_driver[0])
|
||||
prev_driver = mixer->driver;
|
||||
|
||||
// Restore old parameters if volume won't survive reinitialization.
|
||||
// But not if volume scale is possibly different.
|
||||
@@ -394,4 +399,5 @@ void mixer_uninit_audio(struct mixer *mixer)
|
||||
}
|
||||
mixer->ao = NULL;
|
||||
mixer->af = NULL;
|
||||
mixer->softvol = false;
|
||||
}
|
||||
+5
-5
@@ -20,7 +20,7 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
@@ -43,9 +43,9 @@ extern const struct ao_driver audio_out_sndio;
|
||||
extern const struct ao_driver audio_out_pulse;
|
||||
extern const struct ao_driver audio_out_jack;
|
||||
extern const struct ao_driver audio_out_openal;
|
||||
extern const struct ao_driver audio_out_opensles;
|
||||
extern const struct ao_driver audio_out_null;
|
||||
extern const struct ao_driver audio_out_alsa;
|
||||
extern const struct ao_driver audio_out_dsound;
|
||||
extern const struct ao_driver audio_out_wasapi;
|
||||
extern const struct ao_driver audio_out_pcm;
|
||||
extern const struct ao_driver audio_out_lavc;
|
||||
@@ -65,9 +65,6 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_WASAPI
|
||||
&audio_out_wasapi,
|
||||
#endif
|
||||
#if HAVE_DSOUND
|
||||
&audio_out_dsound,
|
||||
#endif
|
||||
#if HAVE_OSS_AUDIO
|
||||
&audio_out_oss,
|
||||
#endif
|
||||
@@ -78,6 +75,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_OPENAL
|
||||
&audio_out_openal,
|
||||
#endif
|
||||
#if HAVE_OPENSLES
|
||||
&audio_out_opensles,
|
||||
#endif
|
||||
#if HAVE_SDL1 || HAVE_SDL2
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
|
||||
@@ -384,8 +384,8 @@ static int hotplug_init(struct ao *ao)
|
||||
err = AudioObjectAddPropertyListener(
|
||||
kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao);
|
||||
if (err != noErr) {
|
||||
char *c1 = fourcc_repr(hotplug_properties[i]);
|
||||
char *c2 = fourcc_repr(err);
|
||||
char *c1 = mp_tag_str(hotplug_properties[i]);
|
||||
char *c2 = mp_tag_str(err);
|
||||
MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2);
|
||||
goto coreaudio_error;
|
||||
}
|
||||
@@ -409,8 +409,8 @@ static void hotplug_uninit(struct ao *ao)
|
||||
err = AudioObjectRemovePropertyListener(
|
||||
kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao);
|
||||
if (err != noErr) {
|
||||
char *c1 = fourcc_repr(hotplug_properties[i]);
|
||||
char *c2 = fourcc_repr(err);
|
||||
char *c1 = mp_tag_str(hotplug_properties[i]);
|
||||
char *c2 = mp_tag_str(err);
|
||||
MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 "common/common.h"
|
||||
@@ -133,6 +133,29 @@ coreaudio_error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
#define CHMAP(n, ...) &(struct mp_chmap) MP_CONCAT(MP_CHMAP, n) (__VA_ARGS__)
|
||||
|
||||
// Replace each channel in a with b (a->num == b->num)
|
||||
static void replace_submap(struct mp_chmap *dst, struct mp_chmap *a,
|
||||
struct mp_chmap *b)
|
||||
{
|
||||
struct mp_chmap t = *dst;
|
||||
if (!mp_chmap_is_valid(&t) || mp_chmap_diffn(a, &t) != 0)
|
||||
return;
|
||||
assert(a->num == b->num);
|
||||
for (int n = 0; n < t.num; n++) {
|
||||
for (int i = 0; i < a->num; i++) {
|
||||
if (t.speaker[n] == a->speaker[i]) {
|
||||
t.speaker[n] = b->speaker[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mp_chmap_is_valid(&t))
|
||||
*dst = t;
|
||||
}
|
||||
|
||||
static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap)
|
||||
{
|
||||
@@ -163,6 +186,10 @@ static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
chmap->speaker[n] = speaker;
|
||||
}
|
||||
|
||||
// Remap weird 7.1(rear) layouts correctly.
|
||||
replace_submap(chmap, CHMAP(6, FL, FR, BL, BR, SDL, SDR),
|
||||
CHMAP(6, FL, FR, SL, SR, BL, BR));
|
||||
|
||||
talloc_free(talloc_ctx);
|
||||
MP_VERBOSE(ao, "mp chmap: %s\n", mp_chmap_to_str(chmap));
|
||||
return mp_chmap_is_valid(chmap) && !mp_chmap_is_unknown(chmap);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPV_COREAUDIO_CHMAP_H
|
||||
|
||||
@@ -367,7 +367,7 @@ static void uninit(struct ao *ao)
|
||||
CHECK_CA_WARN("failed to remove device render callback");
|
||||
|
||||
if (!ca_change_physical_format_sync(ao, p->stream, p->original_asbd))
|
||||
MP_WARN(ao, "can't revert to original device format");
|
||||
MP_WARN(ao, "can't revert to original device format\n");
|
||||
|
||||
err = ca_enable_mixing(ao, p->device, p->changed_mixing);
|
||||
CHECK_CA_WARN("can't re-enable mixing");
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
OSStatus ca_get(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
uint32_t size, void *data)
|
||||
|
||||
@@ -136,36 +136,11 @@ coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
char *fourcc_repr_buf(char *buf, size_t buf_size, uint32_t code)
|
||||
{
|
||||
// Extract FourCC letters from the uint32_t and finde out if it's a valid
|
||||
// code that is made of letters.
|
||||
unsigned char fcc[4] = {
|
||||
(code >> 24) & 0xFF,
|
||||
(code >> 16) & 0xFF,
|
||||
(code >> 8) & 0xFF,
|
||||
code & 0xFF,
|
||||
};
|
||||
|
||||
bool valid_fourcc = true;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (fcc[i] < 32 || fcc[i] >= 128)
|
||||
valid_fourcc = false;
|
||||
}
|
||||
|
||||
if (valid_fourcc)
|
||||
snprintf(buf, buf_size, "'%c%c%c%c'", fcc[0], fcc[1], fcc[2], fcc[3]);
|
||||
else
|
||||
snprintf(buf, buf_size, "%u", (unsigned int)code);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
{
|
||||
if (code == noErr) return true;
|
||||
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, fourcc_repr(code));
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, mp_tag_str(code));
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -258,7 +233,7 @@ void ca_print_asbd(struct ao *ao, const char *description,
|
||||
const AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
uint32_t flags = asbd->mFormatFlags;
|
||||
char *format = fourcc_repr(asbd->mFormatID);
|
||||
char *format = mp_tag_str(asbd->mFormatID);
|
||||
int mpfmt = ca_asbd_to_mp_format(asbd);
|
||||
|
||||
MP_VERBOSE(ao,
|
||||
@@ -470,7 +445,7 @@ int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device)
|
||||
if (err == noErr) {
|
||||
latency_frames += temp;
|
||||
MP_VERBOSE(ao, "Latency property %s: %d frames\n",
|
||||
fourcc_repr(latency_properties[n]), (int)temp);
|
||||
mp_tag_str(latency_properties[n]), (int)temp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
CFStringRef cfstr_from_cstr(char *str);
|
||||
char *cfstr_get_cstr(CFStringRef cfstr);
|
||||
|
||||
char *fourcc_repr_buf(char *buf, size_t buf_size, uint32_t code);
|
||||
#define fourcc_repr(code) fourcc_repr_buf((char[40]){0}, 40, code)
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
|
||||
|
||||
#define CHECK_CA_ERROR_L(label, message) \
|
||||
|
||||
@@ -1,707 +0,0 @@
|
||||
/*
|
||||
* Windows DirectSound interface
|
||||
*
|
||||
* Copyright (c) 2004 Gabor Szecsi <deje@miki.hu>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
\todo verify/extend multichannel support
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
#define DIRECTSOUND_VERSION 0x0600
|
||||
#include <dsound.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/format.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/io.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
/**
|
||||
\todo use the definitions from the win32 api headers when they define these
|
||||
*/
|
||||
#define WAVE_FORMAT_IEEE_FLOAT 0x0003
|
||||
#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092
|
||||
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE
|
||||
|
||||
static const GUID KSDATAFORMAT_SUBTYPE_PCM = {
|
||||
0x1, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
|
||||
};
|
||||
|
||||
#if 0
|
||||
#define DSSPEAKER_HEADPHONE 0x00000001
|
||||
#define DSSPEAKER_MONO 0x00000002
|
||||
#define DSSPEAKER_QUAD 0x00000003
|
||||
#define DSSPEAKER_STEREO 0x00000004
|
||||
#define DSSPEAKER_SURROUND 0x00000005
|
||||
#define DSSPEAKER_5POINT1 0x00000006
|
||||
#endif
|
||||
|
||||
#ifndef _WAVEFORMATEXTENSIBLE_
|
||||
typedef struct {
|
||||
WAVEFORMATEX Format;
|
||||
union {
|
||||
WORD wValidBitsPerSample; /* bits of precision */
|
||||
WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */
|
||||
WORD wReserved; /* If neither applies, set to zero. */
|
||||
} Samples;
|
||||
DWORD dwChannelMask; /* which channels are */
|
||||
/* present in stream */
|
||||
GUID SubFormat;
|
||||
} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE;
|
||||
#endif
|
||||
|
||||
struct priv {
|
||||
HINSTANCE hdsound_dll; ///handle to the dll
|
||||
LPDIRECTSOUND hds; ///direct sound object
|
||||
LPDIRECTSOUNDBUFFER hdspribuf; ///primary direct sound buffer
|
||||
LPDIRECTSOUNDBUFFER hdsbuf; ///secondary direct sound buffer (stream buffer)
|
||||
int buffer_size; ///size in bytes of the direct sound buffer
|
||||
int write_offset; ///offset of the write cursor in the direct sound buffer
|
||||
int min_free_space; ///if the free space is below this value get_space() will return 0
|
||||
///there will always be at least this amout of free space to prevent
|
||||
///get_space() from returning wrong values when buffer is 100% full.
|
||||
///will be replaced with nBlockAlign in init()
|
||||
int underrun_check; ///0 or last reported free space (underrun detection)
|
||||
int device_num; ///wanted device number
|
||||
GUID device; ///guid of the device
|
||||
int audio_volume;
|
||||
|
||||
int device_index;
|
||||
|
||||
int outburst; ///play in multiple of chunks of this size
|
||||
|
||||
int cfg_device;
|
||||
int cfg_buffersize;
|
||||
|
||||
struct ao_device_list *listing; ///temporary during list_devs()
|
||||
};
|
||||
|
||||
/***************************************************************************************/
|
||||
|
||||
/**
|
||||
\brief output error message
|
||||
\param err error code
|
||||
\return string with the error message
|
||||
*/
|
||||
static char * dserr2str(int err)
|
||||
{
|
||||
switch (err) {
|
||||
case DS_OK: return "DS_OK";
|
||||
case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION";
|
||||
case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION";
|
||||
case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL";
|
||||
case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM";
|
||||
case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL";
|
||||
case DSERR_GENERIC: return "DSERR_GENERIC";
|
||||
case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED";
|
||||
case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY";
|
||||
case DSERR_BADFORMAT: return "DSERR_BADFORMAT";
|
||||
case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED";
|
||||
case DSERR_NODRIVER: return "DSERR_NODRIVER";
|
||||
case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED";
|
||||
case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION";
|
||||
case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST";
|
||||
case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO";
|
||||
case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED";
|
||||
case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE";
|
||||
case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
\brief uninitialize direct sound
|
||||
*/
|
||||
static void UninitDirectSound(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
// finally release the DirectSound object
|
||||
if (p->hds) {
|
||||
IDirectSound_Release(p->hds);
|
||||
p->hds = NULL;
|
||||
}
|
||||
// free DSOUND.DLL
|
||||
if (p->hdsound_dll) {
|
||||
FreeLibrary(p->hdsound_dll);
|
||||
p->hdsound_dll = NULL;
|
||||
}
|
||||
MP_VERBOSE(ao, "DirectSound uninitialized\n");
|
||||
}
|
||||
|
||||
/**
|
||||
\brief enumerate direct sound devices
|
||||
\return TRUE to continue with the enumeration
|
||||
*/
|
||||
static BOOL CALLBACK DirectSoundEnum(LPGUID guid, LPCSTR desc, LPCSTR module,
|
||||
LPVOID context)
|
||||
{
|
||||
struct ao *ao = context;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
MP_VERBOSE(ao, "%i %s ", p->device_index, desc);
|
||||
if (p->device_num == p->device_index) {
|
||||
MP_VERBOSE(ao, "<--");
|
||||
if (guid)
|
||||
memcpy(&p->device, guid, sizeof(GUID));
|
||||
}
|
||||
char *guidstr = talloc_strdup(NULL, "");
|
||||
if (guid) {
|
||||
wchar_t guidwstr[80] = {0};
|
||||
StringFromGUID2(guid, guidwstr, MP_ARRAY_SIZE(guidwstr));
|
||||
char *nstr = mp_to_utf8(NULL, guidwstr);
|
||||
if (nstr) {
|
||||
talloc_free(guidstr);
|
||||
guidstr = nstr;
|
||||
}
|
||||
}
|
||||
if (p->device_num < 0 && ao->device) {
|
||||
if (strcmp(ao->device, guidstr) == 0) {
|
||||
MP_VERBOSE(ao, "<--");
|
||||
p->device_num = p->device_index;
|
||||
if (guid)
|
||||
memcpy(&p->device, guid, sizeof(GUID));
|
||||
}
|
||||
}
|
||||
if (p->listing) {
|
||||
struct ao_device_desc e = {guidstr, desc};
|
||||
ao_device_list_add(p->listing, ao, &e);
|
||||
}
|
||||
talloc_free(guidstr);
|
||||
|
||||
MP_VERBOSE(ao, "\n");
|
||||
p->device_index++;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void EnumDevs(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
p->device_index = 0;
|
||||
p->device_num = p->cfg_device;
|
||||
|
||||
HRESULT (WINAPI *OurDirectSoundEnumerate)(LPDSENUMCALLBACKA, LPVOID);
|
||||
OurDirectSoundEnumerate = (void *)GetProcAddress(p->hdsound_dll,
|
||||
"DirectSoundEnumerateA");
|
||||
|
||||
if (OurDirectSoundEnumerate == NULL) {
|
||||
MP_ERR(ao, "GetProcAddress FAILED\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Enumerate all directsound p->devices
|
||||
MP_VERBOSE(ao, "Output Devices:\n");
|
||||
OurDirectSoundEnumerate(DirectSoundEnum, ao);
|
||||
}
|
||||
|
||||
static int LoadDirectSound(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
// initialize directsound
|
||||
p->hdsound_dll = LoadLibrary(L"DSOUND.DLL");
|
||||
if (p->hdsound_dll == NULL) {
|
||||
MP_ERR(ao, "cannot load DSOUND.DLL\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
bool need_init = !p->hdsound_dll;
|
||||
if (need_init && !LoadDirectSound(ao))
|
||||
return;
|
||||
|
||||
p->listing = list;
|
||||
EnumDevs(ao);
|
||||
p->listing = NULL;
|
||||
|
||||
if (need_init)
|
||||
UninitDirectSound(ao);
|
||||
}
|
||||
|
||||
/**
|
||||
\brief initilize direct sound
|
||||
\return 0 if error, 1 if ok
|
||||
*/
|
||||
static int InitDirectSound(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
DSCAPS dscaps;
|
||||
|
||||
if (!LoadDirectSound(ao))
|
||||
return 0;
|
||||
|
||||
HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
|
||||
OurDirectSoundCreate =
|
||||
(void *)GetProcAddress(p->hdsound_dll, "DirectSoundCreate");
|
||||
|
||||
if (OurDirectSoundCreate == NULL) {
|
||||
MP_ERR(ao, "GetProcAddress FAILED\n");
|
||||
FreeLibrary(p->hdsound_dll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EnumDevs(ao);
|
||||
|
||||
// Create the direct sound object
|
||||
if (FAILED(OurDirectSoundCreate((p->device_num > 0) ? &p->device : NULL,
|
||||
&p->hds, NULL)))
|
||||
{
|
||||
MP_ERR(ao, "cannot create a DirectSound device\n");
|
||||
FreeLibrary(p->hdsound_dll);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set DirectSound Cooperative level, ie what control we want over Windows
|
||||
* sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
|
||||
* settings of the primary buffer, but also that only the sound of our
|
||||
* application will be audible when it will have the focus.
|
||||
* !!! (this is not really working as intended yet because to set the
|
||||
* cooperative level you need the window handle of your application, and
|
||||
* I don't know of any easy way to get it. Especially since we might play
|
||||
* sound without any video, and so what window handle should we use ???
|
||||
* The hack for now is to use the Desktop window handle - it seems to be
|
||||
* working */
|
||||
if (IDirectSound_SetCooperativeLevel(p->hds, GetDesktopWindow(),
|
||||
DSSCL_EXCLUSIVE))
|
||||
{
|
||||
MP_ERR(ao, "cannot set direct sound cooperative level\n");
|
||||
IDirectSound_Release(p->hds);
|
||||
FreeLibrary(p->hdsound_dll);
|
||||
return 0;
|
||||
}
|
||||
MP_VERBOSE(ao, "DirectSound initialized\n");
|
||||
|
||||
memset(&dscaps, 0, sizeof(DSCAPS));
|
||||
dscaps.dwSize = sizeof(DSCAPS);
|
||||
if (DS_OK == IDirectSound_GetCaps(p->hds, &dscaps)) {
|
||||
if (dscaps.dwFlags & DSCAPS_EMULDRIVER)
|
||||
MP_VERBOSE(ao, "DirectSound is emulated\n");
|
||||
} else {
|
||||
MP_VERBOSE(ao, "cannot get device capabilities\n");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief destroy the direct sound buffer
|
||||
*/
|
||||
static void DestroyBuffer(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->hdsbuf) {
|
||||
IDirectSoundBuffer_Release(p->hdsbuf);
|
||||
p->hdsbuf = NULL;
|
||||
}
|
||||
if (p->hdspribuf) {
|
||||
IDirectSoundBuffer_Release(p->hdspribuf);
|
||||
p->hdspribuf = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
\brief fill sound buffer
|
||||
\param data pointer to the sound data to copy
|
||||
\param len length of the data to copy in bytes
|
||||
\return number of copyed bytes
|
||||
*/
|
||||
static int write_buffer(struct ao *ao, unsigned char *data, int len)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
HRESULT res;
|
||||
LPVOID lpvPtr1;
|
||||
DWORD dwBytes1;
|
||||
LPVOID lpvPtr2;
|
||||
DWORD dwBytes2;
|
||||
|
||||
p->underrun_check = 0;
|
||||
|
||||
// Lock the buffer
|
||||
res = IDirectSoundBuffer_Lock(p->hdsbuf, p->write_offset, len, &lpvPtr1,
|
||||
&dwBytes1, &lpvPtr2, &dwBytes2, 0);
|
||||
// If the buffer was lost, restore and retry lock.
|
||||
if (DSERR_BUFFERLOST == res) {
|
||||
IDirectSoundBuffer_Restore(p->hdsbuf);
|
||||
res = IDirectSoundBuffer_Lock(p->hdsbuf, p->write_offset, len, &lpvPtr1,
|
||||
&dwBytes1, &lpvPtr2, &dwBytes2, 0);
|
||||
}
|
||||
|
||||
|
||||
if (SUCCEEDED(res)) {
|
||||
memcpy(lpvPtr1, data, dwBytes1);
|
||||
if (NULL != lpvPtr2)
|
||||
memcpy(lpvPtr2, data + dwBytes1, dwBytes2);
|
||||
p->write_offset += dwBytes1 + dwBytes2;
|
||||
if (p->write_offset >= p->buffer_size)
|
||||
p->write_offset = dwBytes2;
|
||||
|
||||
// Release the data back to DirectSound.
|
||||
res = IDirectSoundBuffer_Unlock(p->hdsbuf, lpvPtr1, dwBytes1, lpvPtr2,
|
||||
dwBytes2);
|
||||
if (SUCCEEDED(res)) {
|
||||
// Success.
|
||||
DWORD status;
|
||||
IDirectSoundBuffer_GetStatus(p->hdsbuf, &status);
|
||||
if (!(status & DSBSTATUS_PLAYING))
|
||||
res = IDirectSoundBuffer_Play(p->hdsbuf, 0, 0, DSBPLAY_LOOPING);
|
||||
return dwBytes1 + dwBytes2;
|
||||
}
|
||||
}
|
||||
// Lock, Unlock, or Restore failed.
|
||||
return 0;
|
||||
}
|
||||
|
||||
/***************************************************************************************/
|
||||
|
||||
/**
|
||||
\brief handle control commands
|
||||
\param cmd command
|
||||
\param arg argument
|
||||
\return CONTROL_OK or CONTROL_UNKNOWN in case the command is not supported
|
||||
*/
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
DWORD volume;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME: {
|
||||
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
||||
vol->left = vol->right = p->audio_volume;
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_SET_VOLUME: {
|
||||
ao_control_vol_t *vol = (ao_control_vol_t *)arg;
|
||||
volume = p->audio_volume = vol->right;
|
||||
if (volume < 1)
|
||||
volume = 1;
|
||||
volume = (DWORD)(log10(volume) * 5000.0) - 10000;
|
||||
IDirectSoundBuffer_SetVolume(p->hdsbuf, volume);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief setup sound device
|
||||
\param rate samplerate
|
||||
\param channels number of channels
|
||||
\param format format
|
||||
\param flags unused
|
||||
\return 0=success -1=fail
|
||||
*/
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int res;
|
||||
|
||||
if (!InitDirectSound(ao))
|
||||
return -1;
|
||||
|
||||
p->audio_volume = 100;
|
||||
|
||||
// ok, now create the buffers
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
DSBUFFERDESC dsbpridesc;
|
||||
DSBUFFERDESC dsbdesc;
|
||||
int format = af_fmt_from_planar(ao->format);
|
||||
int rate = ao->samplerate;
|
||||
|
||||
if (!af_fmt_is_spdif(format)) {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
return -1;
|
||||
}
|
||||
switch (format) {
|
||||
case AF_FORMAT_S24:
|
||||
case AF_FORMAT_S16:
|
||||
case AF_FORMAT_U8:
|
||||
break;
|
||||
default:
|
||||
if (af_fmt_is_spdif(format))
|
||||
break;
|
||||
MP_VERBOSE(ao, "format %s not supported defaulting to Signed 16-bit Little-Endian\n",
|
||||
af_fmt_to_str(format));
|
||||
format = AF_FORMAT_S16;
|
||||
}
|
||||
//set our audio parameters
|
||||
ao->samplerate = rate;
|
||||
ao->format = format;
|
||||
ao->bps = ao->channels.num * rate * af_fmt_to_bytes(format);
|
||||
int buffersize = ao->bps * p->cfg_buffersize / 1000;
|
||||
MP_VERBOSE(ao, "Samplerate:%iHz Channels:%i Format:%s\n", rate,
|
||||
ao->channels.num, af_fmt_to_str(format));
|
||||
MP_VERBOSE(ao, "Buffersize:%d bytes (%f msec)\n",
|
||||
buffersize, buffersize * 1000.0 / ao->bps);
|
||||
|
||||
//fill waveformatex
|
||||
ZeroMemory(&wformat, sizeof(WAVEFORMATEXTENSIBLE));
|
||||
wformat.Format.cbSize = (ao->channels.num > 2)
|
||||
? sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) : 0;
|
||||
wformat.Format.nChannels = ao->channels.num;
|
||||
wformat.Format.nSamplesPerSec = rate;
|
||||
if (af_fmt_is_spdif(format)) {
|
||||
// Whether it also works with e.g. DTS is unknown, but probably does.
|
||||
wformat.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF;
|
||||
wformat.Format.wBitsPerSample = 16;
|
||||
wformat.Format.nBlockAlign = 4;
|
||||
} else {
|
||||
wformat.Format.wFormatTag = (ao->channels.num > 2)
|
||||
? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM;
|
||||
int bps = af_fmt_to_bytes(format);
|
||||
wformat.Format.wBitsPerSample = bps * 8;
|
||||
wformat.Format.nBlockAlign = wformat.Format.nChannels * bps;
|
||||
}
|
||||
|
||||
// fill in primary sound buffer descriptor
|
||||
memset(&dsbpridesc, 0, sizeof(DSBUFFERDESC));
|
||||
dsbpridesc.dwSize = sizeof(DSBUFFERDESC);
|
||||
dsbpridesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
dsbpridesc.dwBufferBytes = 0;
|
||||
dsbpridesc.lpwfxFormat = NULL;
|
||||
|
||||
// fill in the secondary sound buffer (=stream buffer) descriptor
|
||||
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
|
||||
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
|
||||
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */
|
||||
| DSBCAPS_GLOBALFOCUS /** Allows background playing */
|
||||
| DSBCAPS_CTRLVOLUME; /** volume control enabled */
|
||||
|
||||
if (ao->channels.num > 2) {
|
||||
wformat.dwChannelMask = mp_chmap_to_waveext(&ao->channels);
|
||||
wformat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
wformat.Samples.wValidBitsPerSample = wformat.Format.wBitsPerSample;
|
||||
// Needed for 5.1 on emu101k - shit soundblaster
|
||||
dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE;
|
||||
}
|
||||
wformat.Format.nAvgBytesPerSec = wformat.Format.nSamplesPerSec *
|
||||
wformat.Format.nBlockAlign;
|
||||
|
||||
dsbdesc.dwBufferBytes = buffersize;
|
||||
dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wformat;
|
||||
p->buffer_size = dsbdesc.dwBufferBytes;
|
||||
p->write_offset = 0;
|
||||
p->min_free_space = wformat.Format.nBlockAlign;
|
||||
p->outburst = wformat.Format.nBlockAlign * 512;
|
||||
|
||||
// create primary buffer and set its format
|
||||
|
||||
res = IDirectSound_CreateSoundBuffer(p->hds, &dsbpridesc, &p->hdspribuf, NULL);
|
||||
if (res != DS_OK) {
|
||||
UninitDirectSound(ao);
|
||||
MP_ERR(ao, "cannot create primary buffer (%s)\n", dserr2str(res));
|
||||
return -1;
|
||||
}
|
||||
res = IDirectSoundBuffer_SetFormat(p->hdspribuf, (WAVEFORMATEX *)&wformat);
|
||||
if (res != DS_OK) {
|
||||
MP_WARN(ao, "cannot set primary buffer format (%s), using "
|
||||
"standard setting (bad quality)", dserr2str(res));
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "primary buffer created\n");
|
||||
|
||||
// now create the stream buffer
|
||||
|
||||
res = IDirectSound_CreateSoundBuffer(p->hds, &dsbdesc, &p->hdsbuf, NULL);
|
||||
if (res != DS_OK) {
|
||||
if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) {
|
||||
// Try without DSBCAPS_LOCHARDWARE
|
||||
dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE;
|
||||
res = IDirectSound_CreateSoundBuffer(p->hds, &dsbdesc, &p->hdsbuf, NULL);
|
||||
}
|
||||
if (res != DS_OK) {
|
||||
UninitDirectSound(ao);
|
||||
MP_ERR(ao, "cannot create secondary (stream)buffer (%s)\n",
|
||||
dserr2str(res));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
MP_VERBOSE(ao, "secondary (stream)buffer created\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
\brief stop playing and empty buffers (for seeking/pause)
|
||||
*/
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
IDirectSoundBuffer_Stop(p->hdsbuf);
|
||||
// reset directsound buffer
|
||||
IDirectSoundBuffer_SetCurrentPosition(p->hdsbuf, 0);
|
||||
p->write_offset = 0;
|
||||
p->underrun_check = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief stop playing, keep buffers (for pause)
|
||||
*/
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
IDirectSoundBuffer_Stop(p->hdsbuf);
|
||||
}
|
||||
|
||||
/**
|
||||
\brief resume playing, after audio_pause()
|
||||
*/
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
IDirectSoundBuffer_Play(p->hdsbuf, 0, 0, DSBPLAY_LOOPING);
|
||||
}
|
||||
|
||||
/**
|
||||
\brief close audio device
|
||||
\param immed stop playback immediately
|
||||
*/
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
reset(ao);
|
||||
|
||||
DestroyBuffer(ao);
|
||||
UninitDirectSound(ao);
|
||||
}
|
||||
|
||||
// return exact number of free (safe to write) bytes
|
||||
static int check_free_buffer_size(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int space;
|
||||
DWORD play_offset;
|
||||
IDirectSoundBuffer_GetCurrentPosition(p->hdsbuf, &play_offset, NULL);
|
||||
space = p->buffer_size - (p->write_offset - play_offset);
|
||||
// | | <-- const --> | | |
|
||||
// buffer start play_cursor write_cursor p->write_offset buffer end
|
||||
// play_cursor is the actual position of the play cursor
|
||||
// write_cursor is the position after which it is assumed to be save to write data
|
||||
// p->write_offset is the position where we actually write the data to
|
||||
if (space > p->buffer_size)
|
||||
space -= p->buffer_size; // p->write_offset < play_offset
|
||||
// Check for buffer underruns. An underrun happens if DirectSound
|
||||
// started to play old data beyond the current p->write_offset. Detect this
|
||||
// by checking whether the free space shrinks, even though no data was
|
||||
// written (i.e. no write_buffer). Doesn't always work, but the only
|
||||
// reason we need this is to deal with the situation when playback ends,
|
||||
// and the buffer is only half-filled.
|
||||
if (space < p->underrun_check) {
|
||||
// there's no useful data in the buffers
|
||||
space = p->buffer_size;
|
||||
reset(ao);
|
||||
}
|
||||
p->underrun_check = space;
|
||||
return space;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief find out how many bytes can be written into the audio buffer without
|
||||
\return free space in bytes, has to return 0 if the buffer is almost full
|
||||
*/
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int space = check_free_buffer_size(ao);
|
||||
if (space < p->min_free_space)
|
||||
return 0;
|
||||
return (space - p->min_free_space) / p->outburst * p->outburst / ao->sstride;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief play 'len' bytes of 'data'
|
||||
\param data pointer to the data to play
|
||||
\param len size in bytes of the data buffer, gets rounded down to outburst*n
|
||||
\param flags currently unused
|
||||
\return number of played bytes
|
||||
*/
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int len = samples * ao->sstride;
|
||||
|
||||
int space = check_free_buffer_size(ao);
|
||||
if (space < len)
|
||||
len = space;
|
||||
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
len = (len / p->outburst) * p->outburst;
|
||||
return write_buffer(ao, data[0], len) / ao->sstride;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief get the delay between the first and last sample in the buffer
|
||||
\return delay in seconds
|
||||
*/
|
||||
static double get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int space = check_free_buffer_size(ao);
|
||||
return (p->buffer_size - space) / (double)ao->bps;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_dsound = {
|
||||
.description = "Windows DirectSound audio output",
|
||||
.name = "dsound",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.get_space = get_space,
|
||||
.play = play,
|
||||
.get_delay = get_delay,
|
||||
.pause = audio_pause,
|
||||
.resume = audio_resume,
|
||||
.reset = reset,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INT("device", cfg_device, 0, OPTDEF_INT(-1)),
|
||||
OPT_INTRANGE("buffersize", cfg_buffersize, 0, 1, 10000, OPTDEF_INT(200)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+6
-1
@@ -31,7 +31,7 @@
|
||||
#include "common/common.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/msg.h"
|
||||
@@ -108,6 +108,11 @@ static int init(struct ao *ao)
|
||||
|
||||
codec = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream);
|
||||
|
||||
int samplerate = af_select_best_samplerate(ao->samplerate,
|
||||
codec->supported_samplerates);
|
||||
if (samplerate > 0)
|
||||
ao->samplerate = samplerate;
|
||||
|
||||
// TODO: Remove this redundancy with encode_lavc_alloc_stream also
|
||||
// setting the time base.
|
||||
// Using codec->time_bvase is deprecated, but needed for older lavf.
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
@@ -236,6 +236,7 @@ static int init(struct ao *ao)
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
ao_data = NULL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* OpenSL ES audio output driver.
|
||||
* Copyright (C) 2016 Ilya Zhuravlev <whatever@xyz.is>
|
||||
*
|
||||
* 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/timer.h"
|
||||
|
||||
#include <SLES/OpenSLES.h>
|
||||
#include <SLES/OpenSLES_Android.h>
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
struct priv {
|
||||
SLObjectItf sl, output_mix, player;
|
||||
SLBufferQueueItf buffer_queue;
|
||||
SLEngineItf engine;
|
||||
SLPlayItf play;
|
||||
char *curbuf, *buf1, *buf2;
|
||||
size_t buffer_size;
|
||||
pthread_mutex_t buffer_lock;
|
||||
|
||||
int cfg_frames_per_buffer;
|
||||
int cfg_sample_rate;
|
||||
};
|
||||
|
||||
static const int fmtmap[][2] = {
|
||||
{ AF_FORMAT_U8, SL_PCMSAMPLEFORMAT_FIXED_8 },
|
||||
{ AF_FORMAT_S16, SL_PCMSAMPLEFORMAT_FIXED_16 },
|
||||
{ AF_FORMAT_S32, SL_PCMSAMPLEFORMAT_FIXED_32 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
#define DESTROY(thing) \
|
||||
if (p->thing) { \
|
||||
(*p->thing)->Destroy(p->thing); \
|
||||
p->thing = NULL; \
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
DESTROY(player);
|
||||
DESTROY(output_mix);
|
||||
DESTROY(sl);
|
||||
|
||||
p->buffer_queue = NULL;
|
||||
p->engine = NULL;
|
||||
p->play = NULL;
|
||||
|
||||
pthread_mutex_destroy(&p->buffer_lock);
|
||||
|
||||
free(p->buf1);
|
||||
free(p->buf2);
|
||||
p->curbuf = p->buf1 = p->buf2 = NULL;
|
||||
p->buffer_size = 0;
|
||||
}
|
||||
|
||||
#undef DESTROY
|
||||
|
||||
static void buffer_callback(SLBufferQueueItf buffer_queue, void *context)
|
||||
{
|
||||
struct ao *ao = context;
|
||||
struct priv *p = ao->priv;
|
||||
SLresult res;
|
||||
void *data[1];
|
||||
double delay;
|
||||
|
||||
pthread_mutex_lock(&p->buffer_lock);
|
||||
|
||||
data[0] = p->curbuf;
|
||||
delay = 2 * p->buffer_size / (double)ao->bps;
|
||||
ao_read_data(ao, data, p->buffer_size / ao->sstride,
|
||||
mp_time_us() + 1000000LL * delay);
|
||||
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->curbuf, p->buffer_size);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
MP_ERR(ao, "Failed to Enqueue: %d\n", res);
|
||||
else
|
||||
p->curbuf = (p->curbuf == p->buf1) ? p->buf2 : p->buf1;
|
||||
|
||||
pthread_mutex_unlock(&p->buffer_lock);
|
||||
}
|
||||
|
||||
#define DEFAULT_BUFFER_SIZE_MS 50
|
||||
|
||||
#define CHK(stmt) \
|
||||
{ \
|
||||
SLresult res = stmt; \
|
||||
if (res != SL_RESULT_SUCCESS) { \
|
||||
MP_ERR(ao, "%s: %d\n", #stmt, res); \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
SLDataLocator_BufferQueue locator_buffer_queue;
|
||||
SLDataLocator_OutputMix locator_output_mix;
|
||||
SLDataFormat_PCM pcm;
|
||||
SLDataSource audio_source;
|
||||
SLDataSink audio_sink;
|
||||
|
||||
// This AO only supports two channels at the moment
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
|
||||
CHK(slCreateEngine(&p->sl, 0, NULL, 0, NULL, NULL));
|
||||
CHK((*p->sl)->Realize(p->sl, SL_BOOLEAN_FALSE));
|
||||
CHK((*p->sl)->GetInterface(p->sl, SL_IID_ENGINE, (void*)&p->engine));
|
||||
CHK((*p->engine)->CreateOutputMix(p->engine, &p->output_mix, 0, NULL, NULL));
|
||||
CHK((*p->output_mix)->Realize(p->output_mix, SL_BOOLEAN_FALSE));
|
||||
|
||||
locator_buffer_queue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
|
||||
locator_buffer_queue.numBuffers = 2;
|
||||
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
pcm.numChannels = 2;
|
||||
|
||||
int compatible_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, compatible_formats);
|
||||
pcm.bitsPerSample = 0;
|
||||
for (int i = 0; compatible_formats[i] && !pcm.bitsPerSample; ++i)
|
||||
for (int j = 0; fmtmap[j][0]; ++j)
|
||||
if (compatible_formats[i] == fmtmap[j][0]) {
|
||||
ao->format = fmtmap[j][0];
|
||||
pcm.bitsPerSample = fmtmap[j][1];
|
||||
break;
|
||||
}
|
||||
if (!pcm.bitsPerSample) {
|
||||
MP_ERR(ao, "Cannot find compatible audio format\n");
|
||||
goto error;
|
||||
}
|
||||
pcm.containerSize = 8 * af_fmt_to_bytes(ao->format);
|
||||
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
|
||||
if (p->cfg_sample_rate)
|
||||
ao->samplerate = p->cfg_sample_rate;
|
||||
|
||||
// samplesPerSec is misnamed, actually it's samples per ms
|
||||
pcm.samplesPerSec = ao->samplerate * 1000;
|
||||
|
||||
if (p->cfg_frames_per_buffer)
|
||||
ao->device_buffer = p->cfg_frames_per_buffer;
|
||||
else
|
||||
ao->device_buffer = ao->samplerate * DEFAULT_BUFFER_SIZE_MS / 1000;
|
||||
p->buffer_size = ao->device_buffer * ao->channels.num *
|
||||
af_fmt_to_bytes(ao->format);
|
||||
p->buf1 = calloc(1, p->buffer_size);
|
||||
p->buf2 = calloc(1, p->buffer_size);
|
||||
p->curbuf = p->buf1;
|
||||
if (!p->buf1 || !p->buf2) {
|
||||
MP_ERR(ao, "Failed to allocate device buffer\n");
|
||||
goto error;
|
||||
}
|
||||
int r = pthread_mutex_init(&p->buffer_lock, NULL);
|
||||
if (r) {
|
||||
MP_ERR(ao, "Failed to initialize the mutex: %d\n", r);
|
||||
goto error;
|
||||
}
|
||||
|
||||
audio_source.pFormat = (void*)&pcm;
|
||||
audio_source.pLocator = (void*)&locator_buffer_queue;
|
||||
|
||||
locator_output_mix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
|
||||
locator_output_mix.outputMix = p->output_mix;
|
||||
|
||||
audio_sink.pLocator = (void*)&locator_output_mix;
|
||||
audio_sink.pFormat = NULL;
|
||||
|
||||
SLboolean required[] = { SL_BOOLEAN_TRUE };
|
||||
SLInterfaceID iid_array[] = { SL_IID_BUFFERQUEUE };
|
||||
CHK((*p->engine)->CreateAudioPlayer(p->engine, &p->player, &audio_source,
|
||||
&audio_sink, 1, iid_array, required));
|
||||
CHK((*p->player)->Realize(p->player, SL_BOOLEAN_FALSE));
|
||||
CHK((*p->player)->GetInterface(p->player, SL_IID_PLAY, (void*)&p->play));
|
||||
CHK((*p->player)->GetInterface(p->player, SL_IID_BUFFERQUEUE,
|
||||
(void*)&p->buffer_queue));
|
||||
CHK((*p->buffer_queue)->RegisterCallback(p->buffer_queue,
|
||||
buffer_callback, ao));
|
||||
|
||||
return 1;
|
||||
error:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#undef CHK
|
||||
|
||||
static void set_play_state(struct ao *ao, SLuint32 state)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
SLresult res = (*p->play)->SetPlayState(p->play, state);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
MP_ERR(ao, "Failed to SetPlayState(%d): %d\n", state, res);
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
set_play_state(ao, SL_PLAYSTATE_STOPPED);
|
||||
}
|
||||
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
set_play_state(ao, SL_PLAYSTATE_PLAYING);
|
||||
|
||||
// enqueue two buffers
|
||||
buffer_callback(p->buffer_queue, ao);
|
||||
buffer_callback(p->buffer_queue, ao);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_opensles = {
|
||||
.description = "OpenSL ES audio output",
|
||||
.name = "opensles",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
.resume = resume,
|
||||
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("frames-per-buffer", cfg_frames_per_buffer, 0, 1, 10000),
|
||||
OPT_INTRANGE("sample-rate", cfg_sample_rate, 0, 1000, 100000),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
+33
-22
@@ -376,22 +376,8 @@ fail:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
static bool set_format(struct ao *ao, pa_format_info *format)
|
||||
{
|
||||
struct pa_channel_map map;
|
||||
pa_proplist *proplist = NULL;
|
||||
pa_format_info *format = NULL;
|
||||
struct priv *priv = ao->priv;
|
||||
char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : ao->device;
|
||||
|
||||
if (pa_init_boilerplate(ao) < 0)
|
||||
return -1;
|
||||
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
|
||||
if (!(format = pa_format_info_new()))
|
||||
goto unlock_and_fail;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
format->encoding = map_digital_format(ao->format);
|
||||
@@ -411,8 +397,29 @@ static int init(struct ao *ao)
|
||||
pa_format_info_set_sample_format(format, fmt_map->pa_format);
|
||||
}
|
||||
|
||||
struct pa_channel_map map;
|
||||
|
||||
if (!select_chmap(ao, &map))
|
||||
goto unlock_and_fail;
|
||||
return false;
|
||||
|
||||
pa_format_info_set_rate(format, ao->samplerate);
|
||||
pa_format_info_set_channels(format, ao->channels.num);
|
||||
pa_format_info_set_channel_map(format, &map);
|
||||
|
||||
return ao->samplerate < PA_RATE_MAX && pa_format_info_valid(format);
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
pa_proplist *proplist = NULL;
|
||||
pa_format_info *format = NULL;
|
||||
struct priv *priv = ao->priv;
|
||||
char *sink = priv->cfg_sink && priv->cfg_sink[0] ? priv->cfg_sink : ao->device;
|
||||
|
||||
if (pa_init_boilerplate(ao) < 0)
|
||||
return -1;
|
||||
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
|
||||
if (!(proplist = pa_proplist_new())) {
|
||||
MP_ERR(ao, "Failed to allocate proplist\n");
|
||||
@@ -420,13 +427,17 @@ static int init(struct ao *ao)
|
||||
}
|
||||
(void)pa_proplist_sets(proplist, PA_PROP_MEDIA_ICON_NAME, ao->client_name);
|
||||
|
||||
pa_format_info_set_rate(format, ao->samplerate);
|
||||
pa_format_info_set_channels(format, ao->channels.num);
|
||||
pa_format_info_set_channel_map(format, &map);
|
||||
|
||||
if (!pa_format_info_valid(format)) {
|
||||
MP_ERR(ao, "Invalid audio format\n");
|
||||
if (!(format = pa_format_info_new()))
|
||||
goto unlock_and_fail;
|
||||
|
||||
if (!set_format(ao, format)) {
|
||||
ao->channels = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
|
||||
ao->samplerate = 48000;
|
||||
ao->format = AF_FORMAT_FLOAT;
|
||||
if (!set_format(ao, format)) {
|
||||
MP_ERR(ao, "Invalid audio format\n");
|
||||
goto unlock_and_fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!(priv->stream = pa_stream_new_extended(priv->context, "audio stream",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
#include <unistd.h>
|
||||
#include <rsound.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
+8
-8
@@ -4,23 +4,23 @@
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 "config.h"
|
||||
#include "audio/format.h"
|
||||
#include "talloc.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "common/common.h"
|
||||
|
||||
+300
-222
@@ -3,63 +3,78 @@
|
||||
*
|
||||
* Original author: Jonathan Yong <10walls@gmail.com>
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <inttypes.h>
|
||||
#include <process.h>
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <avrt.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#include "audio/out/ao_wasapi.h"
|
||||
#include "audio/out/ao_wasapi_utils.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/io.h"
|
||||
#include "misc/dispatch.h"
|
||||
#include "ao_wasapi.h"
|
||||
|
||||
static HRESULT get_device_delay(struct wasapi_state *state, double *delay) {
|
||||
// naive av_rescale for unsigned
|
||||
static UINT64 uint64_scale(UINT64 x, UINT64 num, UINT64 den)
|
||||
{
|
||||
return (x / den) * num
|
||||
+ ((x % den) * (num / den))
|
||||
+ ((x % den) * (num % den)) / den;
|
||||
}
|
||||
|
||||
static HRESULT get_device_delay(struct wasapi_state *state, double *delay_us) {
|
||||
UINT64 sample_count = atomic_load(&state->sample_count);
|
||||
UINT64 position, qpc_position;
|
||||
HRESULT hr;
|
||||
|
||||
hr = IAudioClock_GetPosition(state->pAudioClock, &position, &qpc_position);
|
||||
/* GetPosition succeeded, but the result may be inaccurate due to the length of the call */
|
||||
/* http://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx */
|
||||
if (hr == S_FALSE) {
|
||||
MP_DBG(state, "Possibly inaccurate device position.\n");
|
||||
hr = S_OK;
|
||||
}
|
||||
EXIT_ON_ERROR(hr);
|
||||
// GetPosition succeeded, but the result may be
|
||||
// inaccurate due to the length of the call
|
||||
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd370889%28v=vs.85%29.aspx
|
||||
if (hr == S_FALSE)
|
||||
MP_VERBOSE(state, "Possibly inaccurate device position.\n");
|
||||
|
||||
LARGE_INTEGER qpc_count;
|
||||
QueryPerformanceCounter(&qpc_count);
|
||||
double qpc_diff = (qpc_count.QuadPart * 1e7 / state->qpc_frequency.QuadPart) - qpc_position;
|
||||
// convert position to number of samples careful to avoid overflow
|
||||
UINT64 sample_position = uint64_scale(position,
|
||||
state->format.Format.nSamplesPerSec,
|
||||
state->clock_frequency);
|
||||
INT64 diff = sample_count - sample_position;
|
||||
*delay_us = diff * 1e6 / state->format.Format.nSamplesPerSec;
|
||||
|
||||
position += state->clock_frequency * (uint64_t) (qpc_diff / 1e7);
|
||||
// Correct for any delay in IAudioClock_GetPosition above.
|
||||
// This should normally be very small (<1 us), but just in case. . .
|
||||
LARGE_INTEGER qpc;
|
||||
QueryPerformanceCounter(&qpc);
|
||||
INT64 qpc_diff = av_rescale(qpc.QuadPart, 10000000, state->qpc_frequency.QuadPart)
|
||||
- qpc_position;
|
||||
// ignore the above calculation if it yields more than 10 seconds (due to
|
||||
// possible overflow inside IAudioClock_GetPosition)
|
||||
if (qpc_diff < 10 * 10000000) {
|
||||
*delay_us -= qpc_diff / 10.0; // convert to us
|
||||
} else {
|
||||
MP_VERBOSE(state, "Insane qpc delay correction of %g seconds. "
|
||||
"Ignoring it.\n", qpc_diff / 10000000.0);
|
||||
}
|
||||
|
||||
/* convert position to the same base as sample_count */
|
||||
position = position * state->format.Format.nSamplesPerSec / state->clock_frequency;
|
||||
|
||||
double diff = sample_count - position;
|
||||
*delay = diff / state->format.Format.nSamplesPerSec;
|
||||
|
||||
MP_TRACE(state, "Device delay: %g samples (%g ms)\n", diff, *delay * 1000);
|
||||
if (sample_count > 0 && *delay_us <= 0) {
|
||||
MP_WARN(state, "Under-run: Device delay: %g us\n", *delay_us);
|
||||
} else {
|
||||
MP_TRACE(state, "Device delay: %g us\n", *delay_us);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
exit_label:
|
||||
@@ -67,24 +82,44 @@ exit_label:
|
||||
return hr;
|
||||
}
|
||||
|
||||
static void thread_feed(struct ao *ao)
|
||||
static bool thread_feed(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
HRESULT hr;
|
||||
|
||||
UINT32 frame_count = state->bufferFrameCount;
|
||||
|
||||
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
UINT32 padding = 0;
|
||||
hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
frame_count -= padding;
|
||||
MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n", frame_count, padding);
|
||||
}
|
||||
double delay;
|
||||
hr = get_device_delay(state, &delay);
|
||||
UINT32 padding;
|
||||
hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding);
|
||||
EXIT_ON_ERROR(hr);
|
||||
bool refill = false;
|
||||
if (state->share_mode == AUDCLNT_SHAREMODE_SHARED) {
|
||||
// Return if there's nothing to do.
|
||||
if (frame_count <= padding)
|
||||
return false;
|
||||
// In shared mode, there is only one buffer of size bufferFrameCount.
|
||||
// We must therefore take care not to overwrite the samples that have
|
||||
// yet to play.
|
||||
frame_count -= padding;
|
||||
} else if (padding >= 2 * frame_count) {
|
||||
// In exclusive mode, we exchange entire buffers of size
|
||||
// bufferFrameCount with the device. If there are already two such
|
||||
// full buffers waiting to play, there is no work to do.
|
||||
return false;
|
||||
} else if (padding < frame_count) {
|
||||
// If there is not at least one full buffer of audio queued to play in
|
||||
// exclusive mode, call this function again immediately to try and catch
|
||||
// up and avoid a cascade of under-runs. WASAPI doesn't seem to be smart
|
||||
// enough to send more feed events when it gets behind.
|
||||
refill = true;
|
||||
}
|
||||
MP_TRACE(ao, "Frame to fill: %"PRIu32". Padding: %"PRIu32"\n",
|
||||
frame_count, padding);
|
||||
|
||||
double delay_us;
|
||||
hr = get_device_delay(state, &delay_us);
|
||||
EXIT_ON_ERROR(hr);
|
||||
// add the buffer delay
|
||||
delay_us += frame_count * 1e6 / state->format.Format.nSamplesPerSec;
|
||||
|
||||
BYTE *pData;
|
||||
hr = IAudioRenderClient_GetBuffer(state->pRenderClient,
|
||||
@@ -93,50 +128,23 @@ static void thread_feed(struct ao *ao)
|
||||
|
||||
BYTE *data[1] = {pData};
|
||||
|
||||
ao_read_data(ao, (void**)data, frame_count, (int64_t) (
|
||||
mp_time_us() + delay * 1e6 +
|
||||
frame_count * 1e6 / state->format.Format.nSamplesPerSec));
|
||||
ao_read_data(ao, (void **)data, frame_count,
|
||||
mp_time_us() + (int64_t)llrint(delay_us));
|
||||
|
||||
// note, we can't use ao_read_data return value here since we already
|
||||
// commited to frame_count above in the GetBuffer call
|
||||
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
|
||||
frame_count, 0);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
atomic_fetch_add(&state->sample_count, frame_count);
|
||||
|
||||
return;
|
||||
return refill;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error feeding audio: %s\n", mp_HRESULT_to_str(hr));
|
||||
MP_VERBOSE(ao, "Requesting ao reload\n");
|
||||
ao_request_reload(ao);
|
||||
return;
|
||||
}
|
||||
|
||||
static void thread_resume(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
HRESULT hr;
|
||||
|
||||
MP_DBG(state, "Thread Resume\n");
|
||||
UINT32 padding = 0;
|
||||
hr = IAudioClient_GetCurrentPadding(state->pAudioClient, &padding);
|
||||
if (hr != S_OK) {
|
||||
MP_ERR(state, "IAudioClient_GetCurrentPadding returned %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
}
|
||||
|
||||
/* Fill the buffer before starting, but only if there is no audio queued to play. */
|
||||
/* This prevents overfilling the buffer, which leads to problems in exclusive mode */
|
||||
if (padding < (UINT32) state->bufferFrameCount)
|
||||
thread_feed(ao);
|
||||
|
||||
// start feeding next wakeup if something else hasn't been requested
|
||||
int expected = WASAPI_THREAD_RESUME;
|
||||
atomic_compare_exchange_strong(&state->thread_state, &expected, WASAPI_THREAD_FEED);
|
||||
hr = IAudioClient_Start(state->pAudioClient);
|
||||
if (hr != S_OK)
|
||||
MP_ERR(state, "IAudioClient_Start returned %s\n", mp_HRESULT_to_str(hr));
|
||||
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void thread_reset(struct ao *ao)
|
||||
@@ -145,20 +153,43 @@ static void thread_reset(struct ao *ao)
|
||||
HRESULT hr;
|
||||
MP_DBG(state, "Thread Reset\n");
|
||||
hr = IAudioClient_Stop(state->pAudioClient);
|
||||
/* we may get S_FALSE if the stream is already stopped */
|
||||
if (hr != S_OK && hr != S_FALSE)
|
||||
if (FAILED(hr))
|
||||
MP_ERR(state, "IAudioClient_Stop returned: %s\n", mp_HRESULT_to_str(hr));
|
||||
|
||||
/* we may get S_FALSE if the stream is already reset */
|
||||
hr = IAudioClient_Reset(state->pAudioClient);
|
||||
if (hr != S_OK && hr != S_FALSE)
|
||||
if (FAILED(hr))
|
||||
MP_ERR(state, "IAudioClient_Reset returned: %s\n", mp_HRESULT_to_str(hr));
|
||||
|
||||
atomic_store(&state->sample_count, 0);
|
||||
// start feeding next wakeup if something else hasn't been requested
|
||||
int expected = WASAPI_THREAD_RESET;
|
||||
atomic_compare_exchange_strong(&state->thread_state, &expected, WASAPI_THREAD_FEED);
|
||||
return;
|
||||
}
|
||||
|
||||
static void thread_resume(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
MP_DBG(state, "Thread Resume\n");
|
||||
thread_reset(ao);
|
||||
thread_feed(ao);
|
||||
|
||||
HRESULT hr = IAudioClient_Start(state->pAudioClient);
|
||||
if (FAILED(hr)) {
|
||||
MP_ERR(state, "IAudioClient_Start returned %s\n",
|
||||
mp_HRESULT_to_str(hr));
|
||||
}
|
||||
}
|
||||
|
||||
static void thread_wakeup(void *ptr)
|
||||
{
|
||||
struct ao *ao = ptr;
|
||||
struct wasapi_state *state = ao->priv;
|
||||
SetEvent(state->hWake);
|
||||
}
|
||||
|
||||
static void set_thread_state(struct ao *ao,
|
||||
enum wasapi_thread_state thread_state)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
atomic_store(&state->thread_state, thread_state);
|
||||
thread_wakeup(ao);
|
||||
}
|
||||
|
||||
static DWORD __stdcall AudioThread(void *lpParameter)
|
||||
@@ -169,43 +200,38 @@ static DWORD __stdcall AudioThread(void *lpParameter)
|
||||
|
||||
state->init_ret = wasapi_thread_init(ao);
|
||||
SetEvent(state->hInitDone);
|
||||
if (state->init_ret != S_OK)
|
||||
if (FAILED(state->init_ret))
|
||||
goto exit_label;
|
||||
|
||||
MP_DBG(ao, "Entering dispatch loop\n");
|
||||
while (true) { /* watch events */
|
||||
HANDLE events[] = {state->hWake};
|
||||
switch (MsgWaitForMultipleObjects(MP_ARRAY_SIZE(events), events, FALSE, INFINITE,
|
||||
QS_POSTMESSAGE | QS_SENDMESSAGE)) {
|
||||
/* AudioThread wakeup */
|
||||
case WAIT_OBJECT_0:
|
||||
switch (atomic_load(&state->thread_state)) {
|
||||
case WASAPI_THREAD_FEED:
|
||||
thread_feed(ao);
|
||||
break;
|
||||
case WASAPI_THREAD_RESET:
|
||||
thread_reset(ao);
|
||||
break;
|
||||
case WASAPI_THREAD_RESUME:
|
||||
thread_reset(ao);
|
||||
thread_resume(ao);
|
||||
break;
|
||||
case WASAPI_THREAD_SHUTDOWN:
|
||||
thread_reset(ao);
|
||||
goto exit_label;
|
||||
default:
|
||||
MP_ERR(ao, "Unhandled thread state\n");
|
||||
goto exit_label;
|
||||
}
|
||||
while (true) {
|
||||
if (WaitForSingleObject(state->hWake, INFINITE) != WAIT_OBJECT_0)
|
||||
MP_ERR(ao, "Unexpected return value from WaitForSingleObject\n");
|
||||
|
||||
mp_dispatch_queue_process(state->dispatch, 0);
|
||||
|
||||
int thread_state = atomic_load(&state->thread_state);
|
||||
switch (thread_state) {
|
||||
case WASAPI_THREAD_FEED:
|
||||
// fill twice on under-full buffer (see comment in thread_feed)
|
||||
if (thread_feed(ao) && thread_feed(ao))
|
||||
MP_ERR(ao, "Unable to fill buffer fast enough\n");
|
||||
break;
|
||||
/* messages to dispatch (COM marshalling) */
|
||||
case (WAIT_OBJECT_0 + MP_ARRAY_SIZE(events)):
|
||||
wasapi_dispatch(ao);
|
||||
case WASAPI_THREAD_RESET:
|
||||
thread_reset(ao);
|
||||
break;
|
||||
default:
|
||||
MP_ERR(ao, "Unhandled thread event\n");
|
||||
case WASAPI_THREAD_RESUME:
|
||||
thread_resume(ao);
|
||||
break;
|
||||
case WASAPI_THREAD_SHUTDOWN:
|
||||
thread_reset(ao);
|
||||
goto exit_label;
|
||||
default:
|
||||
MP_ERR(ao, "Unhandled thread state: %d\n", thread_state);
|
||||
}
|
||||
// the default is to feed unless something else is requested
|
||||
atomic_compare_exchange_strong(&state->thread_state, &thread_state,
|
||||
WASAPI_THREAD_FEED);
|
||||
}
|
||||
exit_label:
|
||||
wasapi_thread_uninit(ao);
|
||||
@@ -215,33 +241,28 @@ exit_label:
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_thread_state(struct ao *ao, enum wasapi_thread_state thread_state)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
atomic_store(&state->thread_state, thread_state);
|
||||
SetEvent(state->hWake);
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
MP_DBG(ao, "Uninit wasapi\n");
|
||||
struct wasapi_state *state = ao->priv;
|
||||
wasapi_release_proxies(state);
|
||||
if (state->hWake)
|
||||
set_thread_state(ao, WASAPI_THREAD_SHUTDOWN);
|
||||
|
||||
/* wait up to 10 seconds */
|
||||
if (state->hAudioThread &&
|
||||
WaitForSingleObject(state->hAudioThread, 10000) == WAIT_TIMEOUT)
|
||||
WaitForSingleObject(state->hAudioThread, INFINITE) != WAIT_OBJECT_0)
|
||||
{
|
||||
MP_ERR(ao, "Audio loop thread refuses to abort\n");
|
||||
return;
|
||||
MP_ERR(ao, "Unexpected return value from WaitForSingleObject "
|
||||
"while waiting for audio thread to terminate\n");
|
||||
}
|
||||
|
||||
SAFE_RELEASE(state->hInitDone, CloseHandle(state->hInitDone));
|
||||
SAFE_RELEASE(state->hWake, CloseHandle(state->hWake));
|
||||
SAFE_RELEASE(state->hAudioThread,CloseHandle(state->hAudioThread));
|
||||
|
||||
wasapi_change_uninit(ao);
|
||||
|
||||
talloc_free(state->deviceID);
|
||||
|
||||
CoUninitialize();
|
||||
MP_DBG(ao, "Uninit wasapi done\n");
|
||||
}
|
||||
@@ -254,114 +275,176 @@ static int init(struct ao *ao)
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
|
||||
state->hInitDone = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hWake = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
if (!state->hInitDone || !state->hWake) {
|
||||
MP_ERR(ao, "Error creating events\n");
|
||||
state->deviceID = wasapi_find_deviceID(ao);
|
||||
if (!state->deviceID) {
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wasapi_change_init(ao, false);
|
||||
|
||||
state->hInitDone = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->hWake = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
if (!state->hInitDone || !state->hWake) {
|
||||
MP_FATAL(ao, "Error creating events\n");
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
state->dispatch = mp_dispatch_create(state);
|
||||
mp_dispatch_set_wakeup_fn(state->dispatch, thread_wakeup, ao);
|
||||
|
||||
state->init_ret = E_FAIL;
|
||||
state->hAudioThread = CreateThread(NULL, 0, &AudioThread, ao, 0, NULL);
|
||||
if (!state->hAudioThread) {
|
||||
MP_ERR(ao, "Failed to create audio thread\n");
|
||||
MP_FATAL(ao, "Failed to create audio thread\n");
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
WaitForSingleObject(state->hInitDone, INFINITE); /* wait on init complete */
|
||||
WaitForSingleObject(state->hInitDone, INFINITE); // wait on init complete
|
||||
SAFE_RELEASE(state->hInitDone,CloseHandle(state->hInitDone));
|
||||
if (state->init_ret != S_OK) {
|
||||
if (FAILED(state->init_ret)) {
|
||||
if (!ao->probing)
|
||||
MP_ERR(ao, "Received failure from audio thread\n");
|
||||
MP_FATAL(ao, "Received failure from audio thread\n");
|
||||
uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
wasapi_setup_proxies(state);
|
||||
MP_DBG(ao, "Init wasapi done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int thread_control_exclusive(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
if (!state->pEndpointVolume)
|
||||
return CONTROL_UNKNOWN;
|
||||
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
if (!(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_VOLUME))
|
||||
return CONTROL_FALSE;
|
||||
break;
|
||||
case AOCONTROL_GET_MUTE:
|
||||
case AOCONTROL_SET_MUTE:
|
||||
if (!(state->vol_hw_support & ENDPOINT_HARDWARE_SUPPORT_MUTE))
|
||||
return CONTROL_FALSE;
|
||||
break;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_FALSE;
|
||||
}
|
||||
|
||||
float volume;
|
||||
BOOL mute;
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
IAudioEndpointVolume_GetMasterVolumeLevelScalar(
|
||||
state->pEndpointVolume, &volume);
|
||||
*(ao_control_vol_t *)arg = (ao_control_vol_t){
|
||||
.left = 100.0f * volume,
|
||||
.right = 100.0f * volume,
|
||||
};
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
volume = ((ao_control_vol_t *)arg)->left / 100.f;
|
||||
IAudioEndpointVolume_SetMasterVolumeLevelScalar(
|
||||
state->pEndpointVolume, volume, NULL);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_GET_MUTE:
|
||||
IAudioEndpointVolume_GetMute(state->pEndpointVolume, &mute);
|
||||
*(bool *)arg = mute;
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_MUTE:
|
||||
mute = *(bool *)arg;
|
||||
IAudioEndpointVolume_SetMute(state->pEndpointVolume, mute, NULL);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int thread_control_shared(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
if (!state->pAudioVolume)
|
||||
return CONTROL_UNKNOWN;
|
||||
|
||||
float volume;
|
||||
BOOL mute;
|
||||
switch(cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolume, &volume);
|
||||
*(ao_control_vol_t *)arg = (ao_control_vol_t){
|
||||
.left = 100.0f * volume,
|
||||
.right = 100.0f * volume,
|
||||
};
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
volume = ((ao_control_vol_t *)arg)->left / 100.f;
|
||||
ISimpleAudioVolume_SetMasterVolume(state->pAudioVolume, volume, NULL);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_GET_MUTE:
|
||||
ISimpleAudioVolume_GetMute(state->pAudioVolume, &mute);
|
||||
*(bool *)arg = mute;
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_MUTE:
|
||||
mute = *(bool *)arg;
|
||||
ISimpleAudioVolume_SetMute(state->pAudioVolume, mute, NULL);
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int thread_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
|
||||
// common to exclusive and shared
|
||||
switch (cmd) {
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE:
|
||||
if (!state->pSessionControl)
|
||||
return CONTROL_FALSE;
|
||||
|
||||
wchar_t *title = mp_from_utf8(NULL, (char*)arg);
|
||||
wchar_t *tmp = NULL;
|
||||
// There is a weird race condition in the IAudioSessionControl itself --
|
||||
// it seems that *sometimes* the SetDisplayName does not take effect and
|
||||
// it still shows the old title. Use this loop to insist until it works.
|
||||
do {
|
||||
IAudioSessionControl_SetDisplayName(state->pSessionControl, title, NULL);
|
||||
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
IAudioSessionControl_GetDisplayName(state->pSessionControl, &tmp);
|
||||
} while (lstrcmpW(title, tmp));
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
talloc_free(title);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
return state->share_mode == AUDCLNT_SHAREMODE_EXCLUSIVE ?
|
||||
thread_control_exclusive(ao, cmd, arg) :
|
||||
thread_control_shared(ao, cmd, arg);
|
||||
}
|
||||
|
||||
static void run_control(void *p)
|
||||
{
|
||||
void **pp = p;
|
||||
struct ao *ao = pp[0];
|
||||
enum aocontrol cmd = *(enum aocontrol *)pp[1];
|
||||
void *arg = pp[2];
|
||||
*(int *)pp[3] = thread_control(ao, cmd, arg);
|
||||
}
|
||||
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
ao_control_vol_t *vol = arg;
|
||||
BOOL mute;
|
||||
|
||||
switch (cmd) {
|
||||
case AOCONTROL_GET_VOLUME:
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_GetMasterVolumeLevelScalar(state->pEndpointVolumeProxy,
|
||||
&state->audio_volume);
|
||||
else
|
||||
ISimpleAudioVolume_GetMasterVolume(state->pAudioVolumeProxy,
|
||||
&state->audio_volume);
|
||||
|
||||
/* check to see if user manually changed volume through mixer;
|
||||
this information is used in exclusive mode for restoring the mixer volume on uninit */
|
||||
if (state->audio_volume != state->previous_volume) {
|
||||
MP_VERBOSE(state, "Mixer difference: %.2g now, expected %.2g\n",
|
||||
state->audio_volume, state->previous_volume);
|
||||
state->initial_volume = state->audio_volume;
|
||||
}
|
||||
|
||||
vol->left = vol->right = 100.0f * state->audio_volume;
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_VOLUME:
|
||||
state->audio_volume = vol->left / 100.f;
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_SetMasterVolumeLevelScalar(state->pEndpointVolumeProxy,
|
||||
state->audio_volume, NULL);
|
||||
else
|
||||
ISimpleAudioVolume_SetMasterVolume(state->pAudioVolumeProxy,
|
||||
state->audio_volume, NULL);
|
||||
|
||||
state->previous_volume = state->audio_volume;
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_GET_MUTE:
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_GetMute(state->pEndpointVolumeProxy, &mute);
|
||||
else
|
||||
ISimpleAudioVolume_GetMute(state->pAudioVolumeProxy, &mute);
|
||||
*(bool*)arg = mute;
|
||||
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_SET_MUTE:
|
||||
mute = *(bool*)arg;
|
||||
if (state->opt_exclusive)
|
||||
IAudioEndpointVolume_SetMute(state->pEndpointVolumeProxy, mute, NULL);
|
||||
else
|
||||
ISimpleAudioVolume_SetMute(state->pAudioVolumeProxy, mute, NULL);
|
||||
|
||||
return CONTROL_OK;
|
||||
case AOCONTROL_HAS_PER_APP_VOLUME:
|
||||
return state->share_mode == AUDCLNT_SHAREMODE_SHARED ?
|
||||
CONTROL_TRUE : CONTROL_FALSE;
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
||||
MP_VERBOSE(state, "Updating stream title to \"%s\"\n", (char*)arg);
|
||||
wchar_t *title = mp_from_utf8(NULL, (char*)arg);
|
||||
|
||||
wchar_t *tmp = NULL;
|
||||
|
||||
/* There is a weird race condition in the IAudioSessionControl itself --
|
||||
it seems that *sometimes* the SetDisplayName does not take effect and it still shows
|
||||
the old title. Use this loop to insist until it works. */
|
||||
do {
|
||||
IAudioSessionControl_SetDisplayName(state->pSessionControlProxy, title, NULL);
|
||||
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
IAudioSessionControl_GetDisplayName(state->pSessionControlProxy, &tmp);
|
||||
} while (lstrcmpW(title, tmp));
|
||||
SAFE_RELEASE(tmp, CoTaskMemFree(tmp));
|
||||
talloc_free(title);
|
||||
|
||||
return CONTROL_OK;
|
||||
}
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
int ret;
|
||||
void *p[] = {ao, &cmd, arg, &ret};
|
||||
mp_dispatch_run(state->dispatch, run_control, p);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void audio_reset(struct ao *ao)
|
||||
@@ -377,9 +460,7 @@ static void audio_resume(struct ao *ao)
|
||||
static void hotplug_uninit(struct ao *ao)
|
||||
{
|
||||
MP_DBG(ao, "Hotplug uninit\n");
|
||||
struct wasapi_state *state = ao->priv;
|
||||
wasapi_change_uninit(ao);
|
||||
SAFE_RELEASE(state->pEnumerator, IMMDeviceEnumerator_Release(state->pEnumerator));
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
@@ -389,15 +470,12 @@ static int hotplug_init(struct ao *ao)
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator, (void **)&state->pEnumerator);
|
||||
EXIT_ON_ERROR(hr);
|
||||
hr = wasapi_change_init(ao, true);
|
||||
HRESULT hr = wasapi_change_init(ao, true);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
return 0;
|
||||
exit_label:
|
||||
MP_ERR(state, "Error setting up audio hotplug: %s\n", mp_HRESULT_to_str(hr));
|
||||
MP_FATAL(state, "Error setting up audio hotplug: %s\n", mp_HRESULT_to_str(hr));
|
||||
hotplug_uninit(ao);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Executable → Regular
+59
-54
@@ -3,34 +3,41 @@
|
||||
*
|
||||
* Original author: Jonathan Yong <10walls@gmail.com>
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_AO_WASAPI_H_
|
||||
#define MP_AO_WASAPI_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <windows.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <audioclient.h>
|
||||
#include <audiopolicy.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <avrt.h>
|
||||
#include <endpointvolume.h>
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/windows_utils.h"
|
||||
#include "internal.h"
|
||||
#include "ao.h"
|
||||
|
||||
typedef struct change_notify {
|
||||
IMMNotificationClient client; /* this must be first in the structure! */
|
||||
LPWSTR monitored; /* Monitored device */
|
||||
IMMNotificationClient client; // this must be first in the structure!
|
||||
IMMDeviceEnumerator *pEnumerator; // object where client is registered
|
||||
LPWSTR monitored; // Monitored device
|
||||
bool is_hotplug;
|
||||
struct ao *ao;
|
||||
} change_notify;
|
||||
@@ -52,64 +59,62 @@ enum wasapi_thread_state {
|
||||
|
||||
typedef struct wasapi_state {
|
||||
struct mp_log *log;
|
||||
/* Init phase */
|
||||
HRESULT init_ret;
|
||||
HANDLE hInitDone;
|
||||
int share_mode;
|
||||
|
||||
/* volume control */
|
||||
DWORD vol_hw_support;
|
||||
float audio_volume;
|
||||
float previous_volume;
|
||||
float initial_volume;
|
||||
// Thread handles
|
||||
HRESULT init_ret; // status of init phase
|
||||
HANDLE hInitDone; // set when init is complete in audio thread
|
||||
HANDLE hAudioThread; // the audio thread itself
|
||||
HANDLE hWake; // thread wakeup event
|
||||
atomic_int thread_state; // enum wasapi_thread_state (what to do on wakeup)
|
||||
struct mp_dispatch_queue *dispatch; // for volume/mute/session display
|
||||
|
||||
/* WASAPI handles, owned by audio thread */
|
||||
// for setting the audio thread priority
|
||||
HANDLE hTask;
|
||||
|
||||
// ID of the device to use
|
||||
LPWSTR deviceID;
|
||||
// WASAPI object handles owned and used by audio thread
|
||||
IMMDevice *pDevice;
|
||||
IAudioClient *pAudioClient;
|
||||
IAudioRenderClient *pRenderClient;
|
||||
ISimpleAudioVolume *pAudioVolume;
|
||||
IAudioEndpointVolume *pEndpointVolume;
|
||||
IAudioSessionControl *pSessionControl;
|
||||
IMMDeviceEnumerator *pEnumerator;
|
||||
|
||||
/* thread handles */
|
||||
HANDLE hAudioThread; /* the thread itself */
|
||||
HANDLE hWake; /* thread wakeup event */
|
||||
atomic_int thread_state; /* enum wasapi_thread_state */
|
||||
|
||||
/* for setting the audio thread priority */
|
||||
HANDLE hTask; /* AV thread */
|
||||
|
||||
/* WASAPI proxy handles, for Single-Threaded Apartment communication.
|
||||
One is needed for each audio thread object that's accessed from the main thread. */
|
||||
ISimpleAudioVolume *pAudioVolumeProxy;
|
||||
IAudioEndpointVolume *pEndpointVolumeProxy;
|
||||
IAudioSessionControl *pSessionControlProxy;
|
||||
|
||||
/* Streams used to marshal the proxy objects. The thread owning the actual objects
|
||||
needs to marshal proxy objects into these streams, and the thread that wants the
|
||||
proxies unmarshals them from here. */
|
||||
IStream *sAudioVolume;
|
||||
IStream *sEndpointVolume;
|
||||
IStream *sSessionControl;
|
||||
|
||||
/* WASAPI internal clock information, for estimating delay */
|
||||
// WASAPI internal clock information, for estimating delay
|
||||
IAudioClock *pAudioClock;
|
||||
UINT64 clock_frequency; /* scale for the "samples" returned by the clock */
|
||||
atomic_ullong sample_count; /* the amount of samples per channel written to a GetBuffer buffer */
|
||||
LARGE_INTEGER qpc_frequency; /* frequency of windows' high resolution timer */
|
||||
atomic_ullong sample_count; // samples per channel written by GetBuffer
|
||||
UINT64 clock_frequency; // scale for position returned by GetPosition
|
||||
LARGE_INTEGER qpc_frequency; // frequency of Windows' high resolution timer
|
||||
|
||||
/* ao options */
|
||||
// WASAPI control
|
||||
IAudioSessionControl *pSessionControl; // setting the stream title
|
||||
IAudioEndpointVolume *pEndpointVolume; // exclusive mode volume/mute
|
||||
ISimpleAudioVolume *pAudioVolume; // shared mode volume/mute
|
||||
DWORD vol_hw_support; // is hardware volume supported for exclusive-mode?
|
||||
|
||||
// ao options
|
||||
int opt_exclusive;
|
||||
int opt_list;
|
||||
char *opt_device;
|
||||
|
||||
/* format info */
|
||||
// format info
|
||||
WAVEFORMATEXTENSIBLE format;
|
||||
size_t buffer_block_size; /* Size of each block in bytes */
|
||||
UINT32 bufferFrameCount; /* wasapi buffer block size, number of frames, frame size at format.nBlockAlign */
|
||||
AUDCLNT_SHAREMODE share_mode; // AUDCLNT_SHAREMODE_EXCLUSIVE / SHARED
|
||||
UINT32 bufferFrameCount; // number of frames in buffer
|
||||
|
||||
change_notify change;
|
||||
} wasapi_state;
|
||||
|
||||
char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey);
|
||||
#define mp_PKEY_to_str(pkey) mp_PKEY_to_str_buf((char[42]){0}, 42, (pkey))
|
||||
|
||||
void wasapi_list_devs(struct ao *ao, struct ao_device_list *list);
|
||||
bstr wasapi_get_specified_device_string(struct ao *ao);
|
||||
LPWSTR wasapi_find_deviceID(struct ao *ao);
|
||||
|
||||
void wasapi_dispatch(struct ao *ao);
|
||||
HRESULT wasapi_thread_init(struct ao *ao);
|
||||
void wasapi_thread_uninit(struct ao *ao);
|
||||
|
||||
void wasapi_receive_proxies(wasapi_state *state);
|
||||
void wasapi_release_proxies(wasapi_state *state);
|
||||
|
||||
#endif
|
||||
Executable → Regular
+43
-43
@@ -3,34 +3,28 @@
|
||||
*
|
||||
* Original author: Jonathan Yong <10walls@gmail.com>
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <wchar.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "ao_wasapi.h"
|
||||
#include "ao_wasapi_utils.h"
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface(
|
||||
IMMNotificationClient* This, REFIID riid, void **ppvObject)
|
||||
{
|
||||
/* Compatible with IMMNotificationClient and IUnknown */
|
||||
// Compatible with IMMNotificationClient and IUnknown
|
||||
if (IsEqualGUID(&IID_IMMNotificationClient, riid) ||
|
||||
IsEqualGUID(&IID_IUnknown, riid))
|
||||
{
|
||||
@@ -42,14 +36,14 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_QueryInterface(
|
||||
}
|
||||
}
|
||||
|
||||
/* these are required, but not actually used */
|
||||
// these are required, but not actually used
|
||||
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_AddRef(
|
||||
IMMNotificationClient *This)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* MSDN says it should free itself, but we're static */
|
||||
// MSDN says it should free itself, but we're static
|
||||
static ULONG STDMETHODCALLTYPE sIMMNotificationClient_Release(
|
||||
IMMNotificationClient *This)
|
||||
{
|
||||
@@ -65,7 +59,8 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceStateChanged(
|
||||
struct ao *ao = change->ao;
|
||||
|
||||
if (change->is_hotplug) {
|
||||
MP_VERBOSE(ao, "OnDeviceStateChanged triggered: sending hotplug event\n");
|
||||
MP_VERBOSE(ao,
|
||||
"OnDeviceStateChanged triggered: sending hotplug event\n");
|
||||
ao_hotplug_event(ao);
|
||||
} else if (pwstrDeviceId && !wcscmp(pwstrDeviceId, change->monitored)) {
|
||||
switch (dwNewState) {
|
||||
@@ -99,7 +94,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* maybe MPV can go over to the prefered device once it is plugged in? */
|
||||
// maybe MPV can go over to the prefered device once it is plugged in?
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
@@ -127,31 +122,33 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDefaultDeviceChanged(
|
||||
{
|
||||
change_notify *change = (change_notify *)This;
|
||||
struct ao *ao = change->ao;
|
||||
struct wasapi_state *state = ao->priv;
|
||||
|
||||
/* don't care about "eCapture" or non-"eMultimedia" roles */
|
||||
// don't care about "eCapture" or non-"eMultimedia" roles
|
||||
if (flow == eCapture || role != eMultimedia) return S_OK;
|
||||
|
||||
if (change->is_hotplug) {
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: sending hotplug event\n");
|
||||
MP_VERBOSE(ao,
|
||||
"OnDefaultDeviceChanged triggered: sending hotplug event\n");
|
||||
ao_hotplug_event(ao);
|
||||
} else {
|
||||
/* stay on the device the user specified */
|
||||
if (state->opt_device) {
|
||||
// stay on the device the user specified
|
||||
bstr device = wasapi_get_specified_device_string(ao);
|
||||
if (device.len) {
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: "
|
||||
"staying on specified device %s\n", state->opt_device);
|
||||
"staying on specified device %.*s\n", BSTR_P(device));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* don't reload if already on the new default */
|
||||
// don't reload if already on the new default
|
||||
if (pwstrDeviceId && !wcscmp(pwstrDeviceId, change->monitored)) {
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: "
|
||||
"already using default device, no reload required\n");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* if we got here, we need to reload */
|
||||
MP_VERBOSE(ao, "OnDefaultDeviceChanged triggered: requesting ao reload\n");
|
||||
// if we got here, we need to reload
|
||||
MP_VERBOSE(ao,
|
||||
"OnDefaultDeviceChanged triggered: requesting ao reload\n");
|
||||
ao_request_reload(ao);
|
||||
}
|
||||
|
||||
@@ -184,7 +181,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnPropertyValueChanged(
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
static CONST_VTBL IMMNotificationClientVtbl sIMMDeviceEnumeratorVtbl_vtbl = {
|
||||
static CONST_VTBL IMMNotificationClientVtbl sIMMNotificationClientVtbl = {
|
||||
.QueryInterface = sIMMNotificationClient_QueryInterface,
|
||||
.AddRef = sIMMNotificationClient_AddRef,
|
||||
.Release = sIMMNotificationClient_Release,
|
||||
@@ -200,27 +197,30 @@ HRESULT wasapi_change_init(struct ao *ao, bool is_hotplug)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
struct change_notify *change = &state->change;
|
||||
HRESULT hr;
|
||||
/* COM voodoo to emulate c++ class */
|
||||
change->client.lpVtbl = &sIMMDeviceEnumeratorVtbl_vtbl;
|
||||
|
||||
/* register the change notification client */
|
||||
hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
|
||||
state->pEnumerator, (IMMNotificationClient *)change);
|
||||
HRESULT hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
|
||||
&IID_IMMDeviceEnumerator,
|
||||
(void **)&change->pEnumerator);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
/* so the callbacks can access the ao */
|
||||
// COM voodoo to emulate c++ class
|
||||
change->client.lpVtbl = &sIMMNotificationClientVtbl;
|
||||
|
||||
// register the change notification client
|
||||
hr = IMMDeviceEnumerator_RegisterEndpointNotificationCallback(
|
||||
change->pEnumerator, (IMMNotificationClient *)change);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
// so the callbacks can access the ao
|
||||
change->ao = ao;
|
||||
|
||||
/* whether or not this is the hotplug instance */
|
||||
// whether or not this is the hotplug instance
|
||||
change->is_hotplug = is_hotplug;
|
||||
|
||||
if (is_hotplug) {
|
||||
MP_DBG(ao, "Monitoring for hotplug events\n");
|
||||
} else {
|
||||
/* Get the device string to compare with the pwstrDeviceId */
|
||||
hr = IMMDevice_GetId(state->pDevice, &change->monitored);
|
||||
EXIT_ON_ERROR(hr);
|
||||
// Get the device string to compare with the pwstrDeviceId
|
||||
change->monitored = state->deviceID;
|
||||
MP_VERBOSE(ao, "Monitoring changes in device %S\n", change->monitored);
|
||||
}
|
||||
|
||||
@@ -237,10 +237,10 @@ void wasapi_change_uninit(struct ao *ao)
|
||||
struct wasapi_state *state = ao->priv;
|
||||
struct change_notify *change = &state->change;
|
||||
|
||||
if (state->pEnumerator && change->client.lpVtbl) {
|
||||
if (change->pEnumerator && change->client.lpVtbl) {
|
||||
IMMDeviceEnumerator_UnregisterEndpointNotificationCallback(
|
||||
state->pEnumerator, (IMMNotificationClient *)change);
|
||||
change->pEnumerator, (IMMNotificationClient *)change);
|
||||
}
|
||||
|
||||
if (change->monitored) CoTaskMemFree(change->monitored);
|
||||
SAFE_RELEASE(change->pEnumerator, IMMDeviceEnumerator_Release(change->pEnumerator));
|
||||
}
|
||||
Executable → Regular
+341
-517
File diff suppressed because it is too large.
Load diff
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* Original author: Jonathan Yong <10walls@gmail.com>
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_AO_WASAPI_UTILS_H_
|
||||
#define MP_AO_WASAPI_UTILS_H_
|
||||
|
||||
#include "audio/out/ao_wasapi.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
|
||||
char *mp_GUID_to_str_buf(char *buf, size_t buf_size, const GUID *guid);
|
||||
char *mp_PKEY_to_str_buf(char *buf, size_t buf_size, const PROPERTYKEY *pkey);
|
||||
char *mp_HRESULT_to_str_buf(char *buf, size_t buf_size, HRESULT hr);
|
||||
#define mp_GUID_to_str(guid) mp_GUID_to_str_buf((char[40]){0}, 40, (guid))
|
||||
#define mp_PKEY_to_str(pkey) mp_PKEY_to_str_buf((char[42]){0}, 42, (pkey))
|
||||
#define mp_HRESULT_to_str(hr) mp_HRESULT_to_str_buf((char[60]){0}, 60, (hr))
|
||||
#define mp_LastError_to_str() mp_HRESULT_to_str(HRESULT_FROM_WIN32(GetLastError()))
|
||||
|
||||
bool wasapi_fill_VistaBlob(wasapi_state *state);
|
||||
|
||||
void wasapi_list_devs(struct ao *ao, struct ao_device_list *list);
|
||||
|
||||
void wasapi_dispatch(struct ao *ao);
|
||||
HRESULT wasapi_thread_init(struct ao *ao);
|
||||
void wasapi_thread_uninit(struct ao *ao);
|
||||
|
||||
HRESULT wasapi_setup_proxies(wasapi_state *state);
|
||||
void wasapi_release_proxies(wasapi_state *state);
|
||||
|
||||
#endif
|
||||
+8
-8
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <stddef.h>
|
||||
@@ -113,7 +113,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
// rest of the user-provided buffer with silence.
|
||||
// This basically assumes that the audio device doesn't care about underruns.
|
||||
// If this is called in paused mode, it will always return 0.
|
||||
// The caller should set out_time_us to the expected delay the last sample
|
||||
// The caller should set out_time_us to the expected delay until the last sample
|
||||
// reaches the speakers, in microseconds, using mp_time_us() as reference.
|
||||
int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
|
||||
{
|
||||
|
||||
+7
-7
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <stddef.h>
|
||||
|
||||
+44
-8
@@ -1,18 +1,18 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
* mpv is 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 General Public License for more details.
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
* 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 <assert.h>
|
||||
@@ -25,9 +25,12 @@
|
||||
#include <libavutil/cpu.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "av_common.h"
|
||||
#include "codecs.h"
|
||||
|
||||
@@ -68,6 +71,19 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
|
||||
avctx->has_b_frames = st->has_b_frames;
|
||||
}
|
||||
|
||||
// This only copies ffmpeg-native codec parameters. Parameters produced by
|
||||
// other demuxers must be handled manually.
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
{
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (c->lav_codecpar)
|
||||
avcodec_parameters_to_context(avctx, c->lav_codecpar);
|
||||
#else
|
||||
if (c->lav_headers)
|
||||
mp_copy_lav_codec_headers(avctx, c->lav_headers);
|
||||
#endif
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
union pts { int64_t i; double d; };
|
||||
|
||||
@@ -144,6 +160,11 @@ void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads
|
||||
avctx->thread_count = threads;
|
||||
}
|
||||
|
||||
static bool is_crap(AVCodec *codec)
|
||||
{
|
||||
return !!strstr(codec->name, "_vdpau");
|
||||
}
|
||||
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
|
||||
{
|
||||
AVCodec *cur = NULL;
|
||||
@@ -151,7 +172,22 @@ void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
|
||||
cur = av_codec_next(cur);
|
||||
if (!cur)
|
||||
break;
|
||||
if (av_codec_is_decoder(cur) && cur->type == type) {
|
||||
if (av_codec_is_decoder(cur) && cur->type == type && !is_crap(cur)) {
|
||||
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
|
||||
cur->name, cur->long_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (Abuses the decoder list data structures.)
|
||||
void mp_add_lavc_encoders(struct mp_decoder_list *list)
|
||||
{
|
||||
AVCodec *cur = NULL;
|
||||
for (;;) {
|
||||
cur = av_codec_next(cur);
|
||||
if (!cur)
|
||||
break;
|
||||
if (av_codec_is_encoder(cur)) {
|
||||
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
|
||||
cur->name, cur->long_name);
|
||||
}
|
||||
|
||||
Loaded 100 of 388 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user