Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8c57433b9 | ||
|
|
ead2933792 | ||
|
|
79f8ee2808 | ||
|
|
e3e6c1939f | ||
|
|
4d747400bb | ||
|
|
8838c4a5b1 | ||
|
|
a75e933dac | ||
|
|
ab7ac46bcc | ||
|
|
7a19eb490e | ||
|
|
8782354e6d | ||
|
|
f287ccf017 | ||
|
|
2f4e01e772 | ||
|
|
9d2bc9a4ac | ||
|
|
3586e6ceeb | ||
|
|
21e5e4da4b | ||
|
|
bad743aed5 | ||
|
|
126367bb09 | ||
|
|
1e53308d65 | ||
|
|
fea1e09a9c | ||
|
|
7020908691 | ||
|
|
876e93d8b7 | ||
|
|
4e0e24c3c2 | ||
|
|
f095e86b61 | ||
|
|
c2ee824c26 | ||
|
|
d96f6edf49 | ||
|
|
e76f503fff | ||
|
|
1557d2d470 | ||
|
|
7eacfdcd25 | ||
|
|
09c61e0a45 | ||
|
|
b7144ad8bf | ||
|
|
269f271b73 | ||
|
|
fdf6e5f5aa | ||
|
|
e9822f6012 | ||
|
|
e721660e6d | ||
|
|
60a617df31 | ||
|
|
e45f469280 | ||
|
|
dc04541ba8 | ||
|
|
9e04e31906 | ||
|
|
a8eae12af5 | ||
|
|
e2139488ff | ||
|
|
f1205293a7 | ||
|
|
2492b5f119 | ||
|
|
d3bd5c9541 | ||
|
|
b4abcbd19d | ||
|
|
97363e176d | ||
|
|
95d5bee832 | ||
|
|
eb56807b41 | ||
|
|
7929e36e93 | ||
|
|
44eda2177d | ||
|
|
deebc55014 | ||
|
|
944fa1214a | ||
|
|
37544e53dc | ||
|
|
f622f9d3a3 | ||
|
|
ab6f6aa61e | ||
|
|
af0b903afa | ||
|
|
4eae4a5da7 | ||
|
|
0eb72d786c | ||
|
|
8a9c9e0ede | ||
|
|
cd32179627 | ||
|
|
f3b00ec142 | ||
|
|
7c73f70b89 | ||
|
|
392ae68e5f | ||
|
|
a6694b7b96 | ||
|
|
23f6f3f50c | ||
|
|
215afe7a41 | ||
|
|
d04d2380e3 | ||
|
|
091bfa3abf | ||
|
|
181e1377b2 | ||
|
|
e1fbd3b790 | ||
|
|
496d53a2f2 | ||
|
|
90efe329b9 | ||
|
|
61b902f5a7 | ||
|
|
11f44a10f9 | ||
|
|
3f5715a053 | ||
|
|
418af6f0cb | ||
|
|
79beb60c54 | ||
|
|
ebf80c07e3 | ||
|
|
8ffbb22c27 | ||
|
|
4241120363 | ||
|
|
3a5d769d4a | ||
|
|
a8e6de90fe | ||
|
|
eabc530945 | ||
|
|
62378b394a | ||
|
|
9be10f3bf1 | ||
|
|
d60270ed3d | ||
|
|
e6fd6d6e49 | ||
|
|
fb94744595 | ||
|
|
82f0d373fb | ||
|
|
061b947c84 | ||
|
|
638322f77e | ||
|
|
c80b7eed53 | ||
|
|
f14f6fdb31 | ||
|
|
2cfa62e651 | ||
|
|
ed0bc8b64f | ||
|
|
ef5f4b4d9a | ||
|
|
c61675320b | ||
|
|
3c86bd2bb5 | ||
|
|
ba384fffca | ||
|
|
dae464a491 | ||
|
|
f1778d1f5b | ||
|
|
6238491688 | ||
|
|
cf94fce467 | ||
|
|
1ec1b4186c | ||
|
|
2172c22ee3 | ||
|
|
a48a8a746e | ||
|
|
3bbcbc15a5 | ||
|
|
63dc0085c8 | ||
|
|
fb5368b159 | ||
|
|
5c3196d20b | ||
|
|
602105dbda | ||
|
|
1e4113040c | ||
|
|
0e2024ef2b | ||
|
|
8565073f68 | ||
|
|
2e3ce738f5 | ||
|
|
8a9fc9398d | ||
|
|
d29e699491 | ||
|
|
e05dc7bfb7 | ||
|
|
3245bfefc3 | ||
|
|
b144da63a8 | ||
|
|
9cd929bf79 | ||
|
|
addbf8faae | ||
|
|
e1fd80097c | ||
|
|
96648169e3 | ||
|
|
147f4956d3 | ||
|
|
895c8801a2 | ||
|
|
0d5541af47 | ||
|
|
1b93a7a895 | ||
|
|
b8fe17f2e6 | ||
|
|
3bceb37b06 | ||
|
|
bffd78748f | ||
|
|
cab1f6439c | ||
|
|
d0e19b659e | ||
|
|
2dac2efce4 | ||
|
|
1b7883a3e5 | ||
|
|
4427fa9900 | ||
|
|
594071dbf4 | ||
|
|
5eb6466e54 | ||
|
|
58ba2a9087 | ||
|
|
c36c596b7b | ||
|
|
da8a9ff589 | ||
|
|
cf2fa9d3e5 | ||
|
|
bf5eac8dd3 | ||
|
|
41d7989800 | ||
|
|
6894858bf2 | ||
|
|
2b280f4522 | ||
|
|
00b60710cf | ||
|
|
a6e33b4290 | ||
|
|
70c990cda5 | ||
|
|
baeead7181 | ||
|
|
a6a585d470 | ||
|
|
0ba44d5fef | ||
|
|
ed09e78fb9 | ||
|
|
b989bb96d1 | ||
|
|
a17d5e4bdd | ||
|
|
828881816a | ||
|
|
c7329e5118 | ||
|
|
b103a8e113 | ||
|
|
533e151feb | ||
|
|
f6a354e846 | ||
|
|
2d68a2e55a | ||
|
|
266735a7a2 | ||
|
|
e77bc570d7 | ||
|
|
031555fbe6 | ||
|
|
fedaad8250 | ||
|
|
3d1cc17ab2 | ||
|
|
8f2d9db79f | ||
|
|
5a52fe47a0 | ||
|
|
8d66bd76e2 | ||
|
|
853ff000c3 | ||
|
|
dfd8a5ff06 | ||
|
|
91b9dcf800 | ||
|
|
e9182c5c7c | ||
|
|
fbb22039f3 | ||
|
|
27a17be325 | ||
|
|
62aba69e15 | ||
|
|
dd5c87e1d7 | ||
|
|
60794e2359 | ||
|
|
8c388ce79c | ||
|
|
70e0bc1e4a | ||
|
|
d6c99bcda2 | ||
|
|
caebbded67 | ||
|
|
9bcc882659 | ||
|
|
d1179f9501 | ||
|
|
beb4f8316a | ||
|
|
775d816096 | ||
|
|
410553aaaa | ||
|
|
5aae68610e | ||
|
|
0c23325b3a | ||
|
|
0cd70b2d62 | ||
|
|
31b5a211f4 | ||
|
|
417e256c21 | ||
|
|
e5d3180889 | ||
|
|
3ab6155d21 | ||
|
|
ae2f8fd0be | ||
|
|
fce864acec | ||
|
|
2d77818183 | ||
|
|
0b1c3e8de2 | ||
|
|
e7897dfb9b | ||
|
|
aca591ded3 | ||
|
|
75b1d5043f | ||
|
|
525ada8c7a | ||
|
|
c844d57489 | ||
|
|
1ef7820e6e | ||
|
|
f792f56440 | ||
|
|
59d44ecef1 | ||
|
|
c3803e3254 | ||
|
|
b8591a31e5 | ||
|
|
bbbb2d9723 | ||
|
|
aa0157d9b8 | ||
|
|
920cf8971e | ||
|
|
a74914a057 | ||
|
|
11f2be2bcc | ||
|
|
17fe9d7c0d | ||
|
|
d812599851 | ||
|
|
ee58e7cfe5 | ||
|
|
63ade8a49c | ||
|
|
e0c55cbfea | ||
|
|
efd7100962 | ||
|
|
5b14ceb315 | ||
|
|
41101c2996 | ||
|
|
fefac2c941 | ||
|
|
c8fab7cc7b | ||
|
|
c6b5645991 | ||
|
|
81bfe4da8f | ||
|
|
794dbd847a | ||
|
|
15f97f05b9 | ||
|
|
f2bba55e01 | ||
|
|
0b52ac8a78 | ||
|
|
7d889fbdd0 | ||
|
|
f15e652dd4 | ||
|
|
2cfd87ab15 | ||
|
|
b3468d53c7 | ||
|
|
3452f9aeac | ||
|
|
51ca8a4b3e | ||
|
|
5c8dd832bb | ||
|
|
1e91750c73 | ||
|
|
77a9265561 | ||
|
|
68d5e7a986 | ||
|
|
a3a453b69b | ||
|
|
616d1510a7 | ||
|
|
95b930f866 | ||
|
|
bdc60daffa | ||
|
|
9f65629bd0 | ||
|
|
c581804603 | ||
|
|
3cbf68b470 | ||
|
|
86a60a4dd2 | ||
|
|
281c21b7f1 | ||
|
|
5b47836082 | ||
|
|
b492720c7c | ||
|
|
55879a8c0f | ||
|
|
63d112746d | ||
|
|
aeb99f2718 | ||
|
|
a357d39369 | ||
|
|
e5fac76b3b | ||
|
|
4a1657da01 | ||
|
|
6f7d04be21 | ||
|
|
b11fd7fd2d | ||
|
|
3252d352c9 | ||
|
|
b0d1ac93cf | ||
|
|
253f6f1a95 | ||
|
|
8749900b5f | ||
|
|
2cf019ed70 | ||
|
|
47a62059a9 | ||
|
|
532508823c | ||
|
|
7984fde8de | ||
|
|
fb8d158366 | ||
|
|
050c529e9a | ||
|
|
57043d9269 | ||
|
|
5594333337 | ||
|
|
bad3996d85 | ||
|
|
7ba403002f | ||
|
|
ebdbb4f24c | ||
|
|
968bd3df3b | ||
|
|
bb9717a630 | ||
|
|
4730e0aaba | ||
|
|
15954c36ca | ||
|
|
7ef8f457a8 | ||
|
|
bc68794acc | ||
|
|
50bd2807ad | ||
|
|
e3e20f1431 | ||
|
|
d1c37c0e29 | ||
|
|
423a1a0f6c | ||
|
|
27708eee81 | ||
|
|
739d345d6c | ||
|
|
e1ab9b905f | ||
|
|
f3e8108c78 | ||
|
|
176470b906 | ||
|
|
905d5efded | ||
|
|
8771838408 | ||
|
|
57efe9089c | ||
|
|
0d35c78a6c | ||
|
|
9b15c99aa1 | ||
|
|
f052165dbf | ||
|
|
ba525e68c4 | ||
|
|
78e771554c | ||
|
|
4a4f788a68 | ||
|
|
898855cc0d | ||
|
|
5776b0c940 | ||
|
|
3007250824 | ||
|
|
4b0b9b515b | ||
|
|
8e82a64f56 | ||
|
|
c2b61876c4 | ||
|
|
59b64ceb79 | ||
|
|
140273cdec | ||
|
|
01ce203ed7 | ||
|
|
4c04f74a50 | ||
|
|
707fe102b6 | ||
|
|
4e76782630 | ||
|
|
a46de35abb | ||
|
|
731fc5b816 | ||
|
|
f3d06e3e91 | ||
|
|
d23d9dc394 | ||
|
|
23220db924 | ||
|
|
15581f2209 | ||
|
|
0be07e1d86 | ||
|
|
9aadc0199f | ||
|
|
ef7278b4fd | ||
|
|
d2c05cb559 | ||
|
|
db2268d5b1 | ||
|
|
4781f9e69a | ||
|
|
9620e37d6a | ||
|
|
0ebf5979d1 | ||
|
|
25755f5fe7 | ||
|
|
92727e7332 | ||
|
|
1d29177c5c | ||
|
|
dc33eb56f4 | ||
|
|
459124f66f | ||
|
|
daf32e2d2d | ||
|
|
782c7acd16 | ||
|
|
385febe276 | ||
|
|
dcd167ca37 | ||
|
|
7c032bde3e | ||
|
|
a56a7f3e8c | ||
|
|
e4b963e643 | ||
|
|
7b9d726588 | ||
|
|
34b223d730 | ||
|
|
95424517a2 | ||
|
|
a4d5c19355 | ||
|
|
5728295dab | ||
|
|
f544bcf105 | ||
|
|
029da5abce | ||
|
|
ed44f21289 | ||
|
|
31f2cd03d0 | ||
|
|
fc79fd0474 | ||
|
|
514af9fbd1 | ||
|
|
e9e323f35d | ||
|
|
65e3657bc4 | ||
|
|
74e2c8a6ef | ||
|
|
ae3e151b27 | ||
|
|
d4ab91f016 | ||
|
|
597657110f | ||
|
|
ec21be498f | ||
|
|
8a20e5306c | ||
|
|
561416597e | ||
|
|
b85321d057 | ||
|
|
b6dc11810b | ||
|
|
cc51dafa96 | ||
|
|
5446637e99 | ||
|
|
a609877f00 | ||
|
|
a9bbaa5eb2 | ||
|
|
899dfa957f | ||
|
|
ff25c0ad7d | ||
|
|
53845d81f5 | ||
|
|
dc2b6ab6b7 | ||
|
|
89713808ad | ||
|
|
c169292902 | ||
|
|
99bc049745 | ||
|
|
6afef5839f | ||
|
|
8dff03560a | ||
|
|
89f05dc7d1 | ||
|
|
e3d85ad46d | ||
|
|
03e062d53e | ||
|
|
8043063275 | ||
|
|
0739cfc209 | ||
|
|
f166d12985 | ||
|
|
c5e3613bba | ||
|
|
41ad9d8924 | ||
|
|
7faa80ace8 | ||
|
|
c0aba8e25a | ||
|
|
aa42b6ba35 | ||
|
|
dbf21467f0 | ||
|
|
bf739ca324 | ||
|
|
55a7ef9675 | ||
|
|
eaf1547afc | ||
|
|
2f8eabe216 | ||
|
|
302aaddc26 | ||
|
|
956b8658fb | ||
|
|
2e4f106ef8 | ||
|
|
8b479d2f66 | ||
|
|
a5b96226a0 | ||
|
|
ef3d121c9e | ||
|
|
e0b1b88e83 | ||
|
|
d660e67be9 | ||
|
|
d98cd6609d | ||
|
|
db3d36927d | ||
|
|
fccce81d42 | ||
|
|
745cc7a8cd | ||
|
|
fb1b8f93aa | ||
|
|
b2e1c91f40 | ||
|
|
26f52c5730 | ||
|
|
a8711001c5 | ||
|
|
dde2a5f0e8 | ||
|
|
85b7cbec70 | ||
|
|
1921f40db5 | ||
|
|
03c70a8d81 | ||
|
|
6ffb1e2b66 | ||
|
|
2a67208f40 | ||
|
|
0969502316 | ||
|
|
e0a7e094bc | ||
|
|
3b3170aedb | ||
|
|
d9b19390ad | ||
|
|
8a60283309 | ||
|
|
6147bcce35 | ||
|
|
d6737c5fab | ||
|
|
efb50cabe6 | ||
|
|
554b4217a0 | ||
|
|
e4e7fade96 | ||
|
|
b0176c1d24 | ||
|
|
d11184a256 | ||
|
|
cd6d846b70 | ||
|
|
8134a0601b | ||
|
|
3c61e6eb4e | ||
|
|
5a3cdb8f1e | ||
|
|
fd1194de3c | ||
|
|
5d71188c99 | ||
|
|
0906f758d3 | ||
|
|
e4c5876f57 | ||
|
|
ab2e3cf6f9 | ||
|
|
1b7ce759b1 | ||
|
|
a70c8105fc | ||
|
|
ef020c155f | ||
|
|
fcd589b123 | ||
|
|
ae2873f72e | ||
|
|
47919bdfc9 | ||
|
|
ff9c597fbf | ||
|
|
ddd6f5a9d7 | ||
|
|
68e7e89835 | ||
|
|
62269871aa | ||
|
|
4c6a600943 | ||
|
|
e7d5a5e688 | ||
|
|
cd78e0c5bf | ||
|
|
3d55340c6d | ||
|
|
17e8815e37 | ||
|
|
872b19dfcb | ||
|
|
dbbac7b3e1 | ||
|
|
0316f5514e | ||
|
|
be882175d8 | ||
|
|
c66be698cd | ||
|
|
2b64eee8d5 | ||
|
|
991af7dfb1 | ||
|
|
b17b8ff7fe | ||
|
|
23b83c6676 | ||
|
|
34fce974c6 | ||
|
|
4e1159c3f2 | ||
|
|
da9e0988a6 | ||
|
|
91fdfd3880 | ||
|
|
09eaaf3f2c | ||
|
|
b789acee21 | ||
|
|
f2cc6ce356 | ||
|
|
8b44be54e7 | ||
|
|
fd557a0178 | ||
|
|
f544cd0501 | ||
|
|
0641ec0525 | ||
|
|
aed7c84840 | ||
|
|
b1a56d11fe | ||
|
|
d328b2088c | ||
|
|
797277a233 | ||
|
|
0f0e88cbaa | ||
|
|
32955d3529 | ||
|
|
e00e9d651b | ||
|
|
a87b18aa5a | ||
|
|
f53135ffea | ||
|
|
d4aaf29a05 | ||
|
|
762623cdef | ||
|
|
72808be6f8 | ||
|
|
dbdc46c97a | ||
|
|
b2781c11ed | ||
|
|
552dc0d564 | ||
|
|
5a9f817bfd | ||
|
|
8ee9c170be | ||
|
|
831d7c3c40 | ||
|
|
488ebdb0d5 | ||
|
|
82ff32ffac | ||
|
|
30f5ba9422 | ||
|
|
74a73752c2 | ||
|
|
5eae20fc0f | ||
|
|
ec72feaba3 | ||
|
|
725d840b73 | ||
|
|
d50e01d0c6 | ||
|
|
fd88fb70af | ||
|
|
b33ab743e5 | ||
|
|
efa6d0c746 | ||
|
|
8d6c22def4 | ||
|
|
63e4cb5163 | ||
|
|
9909234abe | ||
|
|
6cc02658fa | ||
|
|
5b269ce696 | ||
|
|
afdc060bb3 | ||
|
|
55624a70ee | ||
|
|
433402b56c | ||
|
|
c890eeac47 | ||
|
|
11fee81a7a | ||
|
|
627b87b0d8 | ||
|
|
0d2456ae5f | ||
|
|
e53cb0890e | ||
|
|
87b60ded88 | ||
|
|
ce513dedd8 | ||
|
|
478ea1d0f3 | ||
|
|
d0fe5e08b9 | ||
|
|
b655ed5ed0 | ||
|
|
a3d561f950 | ||
|
|
7de2411c47 | ||
|
|
4a90b15f74 | ||
|
|
f51459c077 | ||
|
|
b7d833c2a6 | ||
|
|
211088943c | ||
|
|
a2b1c6d3f6 | ||
|
|
3399b775b7 | ||
|
|
bd64763076 | ||
|
|
9986593b50 | ||
|
|
8653ed2183 | ||
|
|
f72028ddad | ||
|
|
0dfbd29828 | ||
|
|
5dbd22e165 | ||
|
|
b694f480fe | ||
|
|
69abb48194 | ||
|
|
92b27bec45 | ||
|
|
f4ce99d082 | ||
|
|
fb7d0c80cc | ||
|
|
57048c7393 | ||
|
|
14ac4f0bd6 | ||
|
|
68aaffc711 | ||
|
|
2bf2254248 | ||
|
|
e0f0f6f3e9 | ||
|
|
650469d65b | ||
|
|
f30b28de46 | ||
|
|
54b6389873 | ||
|
|
b68627ed2b | ||
|
|
af58bfb62b | ||
|
|
c06cd1b993 | ||
|
|
b2d058ef00 | ||
|
|
fd96bddca9 | ||
|
|
935997d4d6 | ||
|
|
2dc46423d6 | ||
|
|
c277c17a93 | ||
|
|
73401f92ec | ||
|
|
38c74270c3 | ||
|
|
04d5bf4f0b | ||
|
|
e14df5c4f4 | ||
|
|
52bd61d698 | ||
|
|
e40b663da3 | ||
|
|
5a97ae2715 | ||
|
|
7556f367d6 | ||
|
|
7c0d3b9a50 | ||
|
|
87a94a5655 | ||
|
|
37d505f363 | ||
|
|
fe8634ea90 | ||
|
|
f14a3df778 | ||
|
|
ed805e16fc | ||
|
|
ebc5237c36 | ||
|
|
56c4cdf369 | ||
|
|
ebde784fab | ||
|
|
8e010a500d | ||
|
|
4c20e45561 | ||
|
|
99bc1df84d | ||
|
|
d91d24ba7e | ||
|
|
a075dfb52c | ||
|
|
3170473003 | ||
|
|
a3f7b38cbc | ||
|
|
47d841458d | ||
|
|
ed925c70c9 | ||
|
|
ad85203d63 | ||
|
|
f44725b8d4 | ||
|
|
5bea966836 | ||
|
|
20bc56b22d | ||
|
|
4763718728 | ||
|
|
d3894290ec | ||
|
|
327b091909 | ||
|
|
9fe6a8c5f5 | ||
|
|
6a5cbe7802 | ||
|
|
c4757ad17c | ||
|
|
6f5a10542c | ||
|
|
0699a6c598 | ||
|
|
a9b1e72ef2 | ||
|
|
e632e37ab8 | ||
|
|
939132cbd9 | ||
|
|
a2eb0ab076 | ||
|
|
0949ee347d | ||
|
|
ce0bf9692f | ||
|
|
eea625736f | ||
|
|
5a1ef9d8db | ||
|
|
dc2c7371f1 | ||
|
|
e8a1d35e9c | ||
|
|
ac6ebbbcbc | ||
|
|
ba45c410d3 | ||
|
|
acc4102ba2 | ||
|
|
b50390d5da | ||
|
|
b65c548b7d | ||
|
|
ae1ac475ac | ||
|
|
22af0dce0d | ||
|
|
13d4ee6d1c | ||
|
|
4d6b9550fe | ||
|
|
bf4dd877e9 | ||
|
|
2dd904289d | ||
|
|
39a339c813 | ||
|
|
51654d2ad5 | ||
|
|
3c24250c14 | ||
|
|
88249baf5b | ||
|
|
bdcd25d3c2 | ||
|
|
302901ddaf | ||
|
|
446a10020f | ||
|
|
289705daaf | ||
|
|
6bfbd4ebdc | ||
|
|
449cdfc0ff | ||
|
|
3fc3357d54 | ||
|
|
34875413fa | ||
|
|
05a75d0a76 | ||
|
|
f3dcd85150 | ||
|
|
58e7d0a30b | ||
|
|
0d0444fed0 | ||
|
|
9e77b8f06a | ||
|
|
36b1b84216 | ||
|
|
8054c034b8 | ||
|
|
a3f8d45fb6 | ||
|
|
fa7b22c687 | ||
|
|
f7ca2c83e1 | ||
|
|
ae46833836 | ||
|
|
a7c831f36d | ||
|
|
f9507fe927 | ||
|
|
1eaceb5fae | ||
|
|
ff0b33ac9d | ||
|
|
450af05338 | ||
|
|
d26803cba0 | ||
|
|
c2f9e3d2b3 | ||
|
|
17b3cab983 | ||
|
|
a165a61415 | ||
|
|
68bbab0e42 | ||
|
|
eb296d4fde | ||
|
|
8c795d7963 | ||
|
|
ac879545ad | ||
|
|
cbaa8d65fc | ||
|
|
83012fd77a | ||
|
|
7412995c94 | ||
|
|
292266f268 | ||
|
|
af667643a0 | ||
|
|
cf023e1f3a | ||
|
|
f07ef6087d | ||
|
|
8724e3e2dd | ||
|
|
9bd3ae311f | ||
|
|
0e78424842 | ||
|
|
3e0dae6959 | ||
|
|
402fe381d7 | ||
|
|
36529bc6f7 | ||
|
|
e57a44c341 | ||
|
|
4d2a01b776 | ||
|
|
0ed1719e1a | ||
|
|
1919f1e05b | ||
|
|
a6d3a6919a | ||
|
|
10915000fb | ||
|
|
0c84f6e820 | ||
|
|
e17a8550c4 | ||
|
|
be9bf4cc7c | ||
|
|
0d020c5c46 | ||
|
|
21cb1f7075 | ||
|
|
f4c412a057 | ||
|
|
aa78dff42b | ||
|
|
bc503a0e88 | ||
|
|
5d2a2c7fc2 | ||
|
|
f9ff4b59a2 | ||
|
|
a4f3df5970 | ||
|
|
06bfa9309d | ||
|
|
372b85b9d2 | ||
|
|
9251fa125f | ||
|
|
24f98d1aaf | ||
|
|
a2235b0280 | ||
|
|
bad932e8ed | ||
|
|
fa39dadb05 | ||
|
|
df97c30e0e | ||
|
|
10de9b091a | ||
|
|
27da344e6f | ||
|
|
5ab05f61ca | ||
|
|
d89eb74cb7 | ||
|
|
7e21f6fd00 | ||
|
|
0468a4ea47 | ||
|
|
a23a46c6ed | ||
|
|
5258c012fe | ||
|
|
5b085fd8b1 | ||
|
|
0bdef9979f | ||
|
|
fc9695e63b | ||
|
|
47d69f366b | ||
|
|
29eb764fe0 | ||
|
|
7735b29732 | ||
|
|
434343d634 | ||
|
|
6b7155c05b | ||
|
|
ee3de1a063 | ||
|
|
4d9255a5e1 | ||
|
|
af157db7e5 | ||
|
|
92b9d75d72 | ||
|
|
ca9964a4fb | ||
|
|
cc24ec5b3c | ||
|
|
e5573728c7 | ||
|
|
10149f68a5 | ||
|
|
a7ecb11ddd | ||
|
|
4858c47e1c | ||
|
|
a5aa58c63c | ||
|
|
47ee67b8a2 | ||
|
|
5d7468a06e | ||
|
|
9438f811ae | ||
|
|
27f7489adc | ||
|
|
0f063a5011 | ||
|
|
1f389b05fc | ||
|
|
5fe2c19029 | ||
|
|
e9963139da | ||
|
|
6d93dd8969 | ||
|
|
b6346cd0ba | ||
|
|
34ee78f2cb | ||
|
|
04c02796bd | ||
|
|
40997b8ae5 | ||
|
|
2263f37dfb | ||
|
|
5631060569 | ||
|
|
859ddc9906 | ||
|
|
51120c9c7f | ||
|
|
f9e2d5026e | ||
|
|
00130651da | ||
|
|
8d5924f2c9 | ||
|
|
8b7035c8ff | ||
|
|
3560a50029 | ||
|
|
d32b71d52e | ||
|
|
5142b0e3f3 | ||
|
|
ad9bce2a5c | ||
|
|
7b09654c33 | ||
|
|
55e777f10b | ||
|
|
b91b4944bd | ||
|
|
3477088741 | ||
|
|
036a49478f | ||
|
|
b12ca2b980 | ||
|
|
1bcb82ec93 | ||
|
|
6a0a67034d | ||
|
|
cd5ab98ff9 | ||
|
|
85fc6b2a05 | ||
|
|
d577872a28 | ||
|
|
9434aa75b2 | ||
|
|
f58d3591d9 | ||
|
|
0ae0e90eb5 | ||
|
|
4444ff48fa | ||
|
|
028739932b | ||
|
|
1e1045b13e | ||
|
|
32bc61ae07 | ||
|
|
e777756301 | ||
|
|
4ffcf2531b | ||
|
|
656703e279 | ||
|
|
86d65c80e1 | ||
|
|
0025030cef | ||
|
|
d76f9a484e | ||
|
|
934109a35b | ||
|
|
399267393b | ||
|
|
7a5f5a8adf | ||
|
|
bbedceb467 | ||
|
|
fd6809f98a | ||
|
|
66f4e7cce4 | ||
|
|
cf210c4ffc | ||
|
|
22455b2961 | ||
|
|
8121529a6c | ||
|
|
305a85cc9a | ||
|
|
f719b8164d | ||
|
|
19ab5f7943 | ||
|
|
4d8a7e0394 | ||
|
|
06050aed99 | ||
|
|
1b0b094ca2 | ||
|
|
937c8e513f | ||
|
|
548cd826c2 | ||
|
|
eead97f103 | ||
|
|
b6381a0ee3 | ||
|
|
08d3ef3d9e | ||
|
|
a2da53027b | ||
|
|
e25ecdd09a | ||
|
|
2ae96f567c | ||
|
|
6a03357b71 | ||
|
|
1982fedca6 | ||
|
|
3508a3fbd1 | ||
|
|
1e7831070f | ||
|
|
19a5b20752 | ||
|
|
d8e92322fa | ||
|
|
15d8cf7fa2 | ||
|
|
f509a2badb | ||
|
|
dce941b99c | ||
|
|
aeea250ab3 | ||
|
|
ff1b5432e7 | ||
|
|
a52a494fe4 | ||
|
|
dd2c65429f | ||
|
|
fd7bf9c07a | ||
|
|
b32a2bf364 | ||
|
|
01b793f117 | ||
|
|
1153f13bee | ||
|
|
47d46ec487 | ||
|
|
756991bd4f | ||
|
|
72373bb5ae | ||
|
|
8192500716 | ||
|
|
7381db60e2 | ||
|
|
6814830b9a | ||
|
|
d3a3cfe54c | ||
|
|
60958ddf9b | ||
|
|
94a3a76ee3 | ||
|
|
e185887ba0 | ||
|
|
0a7abbda6b | ||
|
|
e23e4c7c60 | ||
|
|
ffcad1a72b | ||
|
|
d01228058b | ||
|
|
8c7f3adb41 | ||
|
|
6ae66e717f | ||
|
|
382434d45a | ||
|
|
34a5229b23 | ||
|
|
32b835c03b | ||
|
|
3295ce48ab | ||
|
|
99439f11ea | ||
|
|
0600d378f9 | ||
|
|
d51dee093f | ||
|
|
f07c9b16f9 | ||
|
|
38114b6a36 | ||
|
|
daf4334697 | ||
|
|
0b72f5e5ad | ||
|
|
8b4ca58062 | ||
|
|
d5e9bf66a1 | ||
|
|
5f86fad2f0 | ||
|
|
025cb3b9e3 | ||
|
|
9795216d8a | ||
|
|
0c0c8cd44e | ||
|
|
0ff93a8357 | ||
|
|
c4aa136155 | ||
|
|
2fcf0e6183 | ||
|
|
f77e3cbf0c | ||
|
|
43b6b376dc | ||
|
|
5555f6fc2c | ||
|
|
d3c7fd9d7c | ||
|
|
570f4b136f | ||
|
|
d2292c179c | ||
|
|
4d2ed847ce | ||
|
|
0c70f94c53 | ||
|
|
1e2e504349 | ||
|
|
5f21a68ce9 | ||
|
|
95a0488ac2 | ||
|
|
a5ed6e49bf | ||
|
|
4df8c21f81 | ||
|
|
8998a4b3ed |
No files matched your search
+1
-1
@@ -47,7 +47,7 @@ notifications:
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#mpv-player-dev"
|
||||
- "irc.freenode.org#mpv-devel"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
|
||||
@@ -2,9 +2,15 @@ mpv is a fork of mplayer2, which is a fork of MPlayer.
|
||||
|
||||
mpv as a whole is licensed as GPL version 2 or later (see LICENSE). Most source
|
||||
files are GPLv2+, but some files are available under a more liberal license,
|
||||
such as LGPLv2+, BSD, MIT, ISC, and possibly others. Look at the copyright
|
||||
such as LGPLv2.1+, BSD, MIT, ISC, and possibly others. Look at the copyright
|
||||
header of each source file, and grep the sources for "Copyright" if you need
|
||||
to know details. Files without Copyright notice are licensed as LGPLv2+.
|
||||
to know details. Files without Copyright notice are licensed as LGPLv2.1+.
|
||||
|
||||
All new contributions must be LGPLv2.1+ licensed, or if the changes are done on
|
||||
GPL code, must come with the implicit agreement that the project can relicense
|
||||
the code to LGPLv2.1+ at a later point without asking the contributor. (This
|
||||
is a safeguard for making potential relicensing of the project to LGPLv2.1+
|
||||
easier.) Using a more liberal license compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
@@ -17,3 +23,5 @@ MPlayer as a whole is licensed under GPLv2 (incompatible to GPLv3!), because
|
||||
some files are licensed to GPLv2 (and _not_ any later version of the license).
|
||||
In particular, this affects the file libmpdemux/demux_ty_osd.c. It is disabled
|
||||
under mplayer2, and has been removed from mpv.
|
||||
|
||||
"v2.1+" in this context means "version 2.1 or later".
|
||||
+25
-10
@@ -4,9 +4,7 @@ Introduction
|
||||
This file lists all changes that can cause compatibility issues when using
|
||||
mpv through the client API (libmpv and ``client.h``). Since the client API
|
||||
interfaces to input handling (commands, properties) as well as command line
|
||||
options, this list is interesting for other uses of mpv, such as the Lua
|
||||
scripting interface, key bindings in ``input.rst``, or plain command line
|
||||
usage.
|
||||
options, you should also look at ``interface-changes.rst``.
|
||||
|
||||
Normally, changes to the C API that are incompatible to previous iterations
|
||||
receive a major version bump (i.e. the first version number is increased),
|
||||
@@ -20,21 +18,38 @@ The version number is the same as used for MPV_CLIENT_API_VERSION (see
|
||||
|
||||
Also, read the section ``Compatibility`` in ``client.h``.
|
||||
|
||||
Options, commands, properties
|
||||
=============================
|
||||
|
||||
Changes to these are not listed here, but in ``interface-changes.rst``. (Before
|
||||
client API version 1.17, they were listed here partially.)
|
||||
|
||||
This listing includes changes to the bare C API and behavior only, not what
|
||||
you can access with them.
|
||||
|
||||
API changes
|
||||
===========
|
||||
|
||||
::
|
||||
|
||||
1.17 - add MPV_FORMAT_BYTE_ARRAY
|
||||
--- mpv 0.9.0 will be released ---
|
||||
--- mpv 0.11.0 ---
|
||||
1.19 - add "GL_MP_D3D_interfaces" pseudo extension to make it possible to
|
||||
use DXVA2 in OpenGL fullscreen mode in some situations
|
||||
- mpv_request_log_messages() now accepts "terminal-default" as parameter
|
||||
1.18 - add MPV_END_FILE_REASON_REDIRECT, and change behavior of
|
||||
MPV_EVENT_END_FILE accordingly
|
||||
- a bunch of interface-changes.rst changes
|
||||
1.17 - mpv_initialize() now blocks SIGPIPE (details see client.h)
|
||||
--- mpv 0.9.0 ---
|
||||
1.16 - add mpv_opengl_cb_report_flip()
|
||||
- introduce mpv_opengl_cb_draw() and deprecate mpv_opengl_cb_render()
|
||||
- add MPV_FORMAT_BYTE_ARRAY
|
||||
1.15 - mpv_initialize() will now load config files. This requires setting
|
||||
the "config" and "config-dir" options. In particular, it will load
|
||||
mpv.conf.
|
||||
- minor backwards-compatible change to the "seek" and "screenshot"
|
||||
commands (new flag syntax, old additional args deprecated)
|
||||
--- mpv 0.8.0 is released ---
|
||||
--- mpv 0.8.0 ---
|
||||
1.14 - add mpv_wait_async_requests()
|
||||
- the --msg-level option changes its native type from a flat string to
|
||||
a key-value list (setting/reading the option as string still works)
|
||||
@@ -47,7 +62,7 @@ API changes
|
||||
its own and mpv's OpenGL rendering
|
||||
Warning: this API is not stable yet - anything in opengl_cb.h might
|
||||
be changed in completely incompatible ways in minor API bumps
|
||||
--- mpv 0.7.0 is released ---
|
||||
--- mpv 0.7.0 ---
|
||||
1.10 - deprecate/disable everything directly related to script_dispatch
|
||||
(most likely affects nobody)
|
||||
1.9 - add enum mpv_end_file_reason for mpv_event_end_file.reason
|
||||
@@ -70,7 +85,7 @@ API changes
|
||||
1.6 - modify "core-idle" property behavior
|
||||
- MPV_EVENT_LOG_MESSAGE now always sends complete lines
|
||||
- introduce numeric log levels (mpv_log_level)
|
||||
--- mpv 0.6.0 is released ---
|
||||
--- mpv 0.6.0 ---
|
||||
1.5 - change in X11 and "--wid" behavior again. The previous change didn't
|
||||
work as expected, and now the behavior can be explicitly controlled
|
||||
with the "input-x11-keyboard" option. This is only a temporary
|
||||
@@ -79,7 +94,7 @@ API changes
|
||||
although the old option name still works.
|
||||
1.4 - subtle change in X11 and "--wid" behavior
|
||||
(this change was added to 0.5.2, and broke some things, see #1090)
|
||||
--- mpv 0.5.0 is released ---
|
||||
--- mpv 0.5.0 ---
|
||||
1.3 - add MPV_MAKE_VERSION()
|
||||
1.2 - remove "stream-time-pos" property (no replacement)
|
||||
1.1 - remap dvdnav:// to dvd://
|
||||
@@ -91,6 +106,6 @@ API changes
|
||||
insignificant is now significant
|
||||
- add "cache-free" and "cache-used" properties
|
||||
- OSX: the "coreaudio" AO spdif code is split into a separate AO
|
||||
--- mpv 0.4.0 is released ---
|
||||
--- mpv 0.4.0 ---
|
||||
1.0 - the API is declared stable
|
||||
|
||||
@@ -24,6 +24,11 @@ qml_direct
|
||||
Alternative example, which typically avoids a FBO indirection. Might be
|
||||
slightly faster, but is less flexible and harder to use.
|
||||
|
||||
sdl
|
||||
---
|
||||
|
||||
Show how to embed the mpv OpenGL renderer in SDL.
|
||||
|
||||
simple
|
||||
------
|
||||
|
||||
|
||||
@@ -0,0 +1,304 @@
|
||||
// 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,9 +1,7 @@
|
||||
// Plays a video from the command line in a window provided by mpv.
|
||||
// You likely want to play the video in your own window instead,
|
||||
// but that's not quite ready yet.
|
||||
// You may need a basic Info.plist and MainMenu.xib to make this work.
|
||||
// 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`
|
||||
// Build with: clang -o cocoabasic cocoabasic.m `pkg-config --libs --cflags mpv` -framework cocoa
|
||||
|
||||
#include <mpv/client.h>
|
||||
|
||||
@@ -207,5 +205,5 @@ int main(int argc, const char * argv[]) {
|
||||
app.delegate = delegate;
|
||||
[app run];
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
return 0;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ QT += qml quick
|
||||
HEADERS += main.h
|
||||
SOURCES += main.cpp
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ QT += qml quick
|
||||
HEADERS += main.h
|
||||
SOURCES += main.cpp
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
TARGET = qtexample
|
||||
TEMPLATE = app
|
||||
|
||||
QT_CONFIG -= no-pkg-config
|
||||
CONFIG += link_pkgconfig debug
|
||||
PKGCONFIG += mpv
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -134,10 +134,10 @@ pacman -S git pkg-config python3 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-lua
|
||||
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-mpg123 mingw-w64-x86_64-libguess
|
||||
pacman -S mingw-w64-x86_64-libdvdnav mingw-w64-x86_64-libguess
|
||||
```
|
||||
|
||||
For a 32-bit build, install ``mingw-w64-i686-*`` packages instead.
|
||||
|
||||
+5
-2
@@ -4,8 +4,8 @@ How to contribute
|
||||
General
|
||||
-------
|
||||
|
||||
The main contact for mpv development is IRC, specifically #mpv-player
|
||||
and #mpv-player-dev on Freenode.
|
||||
The main contact for mpv development is IRC, specifically #mpv
|
||||
and #mpv-devel on Freenode.
|
||||
|
||||
Sending patches
|
||||
---------------
|
||||
@@ -54,6 +54,9 @@ Sending patches
|
||||
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).
|
||||
|
||||
Code formatting
|
||||
---------------
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
Introduction
|
||||
============
|
||||
|
||||
mpv provides access to its internal via the following means:
|
||||
|
||||
- options
|
||||
- commands
|
||||
- properties
|
||||
- events
|
||||
|
||||
All of these are important for interfacing both with end users and API users
|
||||
(which include Lua scripts, libmpv, and the JSON IPC). As such, they constitute
|
||||
a large part of the user interface and APIs.
|
||||
|
||||
This document lists changes to them. New changes are added to the top.
|
||||
|
||||
Interface changes
|
||||
=================
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.11.0 ---
|
||||
- add "af-metadata" property
|
||||
--- mpv 0.10.0 ---
|
||||
- add --video-aspect-method option
|
||||
- add --playlist-pos option
|
||||
- add --video-sync* options
|
||||
"display-sync-active" property
|
||||
"vo-missed-frame-count" property
|
||||
"audio-speed-correction" and "video-speed-correction" properties
|
||||
- remove --demuxer-readahead-packets and --demuxer-readahead-bytes
|
||||
add --demuxer-max-packets and --demuxer-max-bytes
|
||||
(the new options are not replacement and have very different semantics)
|
||||
- change "video-aspect" property: always settable, even if no video is
|
||||
running; always return the override - if no override is set, return
|
||||
the video's aspect ratio
|
||||
- remove disc-nav (DVD, BD) related properties and commands
|
||||
- add "option-info/<name>/set-locally" property
|
||||
- add --cache-backbuffer; change --cache-default default to 75MB
|
||||
the new total cache size is the sum of backbuffer and the cache size
|
||||
specified by --cache-default or --cache
|
||||
- add ``track-list/N/audio-channels`` property
|
||||
- change --screenshot-tag-colorspace default value
|
||||
- add --stretch-image-subs-to-screen
|
||||
- add "playlist/N/title" property
|
||||
- add --video-stereo-mode=no to disable auto-conversions
|
||||
- add --force-seekable, and change default seekability in some cases
|
||||
- add vf yadif/vavpp/vdpaupp interlaced-only suboptions
|
||||
Also, the option is enabled by default (Except vf_yadif, which has
|
||||
it enabled only if it's inserted by the deinterlace property.)
|
||||
- add --hwdec-preload
|
||||
- add ao coreaudio exclusive suboption
|
||||
- add ``track-list/N/forced`` property
|
||||
- add audio-params/channel-count and ``audio-params-out/channel-count props.
|
||||
- add af volume replaygain-fallback suboption
|
||||
- add video-params/stereo-in property
|
||||
- add "keypress", "keydown", and "keyup" commands
|
||||
- deprecate --ad-spdif-dtshd and enabling passthrough via --ad
|
||||
add --audio-spdif as replacement
|
||||
- remove "get_property" command
|
||||
- remove --slave-broken
|
||||
- add vo opengl custom shader suboptions (source-shader, scale-shader,
|
||||
pre-shaders, post-shaders)
|
||||
- completely change how the hwdec properties work:
|
||||
- "hwdec" now reflects the --hwdec option
|
||||
- "hwdec-detected" does partially what the old "hwdec" property did
|
||||
(and also, "detected-hwdec" is removed)
|
||||
- "hwdec-active" is added
|
||||
- add protocol-list property
|
||||
- deprecate audio-samplerate and audio-channels properties
|
||||
(audio-params sub-properties are the replacement)
|
||||
- add audio-params and audio-out-params properties
|
||||
- deprecate "audio-format" property, replaced with "audio-codec-name"
|
||||
- deprecate --media-title, replaced with --force-media-title
|
||||
- deprecate "length" property, replaced with "duration"
|
||||
- change volume property:
|
||||
- the value 100 is now always "unchanged volume" - with softvol, the
|
||||
range is 0 to --softvol-max, without it is 0-100
|
||||
- the minimum value of --softvol-max is raised to 100
|
||||
- remove vo opengl npot suboption
|
||||
- add relative seeking by percentage to "seek" command
|
||||
- add playlist_shuffle command
|
||||
- add --force-window=immediate
|
||||
- add ao coreaudio change-physical-format suboption
|
||||
- remove vo opengl icc-cache suboption, add icc-cache-dir suboption
|
||||
- add --screenshot-directory
|
||||
- add --screenshot-high-bit-depth
|
||||
- add --screenshot-jpeg-source-chroma
|
||||
- default action for "rescan_external_files" command changes
|
||||
--- mpv 0.9.0 ---
|
||||
+15
-187
@@ -11,6 +11,10 @@ syntax is:
|
||||
|
||||
To get a full list of available audio filters, see ``--af=help``.
|
||||
|
||||
Also, keep in mind that most actual filters are available via the ``lavfi``
|
||||
wrapper, which gives you access to most of libavfilter's filters. This
|
||||
includes all filters that have been ported from MPlayer to libavfilter.
|
||||
|
||||
You can also set defaults for each filter. The defaults are applied before the
|
||||
normal filter parameters.
|
||||
|
||||
@@ -59,6 +63,13 @@ 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.
|
||||
``o=<string>``
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
@@ -96,59 +107,6 @@ Available filters are:
|
||||
If the input channel number is less than ``<minch>``, the filter will
|
||||
detach itself (default: 3).
|
||||
|
||||
``sweep[=speed]``
|
||||
Produces a sine sweep.
|
||||
|
||||
``<0.0-1.0>``
|
||||
Sine function delta, use very low values to hear the sweep.
|
||||
|
||||
``sinesuppress[=freq:decay]``
|
||||
Remove a sine at the specified frequency. Useful to get rid of the 50/60 Hz
|
||||
noise on low quality audio equipment. It only works on mono input.
|
||||
|
||||
``<freq>``
|
||||
The frequency of the sine which should be removed (in Hz) (default:
|
||||
50)
|
||||
``<decay>``
|
||||
Controls the adaptivity (a larger value will make the filter adapt to
|
||||
amplitude and phase changes quicker, a smaller value will make the
|
||||
adaptation slower) (default: 0.0001). Reasonable values are around
|
||||
0.001.
|
||||
|
||||
``bs2b[=option1:option2:...]``
|
||||
Bauer stereophonic to binaural transformation using libbs2b. Improves the
|
||||
headphone listening experience by making the sound similar to that from
|
||||
loudspeakers, allowing each ear to hear both channels and taking into
|
||||
account the distance difference and the head shadowing effect. It is
|
||||
applicable only to 2-channel audio.
|
||||
|
||||
``fcut=<300-1000>``
|
||||
Set cut frequency in Hz.
|
||||
``feed=<10-150>``
|
||||
Set feed level for low frequencies in 0.1*dB.
|
||||
``profile=<value>``
|
||||
Several profiles are available for convenience:
|
||||
|
||||
:default: will be used if nothing else was specified (fcut=700,
|
||||
feed=45)
|
||||
:cmoy: Chu Moy circuit implementation (fcut=700, feed=60)
|
||||
:jmeier: Jan Meier circuit implementation (fcut=650, feed=95)
|
||||
|
||||
If ``fcut`` or ``feed`` options are specified together with a profile, they
|
||||
will be applied on top of the selected profile.
|
||||
|
||||
``hrtf[=flag]``
|
||||
Head-related transfer function: Converts multichannel audio to 2-channel
|
||||
output for headphones, preserving the spatiality of the sound.
|
||||
|
||||
==== ===================================
|
||||
Flag Meaning
|
||||
==== ===================================
|
||||
m matrix decoding of the rear channel
|
||||
s 2-channel matrix decoding
|
||||
0 no matrix decoding (default)
|
||||
==== ===================================
|
||||
|
||||
``equalizer=g1:g2:g3:...:g10``
|
||||
10 octave band graphic equalizer, implemented using 10 IIR band-pass
|
||||
filters. This means that it works regardless of what type of audio is
|
||||
@@ -263,13 +221,6 @@ Available filters are:
|
||||
used to do conversion itself, unlike this one which lets the filter system
|
||||
handle the conversion.
|
||||
|
||||
``convert24``
|
||||
Filter for internal use only. Converts between 24-bit and 32-bit sample
|
||||
formats.
|
||||
|
||||
``convertsign``
|
||||
Filter for internal use only. Converts between signed/unsigned formats.
|
||||
|
||||
``volume[=<volumedb>[:...]]``
|
||||
Implements software volume control. Use this filter with caution since it
|
||||
can reduce the signal to noise ratio of the sound. In most cases it is
|
||||
@@ -294,6 +245,10 @@ Available filters are:
|
||||
``replaygain-clip=yes|no``
|
||||
Prevent clipping caused by replaygain by automatically lowering the
|
||||
gain (default). Use ``replaygain-clip=no`` to disable this.
|
||||
``replaygain-fallback``
|
||||
Gain in dB to apply if the file has no replay gain tags. This option
|
||||
is always applied if the replaygain logic is somehow inactive. If this
|
||||
is applied, no other replaygain options are applied.
|
||||
``softclip``
|
||||
Turns soft clipping on. Soft-clipping can make the
|
||||
sound more smooth if very high volume levels are used. Enable this
|
||||
@@ -357,64 +312,6 @@ Available filters are:
|
||||
``mpv '--af=format=channels=5.1' '--audio-channels=5.1'`` would always force
|
||||
remixing audio to 5.1 and output it like this.
|
||||
|
||||
``sub[=fc:ch]``
|
||||
Adds a subwoofer channel to the audio stream. The audio data used for
|
||||
creating the subwoofer channel is an average of the sound in channel 0 and
|
||||
channel 1. The resulting sound is then low-pass filtered by a 4th order
|
||||
Butterworth filter with a default cutoff frequency of 60Hz and added to a
|
||||
separate channel in the audio stream.
|
||||
|
||||
.. warning::
|
||||
|
||||
Disable this filter when you are playing media with an LFE channel
|
||||
(e.g. 5.1 surround sound), otherwise this filter will disrupt the sound
|
||||
to the subwoofer.
|
||||
|
||||
``<fc>``
|
||||
cutoff frequency in Hz for the low-pass filter (20 Hz to 300 Hz)
|
||||
(default: 60 Hz) For the best result try setting the cutoff frequency
|
||||
as low as possible. This will improve the stereo or surround sound
|
||||
experience.
|
||||
``<ch>``
|
||||
Determines the channel number in which to insert the sub-channel
|
||||
audio. Channel number can be between 0 and 7 (default: 5). Observe
|
||||
that the number of channels will automatically be increased to <ch> if
|
||||
necessary.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=sub=100:4 --audio-channels=5 media.avi``
|
||||
Would add a subwoofer channel with a cutoff frequency of 100 Hz to
|
||||
output channel 4.
|
||||
|
||||
``center``
|
||||
Creates a center channel from the front channels. May currently be low
|
||||
quality as it does not implement a high-pass filter for proper extraction
|
||||
yet, but averages and halves the channels instead.
|
||||
|
||||
``<ch>``
|
||||
Determines the channel number in which to insert the center channel.
|
||||
Channel number can be between 0 and 7 (default: 5). Observe that the
|
||||
number of channels will automatically be increased to ``<ch>`` if
|
||||
necessary.
|
||||
|
||||
``surround[=delay]``
|
||||
Decoder for matrix encoded surround sound like Dolby Surround. Some files
|
||||
with 2-channel audio actually contain matrix encoded surround sound.
|
||||
|
||||
``<delay>``
|
||||
delay time in ms for the rear speakers (0 to 1000) (default: 20) This
|
||||
delay should be set as follows: If d1 is the distance from the
|
||||
listening position to the front speakers and d2 is the distance from
|
||||
the listening position to the rear speakers, then the delay should be
|
||||
set to 15ms if d1 <= d2 and to 15 + 5*(d1-d2) if d1 > d2.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=surround=15 --audio-channels=4 media.avi``
|
||||
Would add surround sound decoding with 15 ms delay for the sound to
|
||||
the rear speakers.
|
||||
|
||||
``delay[=[ch1,ch2,...]]``
|
||||
Delays the sound to the loudspeakers such that the sound from the
|
||||
different channels arrives at the listening position simultaneously. It is
|
||||
@@ -443,36 +340,6 @@ Available filters are:
|
||||
Would delay front left and right by 10.5 ms, the two rear channels
|
||||
and the subwoofer by 0 ms and the center channel by 7 ms.
|
||||
|
||||
``export=mmapped_file:nsamples]``
|
||||
Exports the incoming signal to other processes using memory mapping
|
||||
(``mmap()``). Memory mapped areas contain a header::
|
||||
|
||||
int nch /* number of channels */
|
||||
int size /* buffer size */
|
||||
unsigned long long counter /* Used to keep sync, updated every time
|
||||
new data is exported. */
|
||||
|
||||
The rest is payload (non-interleaved) 16-bit data.
|
||||
|
||||
``<mmapped_file>``
|
||||
File to map data to (required)
|
||||
``<nsamples>``
|
||||
number of samples per channel (default: 512).
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=export=/tmp/mpv-af_export:1024 media.avi``
|
||||
Would export 1024 samples per channel to ``/tmp/mpv-af_export``.
|
||||
|
||||
``extrastereo[=mul]``
|
||||
(Linearly) increases the difference between left and right channels which
|
||||
adds some sort of "live" effect to playback.
|
||||
|
||||
``<mul>``
|
||||
Sets the difference coefficient (default: 2.5). 0.0 means mono sound
|
||||
(average of both channels), with 1.0 sound will be unchanged, with
|
||||
-1.0 left and right channels will be swapped.
|
||||
|
||||
``drc[=method:target]``
|
||||
Applies dynamic range compression. This maximizes the volume by compressing
|
||||
the audio signal's dynamic range. (Formerly called ``volnorm``.)
|
||||
@@ -496,45 +363,6 @@ Available filters are:
|
||||
This filter can cause distortion with audio signals that have a very
|
||||
large dynamic range.
|
||||
|
||||
``ladspa=file:label:[<control0>,<control1>,...]``
|
||||
Load a LADSPA (Linux Audio Developer's Simple Plugin API) plugin. This
|
||||
filter is reentrant, so multiple LADSPA plugins can be used at once.
|
||||
|
||||
``<file>``
|
||||
Specifies the LADSPA plugin library file.
|
||||
|
||||
.. note::
|
||||
|
||||
See also the note about the ``LADSPA_PATH`` variable in the
|
||||
`ENVIRONMENT VARIABLES`_ section.
|
||||
``<label>``
|
||||
Specifies the filter within the library. Some libraries contain only
|
||||
one filter, but others contain many of them. Entering 'help' here
|
||||
will list all available filters within the specified library, which
|
||||
eliminates the use of 'listplugins' from the LADSPA SDK.
|
||||
``[<control0>,<control1>,...]``
|
||||
Controls are zero or more ``,`` separated floating point values that
|
||||
determine the behavior of the loaded plugin (for example delay,
|
||||
threshold or gain).
|
||||
In verbose mode (add ``-v`` to the mpv command line), all
|
||||
available controls and their valid ranges are printed. This eliminates
|
||||
the use of 'analyseplugin' from the LADSPA SDK.
|
||||
Note that ``,`` is already used by the option parser to separate
|
||||
filters, so you must quote the list of values with ``[...]`` or
|
||||
similar.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=ladspa='/usr/lib/ladspa/delay.so':delay_5s:[0.5,0.2] media.avi``
|
||||
Does something.
|
||||
|
||||
``karaoke``
|
||||
Simple voice removal filter exploiting the fact that voice is usually
|
||||
recorded with mono gear and later 'center' mixed onto the final audio
|
||||
stream. Beware that this filter will turn your signal into mono. Works
|
||||
well for 2 channel tracks; do not bother trying it on anything but 2
|
||||
channel stereo.
|
||||
|
||||
``scaletempo[=option1:option2:...]``
|
||||
Scales audio tempo without altering pitch, optionally synced to playback
|
||||
speed (default).
|
||||
|
||||
+18
-3
@@ -55,7 +55,7 @@ Available audio output drivers are:
|
||||
Allow output of non-interleaved formats (if the audio decoder uses
|
||||
this format). Currently disabled by default, because some popular
|
||||
ALSA plugins are utterly broken with non-interleaved formats.
|
||||
``ingore-chmap``
|
||||
``ignore-chmap``
|
||||
Don't read or set the channel map of the ALSA device - only request the
|
||||
required number of channels, and then pass the audio as-is to it. This
|
||||
option most likely should not be used. It can be useful for debugging,
|
||||
@@ -162,12 +162,23 @@ Available audio output drivers are:
|
||||
Automatically redirects to ``coreaudio_exclusive`` when playing compressed
|
||||
formats.
|
||||
|
||||
``change-physical-format=<yes|no>``
|
||||
Change the physical format to one similar to the requested audio format
|
||||
(default: no). This has the advantage that multichannel audio output
|
||||
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.
|
||||
|
||||
``exclusive``
|
||||
Use exclusive mode access. This merely redirects to
|
||||
``coreaudio_exclusive``, but should be preferred over using that AO
|
||||
directly.
|
||||
|
||||
``coreaudio_exclusive`` (Mac OS X only)
|
||||
Native Mac OS X audio output driver using direct device access and
|
||||
exclusive mode (bypasses the sound server).
|
||||
|
||||
Supports only compressed formats (AC3 and DTS).
|
||||
|
||||
``openal``
|
||||
Experimental OpenAL audio output driver
|
||||
|
||||
@@ -260,6 +271,10 @@ Available audio output drivers are:
|
||||
``broken-delay``
|
||||
Simulate broken audio drivers, which don't report latency correctly.
|
||||
|
||||
``channel-layouts``
|
||||
If not empty, this is a ``,`` separated list of channel layouts the
|
||||
AO allows. This can be used to test channel layout selection.
|
||||
|
||||
``pcm``
|
||||
Raw PCM/WAVE file writer audio output
|
||||
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ You can encode files from one format/codec to another using this facility.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``"--ovc=mpeg4 --oacopts=qscale=5"``
|
||||
``"--ovc=mpeg4 --ovcopts=qscale=5"``
|
||||
selects constant quantizer scale 5 for MPEG-4 encoding.
|
||||
|
||||
``"--ovc=libx264 --ovcopts=crf=23"``
|
||||
|
||||
+274
-150
@@ -65,11 +65,11 @@ C-style escaping can be used.
|
||||
|
||||
You can bind multiple commands to one key. For example:
|
||||
|
||||
| a show_text "command 1" ; show_text "command 2"
|
||||
| a show-text "command 1" ; show-text "command 2"
|
||||
|
||||
It's also possible to bind a command to a sequence of keys:
|
||||
|
||||
| a-b-c show_text "command run after a, b, c have been pressed"
|
||||
| a-b-c show-text "command run after a, b, c have been pressed"
|
||||
|
||||
(This is not shown in the general command syntax.)
|
||||
|
||||
@@ -86,7 +86,7 @@ List of Input Commands
|
||||
disabling default bindings, without disabling all bindings with
|
||||
``--no-input-default-bindings``.
|
||||
|
||||
``seek <seconds> [relative|absolute|absolute-percent|exact|keyframes]``
|
||||
``seek <seconds> [relative|absolute|absolute-percent|relative-percent|exact|keyframes]``
|
||||
Change the playback position. By default, seeks by a relative amount of
|
||||
seconds.
|
||||
|
||||
@@ -98,6 +98,8 @@ List of Input Commands
|
||||
Seek to a given time.
|
||||
absolute-percent
|
||||
Seek to a given percent position.
|
||||
relative-percent
|
||||
Seek relative to current position in percent.
|
||||
keyframes
|
||||
Always restart playback at keyframe boundaries (fast).
|
||||
exact
|
||||
@@ -112,25 +114,25 @@ List of Input Commands
|
||||
3rd parameter (essentially using a space instead of ``+``). The 3rd
|
||||
parameter is still parsed, but is considered deprecated.
|
||||
|
||||
``revert_seek [mode]``
|
||||
``revert-seek [mode]``
|
||||
Undoes the ``seek`` command, and some other commands that seek (but not
|
||||
necessarily all of them). Calling this command once will jump to the
|
||||
playback position before the seek. Calling it a second time undoes the
|
||||
``revert_seek`` command itself. This only works within a single file.
|
||||
``revert-seek`` command itself. This only works within a single file.
|
||||
|
||||
The first argument is optional, and can change the behavior:
|
||||
|
||||
mark
|
||||
Mark the current time position. The next normal ``revert_seek`` command
|
||||
Mark the current time position. The next normal ``revert-seek`` command
|
||||
will seek back to this point, no matter how many seeks happened since
|
||||
last time.
|
||||
|
||||
Using it without any arguments gives you the default behavior.
|
||||
|
||||
``frame_step``
|
||||
``frame-step``
|
||||
Play one frame, then pause. Does nothing with audio-only playback.
|
||||
|
||||
``frame_back_step``
|
||||
``frame-back-step``
|
||||
Go back by one frame, then pause. Note that this can be very slow (it tries
|
||||
to be precise, not fast), and sometimes fails to behave as expected. How
|
||||
well this works depends on whether precise seeking works correctly (e.g.
|
||||
@@ -180,7 +182,7 @@ List of Input Commands
|
||||
frame was dropped. This flag can be combined with the other flags,
|
||||
e.g. ``video+each-frame``.
|
||||
|
||||
``screenshot_to_file "<filename>" [subtitles|video|window]``
|
||||
``screenshot-to-file "<filename>" [subtitles|video|window]``
|
||||
Take a screenshot and save it to a given file. The format of the file will
|
||||
be guessed by the extension (and ``--screenshot-format`` is ignored - the
|
||||
behavior when the extension is missing or unknown is arbitrary).
|
||||
@@ -192,7 +194,7 @@ List of Input Commands
|
||||
Like all input command parameters, the filename is subject to property
|
||||
expansion as described in `Property Expansion`_.
|
||||
|
||||
``playlist_next [weak|force]``
|
||||
``playlist-next [weak|force]``
|
||||
Go to the next entry on the playlist.
|
||||
|
||||
weak (default)
|
||||
@@ -200,7 +202,7 @@ List of Input Commands
|
||||
force
|
||||
Terminate playback if there are no more files on the playlist.
|
||||
|
||||
``playlist_prev [weak|force]``
|
||||
``playlist-prev [weak|force]``
|
||||
Go to the previous entry on the playlist.
|
||||
|
||||
weak (default)
|
||||
@@ -230,22 +232,26 @@ List of Input Commands
|
||||
``loadlist "<playlist>" [replace|append]``
|
||||
Load the given playlist file (like ``--playlist``).
|
||||
|
||||
``playlist_clear``
|
||||
``playlist-clear``
|
||||
Clear the playlist, except the currently played file.
|
||||
|
||||
``playlist_remove current|<index>``
|
||||
``playlist-remove current|<index>``
|
||||
Remove the playlist entry at the given index. Index values start counting
|
||||
with 0. The special value ``current`` removes the current entry. Note that
|
||||
removing the current entry also stops playback and starts playing the next
|
||||
entry.
|
||||
|
||||
``playlist_move <index1> <index2>``
|
||||
``playlist-move <index1> <index2>``
|
||||
Move the playlist entry at index1, so that it takes the place of the
|
||||
entry index2. (Paradoxically, the moved playlist entry will not have
|
||||
the index value index2 after moving if index1 was lower than index2,
|
||||
because index2 refers to the target entry, not the index the entry
|
||||
will have after moving.)
|
||||
|
||||
``playlist-shuffle``
|
||||
Shuffle the playlist. This is similar to what is done on start if the
|
||||
``--shuffle`` option is used.
|
||||
|
||||
``run "command" "arg1" "arg2" ...``
|
||||
Run the given command. Unlike in MPlayer/mplayer2 and earlier versions of
|
||||
mpv (0.2.x and older), this doesn't call the shell. Instead, the command
|
||||
@@ -271,12 +277,12 @@ List of Input Commands
|
||||
``quit [<code>]``
|
||||
Exit the player. If an argument is given, it's used as process exit code.
|
||||
|
||||
``quit_watch_later [<code>]``
|
||||
``quit-watch-later [<code>]``
|
||||
Exit player, and store current playback position. Playing that file later
|
||||
will seek to the previous position on start. The (optional) argument is
|
||||
exactly as in the ``quit`` command.
|
||||
|
||||
``sub_add "<file>" [<flags> [<title> [<lang>]]]``
|
||||
``sub-add "<file>" [<flags> [<title> [<lang>]]]``
|
||||
Load the given subtitle file. It is selected as current subtitle after
|
||||
loading.
|
||||
|
||||
@@ -303,27 +309,27 @@ List of Input Commands
|
||||
The ``lang`` argument sets the track language, and can also influence
|
||||
stream selection with ``flags`` set to ``auto``.
|
||||
|
||||
``sub_remove [<id>]``
|
||||
``sub-remove [<id>]``
|
||||
Remove the given subtitle track. If the ``id`` argument is missing, remove
|
||||
the current track. (Works on external subtitle files only.)
|
||||
|
||||
``sub_reload [<id>]``
|
||||
``sub-reload [<id>]``
|
||||
Reload the given subtitle tracks. If the ``id`` argument is missing, reload
|
||||
the current track. (Works on external subtitle files only.)
|
||||
|
||||
This works by unloading and re-adding the subtitle track.
|
||||
|
||||
``sub_step <skip>``
|
||||
``sub-step <skip>``
|
||||
Change subtitle timing such, that the subtitle event after the next
|
||||
``<skip>`` subtitle events is displayed. ``<skip>`` can be negative to step
|
||||
backwards.
|
||||
|
||||
``sub_seek <skip>``
|
||||
``sub-seek <skip>``
|
||||
Seek to the next (skip set to 1) or the previous (skip set to -1) subtitle.
|
||||
This is similar to ``sub_step``, except that it seeks video and audio
|
||||
This is similar to ``sub-step``, except that it seeks video and audio
|
||||
instead of adjusting the subtitle delay.
|
||||
|
||||
Like with ``sub_step``, this works with external text subtitles only. For
|
||||
Like with ``sub-step``, this works with external text subtitles only. For
|
||||
embedded text subtitles (like with Matroska), this works only with subtitle
|
||||
events that have already been displayed.
|
||||
|
||||
@@ -331,11 +337,11 @@ List of Input Commands
|
||||
Toggle OSD level. If ``<level>`` is specified, set the OSD mode
|
||||
(see ``--osd-level`` for valid values).
|
||||
|
||||
``print_text "<string>"``
|
||||
``print-text "<string>"``
|
||||
Print text to stdout. The string can contain properties (see
|
||||
`Property Expansion`_).
|
||||
|
||||
``show_text "<string>" [<duration>|- [<level>]]``
|
||||
``show-text "<string>" [<duration>|- [<level>]]``
|
||||
Show text on the OSD. The string can contain properties, which are expanded
|
||||
as described in `Property Expansion`_. This can be used to show playback
|
||||
time, filename, and so on.
|
||||
@@ -347,24 +353,12 @@ List of Input Commands
|
||||
<level>
|
||||
The minimum OSD level to show the text at (see ``--osd-level``).
|
||||
|
||||
``show_progress``
|
||||
``show-progress``
|
||||
Show the progress bar, the elapsed time and the total duration of the file
|
||||
on the OSD.
|
||||
|
||||
``discnav "<command>"``
|
||||
Send a menu control command to the DVD/BD menu implementation. The following
|
||||
commands are defined: ``up``, ``down``, ``left``, ``right``,
|
||||
``menu`` (request to enter menu), ``prev`` (previous screen),
|
||||
``select`` (activate current button), ``mouse`` (the mouse was clicked),
|
||||
``mouse_move`` (the mouse cursor changed position).
|
||||
|
||||
``mouse_move`` will use the current mouse position.
|
||||
|
||||
Note that while the menu is active, the input section ``discnav-menu`` will
|
||||
be enabled, so different key bindings can be mapped for menu mode.
|
||||
|
||||
``write_watch_later_config``
|
||||
Write the resume config file that the ``quit_watch_later`` command writes,
|
||||
``write-watch-later-config``
|
||||
Write the resume config file that the ``quit-watch-later`` command writes,
|
||||
but continue playback normally.
|
||||
|
||||
``stop``
|
||||
@@ -389,30 +383,47 @@ List of Input Commands
|
||||
<double>
|
||||
The mouse event represents double-click.
|
||||
|
||||
``audio_add "<file>" [<flags> [<title> [<lang>]]]``
|
||||
Load the given audio file. See ``sub_add`` command.
|
||||
``keypress <key_name>``
|
||||
Send a key event through mpv's input handler, triggering whatever
|
||||
behavior is configured to that key. ``key_name`` uses the ``input.conf``
|
||||
naming scheme for keys and modifiers. Useful for the client API: key events
|
||||
can be sent to libmpv to handle internally.
|
||||
|
||||
``audio_remove [<id>]``
|
||||
Remove the given audio track. See ``sub_remove`` command.
|
||||
``keydown <key_name>``
|
||||
Similar to ``keypress``, but sets the ``KEYDOWN`` flag so that if the key is
|
||||
bound to a repeatable command, it will be run repeatedly with mpv's key
|
||||
repeat timing until the ``keyup`` command is called.
|
||||
|
||||
``audio_reload [<id>]``
|
||||
Reload the given audio tracks. See ``sub_reload`` command.
|
||||
``keyup [<key_name>]``
|
||||
Set the ``KEYUP`` flag, stopping any repeated behavior that had been
|
||||
triggered. ``key_name`` is optional. If ``key_name`` is not given or is an
|
||||
empty string, ``KEYUP`` will be set on all keys. Otherwise, ``KEYUP`` will
|
||||
only be set on the key specified by ``key_name``.
|
||||
|
||||
``rescan_external_files [<mode>]``
|
||||
``audio-add "<file>" [<flags> [<title> [<lang>]]]``
|
||||
Load the given audio file. See ``sub-add`` command.
|
||||
|
||||
``audio-remove [<id>]``
|
||||
Remove the given audio track. See ``sub-remove`` command.
|
||||
|
||||
``audio-reload [<id>]``
|
||||
Reload the given audio tracks. See ``sub-reload`` command.
|
||||
|
||||
``rescan-external-files [<mode>]``
|
||||
Rescan external files according to the current ``--sub-auto`` and
|
||||
``--audio-file-auto`` settings. This can be used to auto-load external
|
||||
files *after* the file was loaded.
|
||||
|
||||
The ``mode`` argument is one of the following:
|
||||
|
||||
<keep-selection> (default)
|
||||
Do not change current track selections.
|
||||
|
||||
<reselect>
|
||||
Select the default audio and video streams, which typically selects
|
||||
<reselect> (default)
|
||||
Select the default audio and subtitle streams, which typically selects
|
||||
external files with highest preference. (The implementation is not
|
||||
perfect, and could be improved on request.)
|
||||
|
||||
<keep-selection>
|
||||
Do not change current track selections.
|
||||
|
||||
|
||||
Input Commands that are Possibly Subject to Change
|
||||
--------------------------------------------------
|
||||
@@ -455,7 +466,7 @@ Input Commands that are Possibly Subject to Change
|
||||
|
||||
The ``vf`` command shows the list of requested filters on the OSD after
|
||||
changing the filter chain. This is roughly equivalent to
|
||||
``show_text ${vf}``. Note that auto-inserted filters for format conversion
|
||||
``show-text ${vf}``. Note that auto-inserted filters for format conversion
|
||||
are not shown on the list, only what was requested by the user.
|
||||
|
||||
Normally, the commands will check whether the video chain is recreated
|
||||
@@ -470,7 +481,7 @@ Input Commands that are Possibly Subject to Change
|
||||
- ``b vf set ""`` remove all video filters on ``b``
|
||||
- ``c vf toggle lavfi=gradfun`` toggle debanding on ``c``
|
||||
|
||||
``cycle_values ["!reverse"] <property> "<value1>" "<value2>" ...``
|
||||
``cycle-values ["!reverse"] <property> "<value1>" "<value2>" ...``
|
||||
Cycle through a list of values. Each invocation of the command will set the
|
||||
given property to the next value in the list. The command maintains an
|
||||
internal counter which value to pick next, and which is initially 0. It is
|
||||
@@ -488,7 +499,7 @@ Input Commands that are Possibly Subject to Change
|
||||
Note that there is a static limit of (as of this writing) 10 arguments
|
||||
(this limit could be raised on demand).
|
||||
|
||||
``enable_section "<section>" [default|exclusive]``
|
||||
``enable-section "<section>" [flags]``
|
||||
Enable all key bindings in the named input section.
|
||||
|
||||
The enabled input sections form a stack. Bindings in sections on the top of
|
||||
@@ -497,14 +508,45 @@ Input Commands that are Possibly Subject to Change
|
||||
implicitly removed beforehand. (A section cannot be on the stack more than
|
||||
once.)
|
||||
|
||||
If ``exclusive`` is specified as second argument, all sections below the
|
||||
newly enabled section are disabled. They will be re-enabled as soon as
|
||||
all exclusive sections above them are removed.
|
||||
The ``flags`` parameter can be a combination (separated by ``+``) of the
|
||||
following flags:
|
||||
|
||||
``disable_section "<section>"``
|
||||
Disable the named input section. Undoes ``enable_section``.
|
||||
<exclusive>
|
||||
All sections enabled before the newly enabled section are disabled.
|
||||
They will be re-enabled as soon as all exclusive sections above them
|
||||
are removed. In other words, the new section shadows all previous
|
||||
sections.
|
||||
<allow-hide-cursor>
|
||||
This feature can't be used through the public API.
|
||||
<allow-vo-dragging>
|
||||
Same.
|
||||
|
||||
``overlay_add <id> <x> <y> "<file>" <offset> "<fmt>" <w> <h> <stride>``
|
||||
``disable-section "<section>"``
|
||||
Disable the named input section. Undoes ``enable-section``.
|
||||
|
||||
``define-section "<section>" "<contents>" [default|forced]``
|
||||
Create a named input section, or replace the contents of an already existing
|
||||
input section. The ``contents`` parameter uses the same syntax as the
|
||||
``input.conf`` file (except that using the section syntax in it is not
|
||||
allowed), including the need to separate bindings with a newline character.
|
||||
|
||||
If the ``contents`` parameter is an empty string, the section is removed.
|
||||
|
||||
The section with the name ``default`` is the normal input section.
|
||||
|
||||
In general, input sections have to be enabled with the ``enable-section``
|
||||
command, or they are ignored.
|
||||
|
||||
The last parameter has the following meaning:
|
||||
|
||||
<default> (also used if parameter omitted)
|
||||
Use a key binding defined by this section only if the user hasn't
|
||||
already bound this key to a command.
|
||||
<forced>
|
||||
Always bind a key. (The input section that was made active most recently
|
||||
wins if there are ambiguities.)
|
||||
|
||||
``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
|
||||
of the video window.
|
||||
@@ -520,7 +562,7 @@ Input Commands that are Possibly Subject to Change
|
||||
``id`` is an integer between 0 and 63 identifying the overlay element. The
|
||||
ID can be used to add multiple overlay parts, update a part by using this
|
||||
command with an already existing ID, or to remove a part with
|
||||
``overlay_remove``. Using a previously unused ID will add a new overlay,
|
||||
``overlay-remove``. Using a previously unused ID will add a new overlay,
|
||||
while reusing an ID will update it. (Future directions: there should be
|
||||
something to ensure different programs wanting to create overlays don't
|
||||
conflict with each others, should that ever be needed.)
|
||||
@@ -534,11 +576,11 @@ Input Commands that are Possibly Subject to Change
|
||||
vdpau), so no actual copying is involved. Truncating the source file while
|
||||
the overlay is active will crash the player. You shouldn't change the data
|
||||
while the overlay is active, because the data is essentially accessed at
|
||||
random points. Instead, call ``overlay_add`` again (preferably with a
|
||||
random points. Instead, call ``overlay-add`` again (preferably with a
|
||||
different memory region to prevent tearing).
|
||||
|
||||
It is also possible to pass a raw memory address for use as bitmap memory
|
||||
by passing a memory address as integer prefixed with a ``&`` character.
|
||||
by passing a memory address as integer prefixed with an ``&`` character.
|
||||
Passing the wrong thing here will crash the player. This mode might be
|
||||
useful for use with libmpv. The ``offset`` parameter is simply added to the
|
||||
memory address (since mpv 0.8.0, ignored before).
|
||||
@@ -578,22 +620,22 @@ Input Commands that are Possibly Subject to Change
|
||||
an overlay's memory at random times whenever it feels the need to do
|
||||
so, for example when redrawing the screen.
|
||||
|
||||
``overlay_remove <id>``
|
||||
Remove an overlay added with ``overlay_add`` and the same ID. Does nothing
|
||||
``overlay-remove <id>``
|
||||
Remove an overlay added with ``overlay-add`` and the same ID. Does nothing
|
||||
if no overlay with this ID exists.
|
||||
|
||||
``script_message "<arg1>" "<arg2>" ...``
|
||||
``script-message "<arg1>" "<arg2>" ...``
|
||||
Send a message to all clients, and pass it the following list of arguments.
|
||||
What this message means, how many arguments it takes, and what the arguments
|
||||
mean is fully up to the receiver and the sender. Every client receives the
|
||||
message, so be careful about name clashes (or use ``script_message_to``).
|
||||
|
||||
``script_message_to "<target>" "<arg1>" "<arg2>" ...``
|
||||
``script-message-to "<target>" "<arg1>" "<arg2>" ...``
|
||||
Same as ``script_message``, but send it only to the client named
|
||||
``<target>``. Each client (scripts etc.) has a unique name. For example,
|
||||
Lua scripts can get their name via ``mp.get_script_name()``.
|
||||
|
||||
``script_binding "<name>"``
|
||||
``script-binding "<name>"``
|
||||
Invoke a script-provided key binding. This can be used to remap key
|
||||
bindings provided by external Lua scripts.
|
||||
|
||||
@@ -614,24 +656,24 @@ Input Commands that are Possibly Subject to Change
|
||||
tracked). The second letter whether the event originates from the mouse,
|
||||
either ``m`` (mouse button) or ``-`` (something else).
|
||||
|
||||
``ab_loop``
|
||||
``ab-loop``
|
||||
Cycle through A-B loop states. The first command will set the ``A`` point
|
||||
(the ``ab-loop-a`` property); the second the ``B`` point, and the third
|
||||
will clear both points.
|
||||
|
||||
``vo_cmdline "<args>"``
|
||||
``vo-cmdline "<args>"``
|
||||
Reset the sub-option of the current VO. Currently works with ``opengl``
|
||||
(including ``opengl-hq``). The argument is the sub-option string usually
|
||||
passed to the VO on the command line. Not all sub-options can be set, but
|
||||
those which can will be reset even if they don't appear in the argument.
|
||||
This command might be changed or removed in the future.
|
||||
|
||||
``drop_buffers``
|
||||
``drop-buffers``
|
||||
Drop audio/video/demuxer buffers, and restart from fresh. Might help with
|
||||
unseekable streams that are going out of sync.
|
||||
This command might be changed or removed in the future.
|
||||
|
||||
``screenshot_raw [subtitles|video|window]``
|
||||
``screenshot-raw [subtitles|video|window]``
|
||||
Return a screenshot in memory. This can be used only through the client
|
||||
API. The MPV_FORMAT_NODE_MAP returned by this command has the ``w``, ``h``,
|
||||
``stride`` fields set to obvious contents. A ``format`` field is set to
|
||||
@@ -640,14 +682,14 @@ 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.
|
||||
|
||||
Undocumented commands: ``tv_last_channel`` (TV/DVB only),
|
||||
``get_property`` (deprecated), ``ao_reload`` (experimental/internal).
|
||||
Undocumented commands: ``tv-last-channel`` (TV/DVB only),
|
||||
``ao-reload`` (experimental/internal).
|
||||
|
||||
Hooks
|
||||
~~~~~
|
||||
|
||||
Hooks are synchronous events between player core and a script or similar. This
|
||||
applies to the Lua scripting interface and the client API and only. Normally,
|
||||
applies to client API (including the Lua scripting interface). Normally,
|
||||
events are supposed to be asynchronous, and the hook API provides an awkward
|
||||
and obscure way to handle events that require stricter coordination. There are
|
||||
no API stability guarantees made. Not following the protocol exactly can make
|
||||
@@ -656,7 +698,7 @@ the player freeze randomly. Basically, nobody should use this API.
|
||||
There are two special commands involved. Also, the client must listen for
|
||||
client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
|
||||
|
||||
``hook_add <hook-name> <id> <priority>``
|
||||
``hook-add <hook-name> <id> <priority>``
|
||||
Subscribe to the hook identified by the first argument (basically, the
|
||||
name of event). The ``id`` argument is an arbitrary integer chosen by the
|
||||
user. ``priority`` is used to sort all hook handlers globally across all
|
||||
@@ -681,9 +723,9 @@ client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
|
||||
typically be stopped.
|
||||
|
||||
When the client is done, it must continue the core's hook execution by
|
||||
running the ``hook_ack`` command.
|
||||
running the ``hook-ack`` command.
|
||||
|
||||
``hook_ack <string>``
|
||||
``hook-ack <string>``
|
||||
Run the next hook in the global chain of hooks. The argument is the 3rd
|
||||
argument of the client message that starts hook execution for the
|
||||
current client.
|
||||
@@ -756,7 +798,7 @@ Properties
|
||||
|
||||
Properties are used to set mpv options during runtime, or to query arbitrary
|
||||
information. They can be manipulated with the ``set``/``add``/``cycle``
|
||||
commands, and retrieved with ``show_text``, or anything else that uses property
|
||||
commands, and retrieved with ``show-text``, or anything else that uses property
|
||||
expansion. (See `Property Expansion`_.)
|
||||
|
||||
The property name is annotated with RW to indicate whether the property is
|
||||
@@ -784,6 +826,16 @@ Property list
|
||||
``speed`` (RW)
|
||||
See ``--speed``.
|
||||
|
||||
``audio-speed-correction``, ``video-speed-correction``
|
||||
Factor multiplied with ``speed`` at which the player attempts to play the
|
||||
file. Usually it's exactly 1. (Display sync mode will make this useful.)
|
||||
|
||||
OSD formatting will display it in the form of ``+1.23456%``, with the number
|
||||
being ``(raw - 1) * 100`` for the given raw property value.
|
||||
|
||||
``display-sync-active``
|
||||
Return whether ``--video-sync=display`` is actually active.
|
||||
|
||||
``filename``
|
||||
Currently played file, with path stripped. If this is an URL, try to undo
|
||||
percent encoding as well. (The result is not necessarily correct, but
|
||||
@@ -836,11 +888,14 @@ Property list
|
||||
``stream-end``
|
||||
Raw end position in bytes in source stream.
|
||||
|
||||
``length``
|
||||
Length of the current file in seconds. If the length is unknown, the
|
||||
``duration``
|
||||
Duration of the current file in seconds. If the duration is unknown, the
|
||||
property is unavailable. Note that the file duration is not always exactly
|
||||
known, so this is an estimate.
|
||||
|
||||
This replaces the ``length`` property, which was deprecated after the
|
||||
mpv 0.9 release. (The semantics are the same.)
|
||||
|
||||
``avsync``
|
||||
Last A/V synchronization difference. Unavailable if audio or video is
|
||||
disabled.
|
||||
@@ -876,10 +931,12 @@ Property list
|
||||
always exactly known, so this is an estimate.
|
||||
|
||||
``playtime-remaining``
|
||||
``time-remaining`` scaled by the the current ``speed``.
|
||||
``time-remaining`` scaled by the current ``speed``.
|
||||
|
||||
``playback-time``
|
||||
Return the playback time, which is the time difference between start PTS and current PTS.
|
||||
``playback-time`` (RW)
|
||||
The playback time, which is the time relative to playback start. (This can
|
||||
be different from the ``time-pos`` property if the file does not start at
|
||||
position ``0``, in which case ``time-pos`` is the source timestamp.)
|
||||
|
||||
``chapter`` (RW)
|
||||
Current chapter number. The number of the first chapter is 0.
|
||||
@@ -922,16 +979,6 @@ Property list
|
||||
Current BD/DVD title number. Writing works only for ``dvdnav://`` and
|
||||
``bd://`` (and aliases for these).
|
||||
|
||||
``disc-menu-active``
|
||||
Return ``yes`` if the BD/DVD menu is active, or ``no`` on normal video
|
||||
playback. The property is unavailable when playing something that is not
|
||||
a BD or DVD. Use the ``discnav menu`` command to actually enter or leave
|
||||
menu mode.
|
||||
|
||||
``disc-mouse-on-button``
|
||||
Return ``yes`` when the mouse cursor is located on a button, or ``no``
|
||||
when cursor is outside of any button for disc navigation.
|
||||
|
||||
``chapters``
|
||||
Number of chapters.
|
||||
|
||||
@@ -1044,6 +1091,9 @@ Property list
|
||||
An example of these kind of metadata are the cropping parameters
|
||||
added by ``--vf=lavfi=cropdetect``.
|
||||
|
||||
``af-metadata/<filter-label>``
|
||||
Equivalent to ``vf-metadata/<filter-label>``, but for audio filters.
|
||||
|
||||
``pause`` (RW)
|
||||
Pause status. This is usually ``yes`` or ``no``. See ``--pause``.
|
||||
|
||||
@@ -1064,11 +1114,13 @@ Property list
|
||||
Network cache fill state (0-100.0).
|
||||
|
||||
``cache-size`` (RW)
|
||||
Total network cache size in KB. This is similar to ``--cache``. This allows
|
||||
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
|
||||
or disable the cache at runtime using this property, just to resize an
|
||||
existing cache.
|
||||
|
||||
This does not include the backbuffer size (changed after mpv 0.10.0).
|
||||
|
||||
Note that this tries to keep the cache contents as far as possible. To make
|
||||
this easier, the cache resizing code will allocate the new cache while the
|
||||
old cache is still allocated.
|
||||
@@ -1092,7 +1144,7 @@ Property list
|
||||
|
||||
``demuxer-cache-time``
|
||||
Approximate time of video buffered in the demuxer, in seconds. Same as
|
||||
``demuxer-cache-duration`` but returns the last timestamp of bufferred
|
||||
``demuxer-cache-duration`` but returns the last timestamp of buffered
|
||||
data in demuxer.
|
||||
|
||||
``demuxer-cache-idle``
|
||||
@@ -1126,7 +1178,7 @@ Property list
|
||||
See ``--hr-seek``.
|
||||
|
||||
``volume`` (RW)
|
||||
Current volume (0-100).
|
||||
Current volume (see ``--volume`` for details).
|
||||
|
||||
``mute`` (RW)
|
||||
Current mute status (``yes``/``no``).
|
||||
@@ -1134,18 +1186,52 @@ Property list
|
||||
``audio-delay`` (RW)
|
||||
See ``--audio-delay``.
|
||||
|
||||
``audio-format``
|
||||
Audio format as string.
|
||||
|
||||
``audio-codec``
|
||||
Audio codec selected for decoding.
|
||||
|
||||
``audio-samplerate``
|
||||
Audio samplerate.
|
||||
``audio-codec-name``
|
||||
Audio codec.
|
||||
|
||||
``audio-channels``
|
||||
Number of audio channels. The OSD value of this property is actually the
|
||||
channel layout, while the raw value returns the number of channels only.
|
||||
``audio-params``
|
||||
Audio format as output by the audio decoder.
|
||||
This has a number of sub-properties:
|
||||
|
||||
``audio-params/format``
|
||||
The sample format as string. This uses the same names as used in other
|
||||
places of mpv.
|
||||
|
||||
``audio-params/samplerate``
|
||||
Samplerate.
|
||||
|
||||
``audio-params/channels``
|
||||
The channel layout as a string. This is similar to what the
|
||||
``--audio-channels`` accepts.
|
||||
|
||||
``audio-params/hr-channels``
|
||||
As ``channels``, but instead of the possibly cryptic actual layout
|
||||
sent to the audio device, return a hopefully more human readable form.
|
||||
(Usually only ``audio-out-params/hr-channels`` makes sense.)
|
||||
|
||||
``audio-params/channel-count``
|
||||
Number of audio channels. This is redundant to the ``channels`` field
|
||||
described above.
|
||||
|
||||
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_MAP
|
||||
"format" MPV_FORMAT_STRING
|
||||
"samplerate" MPV_FORMAT_INT64
|
||||
"channels" MPV_FORMAT_STRING
|
||||
"channel-count" MPV_FORMAT_INT64
|
||||
"hr-channels" MPV_FORMAT_STRING
|
||||
|
||||
``audio-out-params``
|
||||
Same as ``audio-params``, but the format of the data written to the audio
|
||||
API.
|
||||
|
||||
``aid`` (RW)
|
||||
Current audio track (similar to ``--aid``).
|
||||
@@ -1156,7 +1242,7 @@ Property list
|
||||
``balance`` (RW)
|
||||
Audio channel balance. (The implementation of this feature is rather odd.
|
||||
It doesn't change the volumes of each channel, but instead sets up a pan
|
||||
matrix to mix the the left and right channels.)
|
||||
matrix to mix the left and right channels.)
|
||||
|
||||
``fullscreen`` (RW)
|
||||
See ``--fullscreen``.
|
||||
@@ -1167,17 +1253,18 @@ Property list
|
||||
``field-dominance`` (RW)
|
||||
See ``--field-dominance``
|
||||
|
||||
``colormatrix`` (RW)
|
||||
See ``--colormatrix``.
|
||||
``colormatrix`` (R)
|
||||
Redirects to ``video-params/colormatrix``. This parameter (as well as
|
||||
similar ones) can be overridden with the ``format`` video filter.
|
||||
|
||||
``colormatrix-input-range`` (RW)
|
||||
See ``--colormatrix-input-range``.
|
||||
``colormatrix-input-range`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``colormatrix-output-range`` (RW)
|
||||
See ``--colormatrix-output-range``.
|
||||
``colormatrix-output-range`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``colormatrix-primaries`` (RW)
|
||||
See ``--colormatrix-primaries``.
|
||||
``colormatrix-primaries`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``ontop`` (RW)
|
||||
See ``--ontop``.
|
||||
@@ -1207,25 +1294,29 @@ Property list
|
||||
See ``--hue``.
|
||||
|
||||
``hwdec`` (RW)
|
||||
Return the current hardware decoder that is used. This uses the same values
|
||||
as the ``--hwdec`` option. If software decoding is active, this returns
|
||||
``no``. You can write this property. Then the ``--hwdec`` option is set to
|
||||
the new value, and video decoding will be reinitialized (internally, the
|
||||
player will perform a seek to refresh the video properly).
|
||||
Reflects the ``--hwdec`` option.
|
||||
|
||||
Note that you don't know the success of the operation immediately after
|
||||
writing this property. It happens with a delay as video is reinitialized.
|
||||
Writing to it may change the currently used hardware decoder, if possible.
|
||||
(Internally, the player may reinitialize the decoder, and will perform a
|
||||
seek to refresh the video properly.) You can watch the other hwdec
|
||||
properties to see whether this was successful.
|
||||
|
||||
``detected-hwdec``
|
||||
Return the current hardware decoder that was detected and opened. Returns
|
||||
the same values as ``hwdec``.
|
||||
Unlike in mpv 0.9.x and before, this does not return the currently active
|
||||
hardware decoder.
|
||||
|
||||
This is known only once the VO has opened (and possibly later). With some
|
||||
VOs (like ``opengl``), this is never known in advance, but only when the
|
||||
decoder attempted to create the hw decoder successfully. Also, hw decoders
|
||||
with ``-copy`` suffix are returned only while hw decoding is active (and
|
||||
unset afterwards). All this reflects how detecting hw decoders are
|
||||
detected and used internally in mpv.
|
||||
``hwdec-active``
|
||||
Return ``yes`` or ``no``, depending on whether any type of hardware decoding
|
||||
is actually in use.
|
||||
|
||||
``hwdec-detected``
|
||||
If software decoding is active, this returns the hardware decoder in use.
|
||||
Otherwise, it returns either ``no``, or if applicable, the currently loaded
|
||||
hardware decoding API. This is known only once the VO has opened (and
|
||||
possibly later). With some VOs (like ``opengl``), this is never known in
|
||||
advance, but only when the decoder attempted to create the hw decoder
|
||||
successfully. Also, hw decoders with ``-copy`` suffix will return ``no``
|
||||
while no video is being decoded. All this reflects how detecting hw decoders
|
||||
are detected and used internally in mpv.
|
||||
|
||||
``panscan`` (RW)
|
||||
See ``--panscan``.
|
||||
@@ -1289,26 +1380,29 @@ Property list
|
||||
``video-params/rotate``
|
||||
Intended display rotation in degrees (clockwise).
|
||||
|
||||
``video-params/stereo-in``
|
||||
Source file stereo 3D mode. (See ``--video-stereo-mode`` option.)
|
||||
|
||||
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 track)
|
||||
"pixelformat" MPV_FORMAT_STRING
|
||||
"w" MPV_FORMAT_INT64
|
||||
"h" MPV_FORMAT_INT64
|
||||
"dw" MPV_FORMAT_INT64
|
||||
"dh" MPV_FORMAT_INT64
|
||||
"aspect" MPV_FORMAT_DOUBLE
|
||||
"par" MPV_FORMAT_DOUBLE
|
||||
"colormatrix" MPV_FORMAT_STRING
|
||||
"colorlevels" MPV_FORMAT_STRING
|
||||
"primaries" MPV_FORMAT_STRING
|
||||
"chroma-location" MPV_FORMAT_STRING
|
||||
"rotate" MPV_FORMAT_INT64
|
||||
MPV_FORMAT_NODE_MAP
|
||||
"pixelformat" MPV_FORMAT_STRING
|
||||
"w" MPV_FORMAT_INT64
|
||||
"h" MPV_FORMAT_INT64
|
||||
"dw" MPV_FORMAT_INT64
|
||||
"dh" MPV_FORMAT_INT64
|
||||
"aspect" MPV_FORMAT_DOUBLE
|
||||
"par" MPV_FORMAT_DOUBLE
|
||||
"colormatrix" MPV_FORMAT_STRING
|
||||
"colorlevels" MPV_FORMAT_STRING
|
||||
"primaries" MPV_FORMAT_STRING
|
||||
"chroma-location" MPV_FORMAT_STRING
|
||||
"rotate" MPV_FORMAT_INT64
|
||||
"stereo-in" MPV_FORMAT_STRING
|
||||
|
||||
``dwidth``, ``dheight``
|
||||
Video display size. This is the video size after filters and aspect scaling
|
||||
@@ -1462,6 +1556,11 @@ Property list
|
||||
been unloaded yet; in this case, ``current`` refers to the new
|
||||
selection. (Since mpv 0.7.0.)
|
||||
|
||||
``playlist/N/title``
|
||||
Name of the Nth entry. Only available if the playlist file contains
|
||||
such fields, and only if mpv's parser supports it for the given
|
||||
playlist format.
|
||||
|
||||
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:
|
||||
@@ -1473,6 +1572,7 @@ Property list
|
||||
"filename" MPV_FORMAT_STRING
|
||||
"current" MPV_FORMAT_FLAG (might be missing; since mpv 0.7.0)
|
||||
"playing" MPV_FORMAT_FLAG (same)
|
||||
"title" MPV_FORMAT_STRING (optional)
|
||||
|
||||
``track-list``
|
||||
List of audio/video/sub tracks, current entry marked. Currently, the raw
|
||||
@@ -1500,6 +1600,10 @@ 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
|
||||
@@ -1509,6 +1613,10 @@ Property list
|
||||
``yes`` if the track has the default flag set in the file, ``no``
|
||||
otherwise.
|
||||
|
||||
``track-list/N/forced``
|
||||
``yes`` if the track has the forced flag set in the file, ``no``
|
||||
otherwise.
|
||||
|
||||
``track-list/N/codec``
|
||||
The codec name used by this track, for example ``h264``. Unavailable
|
||||
in some rare cases.
|
||||
@@ -1544,8 +1652,10 @@ 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
|
||||
@@ -1624,7 +1734,7 @@ Property list
|
||||
|
||||
``osd-sym-cc``
|
||||
Inserts the current OSD symbol as opaque OSD control code (cc). This makes
|
||||
sense only with the ``show_text`` command or options which set OSD messages.
|
||||
sense only with the ``show-text`` command or options which set OSD messages.
|
||||
The control code is implementation specific and is useless for anything else.
|
||||
|
||||
``osd-ass-cc``
|
||||
@@ -1632,13 +1742,13 @@ Property list
|
||||
``${osd-ass-cc/1}`` enables it again. By default, ASS sequences are
|
||||
escaped to avoid accidental formatting, and this property can disable
|
||||
this behavior. Note that the properties return an opaque OSD control
|
||||
code, which only makes sense for the ``show_text`` command or options
|
||||
code, which only makes sense for the ``show-text`` command or options
|
||||
which set OSD messages.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
- ``--osd-status-msg='This is ${osd-ass-cc/0}{\\b1}bold text'``
|
||||
- ``show_text "This is ${osd-ass-cc/0}{\b1}bold text"``
|
||||
- ``show-text "This is ${osd-ass-cc/0}{\b1}bold text"``
|
||||
|
||||
Any ASS override tags as understood by libass can be used.
|
||||
|
||||
@@ -1739,6 +1849,12 @@ Property list
|
||||
Return the working directory of the mpv process. Can be useful for JSON IPC
|
||||
users, because the command line player usually works with relative paths.
|
||||
|
||||
``protocol-list``
|
||||
List of protocol prefixes potentially recognized by the player. They are
|
||||
returned without trailing ``://`` suffix (which is still always required).
|
||||
In some cases, the protocol will not actually be supported (consider
|
||||
``https`` if ffmpeg is not compiled with TLS support).
|
||||
|
||||
``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.
|
||||
@@ -1778,11 +1894,16 @@ Property list
|
||||
For many complex types, this isn't very accurate.
|
||||
|
||||
``option-info/<name>/set-from-commandline``
|
||||
|
||||
Return ``yes`` if the option was set from the mpv command line,
|
||||
``no`` otherwise. What this is set to if the option is e.g. changed
|
||||
at runtime is left undefined (meaning it could change in the future).
|
||||
|
||||
``option-info/<name>/set-locally``
|
||||
Return ``yes`` if the option was set per-file. This is the case with
|
||||
automatically loaded profiles, file-dir configs, and other cases. It
|
||||
means the option value will be restored to the value before playback
|
||||
start when playback ends.
|
||||
|
||||
``option-info/<name>/default-value``
|
||||
The default value of the option. May not always be available.
|
||||
|
||||
@@ -1805,11 +1926,14 @@ Property Expansion
|
||||
------------------
|
||||
|
||||
All string arguments to input commands as well as certain options (like
|
||||
``--term-playing-msg``) are subject to property expansion.
|
||||
``--term-playing-msg``) are subject to property expansion. Note that property
|
||||
expansion does not work in places where e.g. numeric parameters are expected.
|
||||
(For example, the ``add`` command does not do property expansion. The ``set``
|
||||
command is an exception and not a general rule.)
|
||||
|
||||
.. admonition:: Example for input.conf
|
||||
|
||||
``i show_text "Filename: ${filename}"``
|
||||
``i show-text "Filename: ${filename}"``
|
||||
shows the filename of the current file when pressing the ``i`` key
|
||||
|
||||
Within ``input.conf``, property expansion can be inhibited by putting the
|
||||
|
||||
@@ -79,6 +79,24 @@ mpv will also send events to clients with JSON messages of the following form:
|
||||
where ``event_name`` is the name of the event. Additional event-specific fields
|
||||
can also be present. See `List of events`_ for a list of all supported events.
|
||||
|
||||
Because events can occur at any time, it may be difficult at times to determine
|
||||
which response goes with which command. Commands may optionally include a
|
||||
``request_id`` which, if provided in the command request, will be copied
|
||||
verbatim into the response. mpv does not intrepret the ``request_id`` in any
|
||||
way; it is solely for the use of the requester.
|
||||
|
||||
For example, this request:
|
||||
|
||||
::
|
||||
|
||||
{ "command": ["get_property", "time-pos"], "request_id": 100 }
|
||||
|
||||
Would generate this response:
|
||||
|
||||
::
|
||||
|
||||
{ "error": "success", "data": 1.468135, "request_id": 100 }
|
||||
|
||||
All commands, replies, and events are separated from each other with a line
|
||||
break character (``\n``).
|
||||
|
||||
|
||||
+39
-1
@@ -73,6 +73,10 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
Run the given command. This is similar to the commands used in input.conf.
|
||||
See `List of Input Commands`_.
|
||||
|
||||
By default, this will show something on the OSD (depending on the command),
|
||||
as if it was used in ``input.conf``. See `Input Command Prefixes`_ how
|
||||
to influence OSD usage per command.
|
||||
|
||||
Returns ``true`` on success, or ``nil, error`` on error.
|
||||
|
||||
``mp.commandv(arg1, arg2, ...)``
|
||||
@@ -94,6 +98,9 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
the ``expand-properties`` prefix, or the ``mp.get_property`` family of
|
||||
functions.
|
||||
|
||||
Unlike ``mp.command``, this will not use OSD by default either (except
|
||||
for some OSD-specific commands).
|
||||
|
||||
``mp.command_native(table [,def])``
|
||||
Similar to ``mp.commandv``, but pass the argument list as table. This has
|
||||
the advantage that in at least some cases, arguments can be passed as
|
||||
@@ -512,7 +519,7 @@ Example implementation::
|
||||
optionC = true,
|
||||
}
|
||||
read_options(options, "myscript")
|
||||
print(option.optionA)
|
||||
print(options.optionA)
|
||||
|
||||
|
||||
The config file will be stored in ``lua-settings/identifier.conf`` in mpv's user
|
||||
@@ -623,6 +630,10 @@ strictly part of the guaranteed API.
|
||||
On Windows, ``killed`` is only returned when the process has been
|
||||
killed by mpv as a result of ``cancellable`` being set to ``true``.
|
||||
|
||||
``killed_by_us``
|
||||
Set to ``true`` if the process has been killed by mpv as a result
|
||||
of ``cancellable`` being set to ``true``.
|
||||
|
||||
In all cases, ``mp.resume_all()`` is implicitly called.
|
||||
|
||||
``utils.parse_json(str [, trail])``
|
||||
@@ -683,6 +694,33 @@ List of events
|
||||
Happens after a file was unloaded. Typically, the player will load the
|
||||
next file right away, or quit if this was the last file.
|
||||
|
||||
The event has the ``reason`` field, which takes one of these values:
|
||||
|
||||
``eof``
|
||||
The file has ended. This can (but doesn't have to) include
|
||||
incomplete files or broken network connections under
|
||||
circumstances.
|
||||
|
||||
``stop``
|
||||
Playback was ended by a command.
|
||||
|
||||
``quit``
|
||||
Playback was ended by sending the quit command.
|
||||
|
||||
``error``
|
||||
An error happened. In this case, an ``error`` field is present with
|
||||
the error string.
|
||||
|
||||
``redirect``
|
||||
Happens with playlists and similar. Details see
|
||||
``MPV_END_FILE_REASON_REDIRECT`` in the C API.
|
||||
|
||||
``unknown``
|
||||
Unknown. Normally doesn't happen, unless the Lua API is out of sync
|
||||
with the C API. (Likewise, it could happen that your script gets
|
||||
reason strings that did not exist yet at the time your script was
|
||||
written.)
|
||||
|
||||
``file-loaded``
|
||||
Happens after a file was loaded and begins playback.
|
||||
|
||||
|
||||
+54
-18
@@ -116,11 +116,7 @@ o (also P)
|
||||
Show progression bar, elapsed time and total duration on the OSD.
|
||||
|
||||
O
|
||||
Toggle OSD states: none / seek / seek + timer / seek + timer + total time.
|
||||
|
||||
d
|
||||
Toggle frame dropping states: none / skip display / skip decoding (see
|
||||
``--framedrop``).
|
||||
Toggle OSD states between normal and playback time/duration.
|
||||
|
||||
v
|
||||
Toggle subtitle visibility.
|
||||
@@ -134,6 +130,9 @@ x and z
|
||||
l
|
||||
Set/clear A-B loop points. See ``ab_loop`` command for details.
|
||||
|
||||
L
|
||||
Toggle infinite looping.
|
||||
|
||||
Ctrl + and Ctrl -
|
||||
Adjust audio delay by +/- 0.1 seconds.
|
||||
|
||||
@@ -156,6 +155,10 @@ S
|
||||
Take a screenshot, without subtitles. (Whether this works depends on VO
|
||||
driver support.)
|
||||
|
||||
Ctrl s
|
||||
Take a screenshot, as the window shows it (with subtitles, OSD, and scaled
|
||||
video).
|
||||
|
||||
I
|
||||
Show filename on the OSD.
|
||||
|
||||
@@ -168,7 +171,7 @@ Shift+PGUP and Shift+PGDWN
|
||||
Seek backward or forward by 10 minutes. (This used to be mapped to
|
||||
PGUP/PGDWN without Shift.)
|
||||
|
||||
D
|
||||
d
|
||||
Activate/deactivate deinterlacer.
|
||||
|
||||
A
|
||||
@@ -273,7 +276,7 @@ spaces or characters like ``,`` or ``:``, you need to quote them:
|
||||
``mpv '--vo=opengl:icc-profile="file with spaces.icc",xv'``
|
||||
|
||||
Shells may actually strip some quotes from the string passed to the commandline,
|
||||
so the example quotes the string twice, ensuring that mpv recieves the ``"``
|
||||
so the example quotes the string twice, ensuring that mpv receives the ``"``
|
||||
quotes.
|
||||
|
||||
The ``[...]`` form of quotes wraps everything between ``[`` and ``]``. It's
|
||||
@@ -302,7 +305,7 @@ Suboptions passed to the client API are also subject to escaping. Using
|
||||
command line (but without shell processing of the string). Some options
|
||||
support passing values in a more structured way instead of flat strings, and
|
||||
can avoid the suboption parsing mess. For example, ``--vf`` supports
|
||||
``MPV_FORMAT_NODE``, which let's you pass suboptions as a nested data structure
|
||||
``MPV_FORMAT_NODE``, which lets you pass suboptions as a nested data structure
|
||||
of maps and arrays. (``--vo`` supports this in the same way, although this
|
||||
fact is undocumented.)
|
||||
|
||||
@@ -327,7 +330,17 @@ additionally wrapped in the fixed-length syntax, e.g. ``%n%string_of_length_n``
|
||||
Some mpv options interpret paths starting with ``~``. Currently, the prefix
|
||||
``~~/`` expands to the mpv configuration directory (usually ``~/.config/mpv/``).
|
||||
``~/`` expands to the user's home directory. (The trailing ``/`` is always
|
||||
required.)
|
||||
required.) There are the following paths as well:
|
||||
|
||||
================ ===============================================================
|
||||
Name Meaning
|
||||
================ ===============================================================
|
||||
``~~home/`` same as ``~~/``
|
||||
``~~global/`` the global config path, if available (not on win32)
|
||||
``~~osxbundle/`` the OSX bundle resource path (OSX only)
|
||||
``~~desktop/`` the path to the desktop (win32, OSX)
|
||||
================ ===============================================================
|
||||
|
||||
|
||||
Per-File Options
|
||||
----------------
|
||||
@@ -378,7 +391,8 @@ Location and Syntax
|
||||
You can put all of the options in configuration files which will be read every
|
||||
time mpv is run. The system-wide configuration file 'mpv.conf' is in your
|
||||
configuration directory (e.g. ``/etc/mpv`` or ``/usr/local/etc/mpv``), the
|
||||
user-specific one is ``~/.config/mpv/mpv.conf``.
|
||||
user-specific one is ``~/.config/mpv/mpv.conf``. For details and platform
|
||||
specifics see the `FILES`_ section.
|
||||
User-specific options override system-wide options and options given on the
|
||||
command line override either. The syntax of the configuration files is
|
||||
``option=<value>``; everything after a *#* is considered a comment. Options
|
||||
@@ -402,7 +416,7 @@ but option values still need to be quoted as a whole if it contains certain
|
||||
characters like spaces. A config entry can be quoted with ``"`` and ``'``,
|
||||
as well as with the fixed-length syntax (``%n%``) mentioned before. This is like
|
||||
passing the exact contents of the quoted string as command line option. C-style
|
||||
escapes are currently _not_ interpreted on this level, although some options to
|
||||
escapes are currently _not_ interpreted on this level, although some options do
|
||||
this manually. (This is a mess and should probably be changed at some point.)
|
||||
|
||||
Putting Command Line Options into the Configuration File
|
||||
@@ -512,13 +526,21 @@ listed.
|
||||
if there is audio "missing", or not enough frames can be dropped. Usually
|
||||
this will indicate a problem. (``total-avsync-change`` property.)
|
||||
- Encoding state in ``{...}``, only shown in encoding mode.
|
||||
- Display sync state. If display sync is active (``display-sync-active``
|
||||
property), this shows ``DS: +0.02598%``, where the number is the speed change
|
||||
factor applied to audio to achieve sync to display, expressed in percent
|
||||
deviation from 1.0 (``audio-speed-correction`` property). In sync modes which
|
||||
don't resample, this will always be ``+0.00000%``.
|
||||
- Missed frames, e.g. ``Missed: 4``. (``vo-missed-frame-count`` property.) Shows
|
||||
up in display sync mode only. This is incremented each time a frame took
|
||||
longer to display than intended.
|
||||
- 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
|
||||
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
|
||||
decoder-framedropping is enabled with the ``--framedrop`` options.
|
||||
decoder frame dropping is enabled with the ``--framedrop`` options.
|
||||
(``drop-frame-count`` property.)
|
||||
- Cache state, e.g. ``Cache: 2s+134KB``. Visible if the stream cache is enabled.
|
||||
The first value shows the amount of video buffered in the demuxer in seconds,
|
||||
@@ -549,13 +571,9 @@ PROTOCOLS
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directory directly.
|
||||
|
||||
``bdnav://[title][/device]``
|
||||
Play a Blu-Ray disc, with navigation features enabled. This feature is
|
||||
permanently experimental.
|
||||
|
||||
``dvd://[title|[starttitle]-endtitle][/device]`` ``--dvd-device=PATH``
|
||||
Play a DVD. If you want dvdnav menus, use ``dvd://menu``. If no title
|
||||
is given, the longest title is auto-selected.
|
||||
Play a DVD. DVD menus are not supported. If no title is given, the longest
|
||||
title is auto-selected.
|
||||
|
||||
``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same
|
||||
thing.
|
||||
@@ -600,6 +618,11 @@ PROTOCOLS
|
||||
``PATH`` itself should start with a third ``/`` to make the path an
|
||||
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.
|
||||
|
||||
``edl://[edl specification as in edl-mpv.rst]``
|
||||
Stitch together parts of multiple files and play them.
|
||||
|
||||
@@ -634,6 +657,7 @@ the ``pseudo-gui`` profile being predefined with the following contents:
|
||||
terminal=no
|
||||
force-window=yes
|
||||
idle=once
|
||||
screenshot-directory=~~desktop/
|
||||
|
||||
This follows the mpv config file format. To customize pseudo-GUI mode, you can
|
||||
put your own ``pseudo-gui`` profile into your ``mpv.conf``. This profile will
|
||||
@@ -788,6 +812,8 @@ If errors happen, the following exit codes can be returned:
|
||||
immediately after initialization.
|
||||
:3: There were some files that could be played, and some files which
|
||||
couldn't (using the definition of success from above).
|
||||
:4: Quit due to a signal, Ctrl+c in a VO window (by default), or from the
|
||||
default quit key bindings in encoding mode.
|
||||
|
||||
Note that quitting the player manually will always lead to exit code 0,
|
||||
overriding the exit code that would be returned normally. Also, the ``quit``
|
||||
@@ -796,6 +822,8 @@ input command can take an exit code: in this case, that exit code is returned.
|
||||
FILES
|
||||
=====
|
||||
|
||||
For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
|
||||
``/usr/local/etc/mpv/mpv.conf``
|
||||
mpv system-wide settings (depends on ``--prefix`` passed to configure - mpv
|
||||
in default configuration will use ``/usr/local/etc/mpv/`` as config
|
||||
@@ -857,6 +885,14 @@ Other config files (such as ``input.conf``) are in the same directory. See the
|
||||
The environment variable ``$MPV_HOME`` completely overrides these, like on
|
||||
UNIX.
|
||||
|
||||
If a directory named ``portable_config`` next to the mpv.exe exists, all
|
||||
config will be loaded from this directory only. Watch later config files are
|
||||
written to this directory as well. (This exists on Windows only and is redundant
|
||||
with ``$MPV_HOME``. However, since Windows is very scripting unfriendly, a
|
||||
wrapper script just setting ``$MPV_HOME``, like you could do it on other
|
||||
systems, won't work. ``portable_config`` is provided for convenience to get
|
||||
around this restriction.)
|
||||
|
||||
Config files located in the same directory as ``mpv.exe`` are loaded with
|
||||
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
|
||||
|
||||
+283
-98
@@ -129,6 +129,17 @@ Playback Control
|
||||
Specify which chapter to start playing at. Optionally specify which
|
||||
chapter to end playing at. Also see ``--start``.
|
||||
|
||||
``--playlist-pos=<no|index>``
|
||||
Set which file on the internal playlist to start playback with. The index
|
||||
is an integer, with 0 meaning the first file. The value ``no`` means that
|
||||
the selection of the entry to play is left to the playback resume mechanism
|
||||
(default). If an entry with the given index doesn't exist, the behavior is
|
||||
unspecified and might change in future mpv versions. The same applies if
|
||||
the playlist contains further playlists (don't expect any reasonable
|
||||
behavior). Passing a playlist file to mpv should work with this option,
|
||||
though. E.g. ``mpv playlist.m3u --playlist-pos=123`` will work as expected,
|
||||
as long as ``playlist.m3u`` does not link to further playlists.
|
||||
|
||||
``--playlist=<filename>``
|
||||
Play files according to a playlist file (Supports some common formats. If
|
||||
no format is detected, it will be treated as list of files, separated by
|
||||
@@ -348,7 +359,7 @@ Program Behavior
|
||||
``--idle=<no|yes|once>``
|
||||
Makes mpv wait idly instead of quitting when there is no file to play.
|
||||
Mostly useful in slave mode, where mpv can be controlled through input
|
||||
commands (see also ``--slave-broken``).
|
||||
commands.
|
||||
|
||||
``once`` will only idle at start and let the player close once the
|
||||
first playlist has finished playing back.
|
||||
@@ -457,14 +468,14 @@ Program Behavior
|
||||
(Default: ``best``)
|
||||
|
||||
``--ytdl-raw-options=<key>=<value>[,<key>=<value>[,...]]``
|
||||
Pass arbitraty options to youtube-dl. Parameter and argument should be
|
||||
Pass arbitrary options to youtube-dl. Parameter and argument should be
|
||||
passed as a key-value pair. Options without argument must include ``=``.
|
||||
|
||||
|
||||
There is no sanity checking so it's possible to break things (i.e.
|
||||
passing invalid parameters to youtube-dl).
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
|
||||
``--ytdl-raw-options=username=user,password=pass``
|
||||
``--ytdl-raw-options=force-ipv6=``
|
||||
|
||||
@@ -498,6 +509,10 @@ Video
|
||||
Do not play video. With some demuxers this may not work. In those cases
|
||||
you can try ``--vo=null`` instead.
|
||||
|
||||
mpv will try to download the audio only if media is streamed with
|
||||
youtube-dl, because it saves bandwidth. This is done by setting the ytdl_format
|
||||
to "bestaudio/best" in the ytdl_hook.lua script.
|
||||
|
||||
``--untimed``
|
||||
Do not sleep when outputting video frames. Useful for benchmarks when used
|
||||
with ``--no-audio.``
|
||||
@@ -524,7 +539,7 @@ Video
|
||||
Old, decoder-based framedrop mode. (This is the same as ``--framedrop=yes``
|
||||
in mpv 0.5.x and before.) This tells the decoder to skip frames (unless
|
||||
they are needed to decode future frames). May help with slow systems,
|
||||
but can produce unwatchably choppy output, or even freeze the display
|
||||
but can produce unwatchable choppy output, or even freeze the display
|
||||
completely. Not recommended.
|
||||
The ``--vd-lavc-framedrop`` option controls what frames to drop.
|
||||
<decoder+vo>
|
||||
@@ -555,6 +570,7 @@ Video
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux with Intel GPUs only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:vda: requires ``--vo=opengl`` (OS X only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (newer OS X only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
|
||||
@@ -575,6 +591,22 @@ Video
|
||||
codecs. See ``--hwdec-codecs`` to enable hardware decoding for more
|
||||
codecs.
|
||||
|
||||
``--hwdec-preload=<api>``
|
||||
This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
|
||||
hardware decoding OpenGL interop context, but without actually enabling
|
||||
hardware decoding itself (like ``--hwdec`` does).
|
||||
|
||||
If set to ``no`` (default), the ``--hwdec`` option is used.
|
||||
|
||||
For ``opengl``, if set, do not create the interop context on demand, but
|
||||
when the VO is created.
|
||||
|
||||
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
|
||||
to temporarily set the ``hwdec`` option just during OpenGL context
|
||||
initialization with ``mpv_opengl_cb_init_gl()``.
|
||||
|
||||
``--panscan=<0.0-1.0>``
|
||||
Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9
|
||||
video to make it fit a 4:3 display without black bands). The range
|
||||
@@ -601,6 +633,23 @@ Video
|
||||
Ignore aspect ratio information from video file and assume the video has
|
||||
square pixels. See also ``--video-aspect``.
|
||||
|
||||
``--video-aspect-method=<hybrid|bitstream|container>``
|
||||
This sets the default video aspect determination method (if the aspect is
|
||||
_not_ overridden by the user with ``--video-aspect`` or others).
|
||||
|
||||
:hybrid: Prefer the container aspect ratio. If the bitstream aspect
|
||||
switches mid-stream, switch to preferring the bitstream aspect.
|
||||
This is the default behavior in mpv and mplayer2.
|
||||
:container: Strictly prefer the container aspect ratio. This is apparently
|
||||
the default behavior with VLC, at least with Matroska.
|
||||
:bitstream: Strictly prefer the bitstream aspect ratio, unless the bitstream
|
||||
aspect ratio is not set. This is apparently the default behavior
|
||||
with XBMC/kodi, at least with Matroska.
|
||||
|
||||
Normally you should not set this. Try the ``container`` and ``bitstream``
|
||||
choices if you encounter video that has the wrong aspect ratio in mpv,
|
||||
but seems to be correct in other players.
|
||||
|
||||
``--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
|
||||
@@ -635,17 +684,17 @@ Video
|
||||
which means the value ``0`` would rotate the video according to the
|
||||
rotation metadata.)
|
||||
|
||||
``--video-stereo-mode=<mode>``
|
||||
``--video-stereo-mode=<no|mode>``
|
||||
Set the stereo 3D output mode (default: ``mono``). This is done by inserting
|
||||
the ``stereo3d`` conversion filter.
|
||||
|
||||
The pseudo-mode ``no`` disables automatic conversion completely.
|
||||
|
||||
The mode ``mono`` is an alias to ``ml``, which refers to the left frame in
|
||||
2D. This is the default, which means mpv will try to show 3D movies in 2D,
|
||||
instead of the mangled 3D image not intended for consumption (such as
|
||||
showing the left and right frame side by side, etc.).
|
||||
|
||||
The pseudo-mode ``none`` disables automatic conversion completely.
|
||||
|
||||
Use ``--video-stereo-mode=help`` to list all available modes. Check with
|
||||
the ``stereo3d`` filter documentation to see what the names mean. Note that
|
||||
some names refer to modes not supported by ``stereo3d`` - these modes can
|
||||
@@ -712,6 +761,10 @@ Video
|
||||
:top: top field first
|
||||
:bottom: bottom field first
|
||||
|
||||
.. note::
|
||||
|
||||
Setting either ``top`` or ``bottom`` will flag all frames as interlaced.
|
||||
|
||||
``--frames=<number>``
|
||||
Play/convert only first ``<number>`` video frames, then quit.
|
||||
|
||||
@@ -729,7 +782,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``. Note that the hardware
|
||||
By default this is set to ``h264,vc1,wmv3,hevc``. 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.
|
||||
|
||||
@@ -804,10 +857,11 @@ Video
|
||||
Set framedropping mode used with ``--framedrop`` (see skiploopfilter for
|
||||
available skip values).
|
||||
|
||||
``--vd-lavc-threads=<0-16>``
|
||||
``--vd-lavc-threads=<N>``
|
||||
Number of threads to use for decoding. Whether threading is actually
|
||||
supported depends on codec. 0 means autodetect number of cores on the
|
||||
machine and use that, up to the maximum of 16 (default: 0).
|
||||
supported depends on codec (default: 0). 0 means autodetect number of cores
|
||||
on the machine and use that, up to the maximum of 16. You can set more than
|
||||
16 threads manually.
|
||||
|
||||
|
||||
|
||||
@@ -834,7 +888,7 @@ Audio
|
||||
Note that many AOs have a ``device`` sub-option, which overrides the
|
||||
device selection of this option (but not the audio output selection).
|
||||
Likewise, forcing an AO with ``--ao`` will override the audio output
|
||||
selection of ``--audio-device`` (but not the device selecton).
|
||||
selection of ``--audio-device`` (but not the device selection).
|
||||
|
||||
Currently not implemented for most AOs.
|
||||
|
||||
@@ -851,6 +905,25 @@ Audio
|
||||
``--af-clr`` exist to modify a previously specified list, but you
|
||||
should not need these for typical use.
|
||||
|
||||
``--audio-spdif=<codecs>``
|
||||
List of codecs for which compressed audio passthrough should be used. This
|
||||
works for both classic S/PDIF and HDMI.
|
||||
|
||||
Possible codecs are ``ac3``, ``dts``, ``dts-hd``. Multiple codecs can be
|
||||
specified by separating them with ``,``. ``dts`` refers to low bitrate DTS
|
||||
core, while ``dts-hd`` refers to DTS MA (receiver and OS support varies).
|
||||
You should only use either ``dts`` or ``dts-hd`` (if both are specified,
|
||||
and ``dts`` comes first, only ``dts`` will be used).
|
||||
|
||||
In general, all codecs in the ``spdif`` family listed with ``--ad=help``
|
||||
are supported in theory.
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
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.
|
||||
|
||||
``--ad=<[+|-]family1:(*|decoder1),[+|-]family2:(*|decoder2),...[-]>``
|
||||
Specify a priority list of audio decoders to be used, according to their
|
||||
family and decoder name. Entries like ``family:*`` prioritize all decoders
|
||||
@@ -878,9 +951,21 @@ Audio
|
||||
``--ad=help``
|
||||
List all available decoders.
|
||||
|
||||
``--volume=<-1-100>``
|
||||
Set the startup volume. A value of -1 (the default) will not change the
|
||||
volume. See also ``--softvol``.
|
||||
.. admonition:: Warning
|
||||
|
||||
Enabling compressed audio passthrough (AC3 and DTS via SPDIF/HDMI) with
|
||||
this option is deprecated. Use ``--audio-spdif`` instead.
|
||||
|
||||
``--volume=<value>``
|
||||
Set the startup volume. 0 means silence, 100 means no volume reduction or
|
||||
amplification. A value of -1 (the default) will not change the volume. See
|
||||
also ``--softvol``.
|
||||
|
||||
.. note::
|
||||
|
||||
This was changed after the mpv 0.9 release. Before that, 100 actually
|
||||
meant maximum volume. At the same time, the volume scale was made cubic,
|
||||
so the old values won't match up with the new ones anyway.
|
||||
|
||||
``--audio-delay=<sec>``
|
||||
Audio delay in seconds (positive or negative float value). Positive values
|
||||
@@ -942,17 +1027,12 @@ Audio
|
||||
welcome. A full list of AVOptions can be found in the FFmpeg manual.
|
||||
|
||||
``--ad-spdif-dtshd=<yes|no>``, ``--dtshd``, ``--no-dtshd``
|
||||
When using DTS pass-through, output any DTS-HD track as-is.
|
||||
With ``ad-spdif-dtshd=no`` (the default), only the DTS Core parts will be
|
||||
output.
|
||||
If DTS is passed through, use DTS-HD.
|
||||
|
||||
DTS-HD tracks can be sent over HDMI but not over the original
|
||||
coax/TOSLINK S/PDIF system.
|
||||
.. admonition:: Warning
|
||||
|
||||
Some receivers don't accept DTS core-only when ``--ad-spdif-dtshd=yes`` is
|
||||
used, even though they accept DTS-HD.
|
||||
|
||||
``--dtshd`` and ``--no-dtshd`` are deprecated aliases.
|
||||
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
|
||||
@@ -981,6 +1061,14 @@ Audio
|
||||
If the channel layout of the media file (i.e. the decoder) and the AO's
|
||||
channel layout don't match, mpv will attempt to insert a conversion filter.
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
Using ``auto`` can cause issues when using audio over HDMI. The OS will
|
||||
typically report all channel layouts that _can_ go over HDMI, even if
|
||||
the receiver does not support them. If a receiver gets an unsupported
|
||||
channel layout, random things can happen, such as dropping the
|
||||
additional channels, or adding noise.
|
||||
|
||||
``--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
|
||||
@@ -1049,19 +1137,9 @@ Audio
|
||||
their start timestamps differ, and then video timing is gradually adjusted
|
||||
if necessary to reach correct synchronization later.
|
||||
|
||||
``--softvol-max=<10.0-10000.0>``
|
||||
Set the maximum amplification level in percent (default: 200). A value of
|
||||
200 will allow you to adjust the volume up to a maximum of double the
|
||||
current level. With values below 100 the initial volume (which is 100%)
|
||||
will be above the maximum, which e.g. the OSD cannot display correctly.
|
||||
|
||||
.. admonition:: Note
|
||||
|
||||
The maximum value of ``--volume`` as well as the ``volume`` property
|
||||
is always 100. Likewise, the volume OSD bar always goes from 0 to 100.
|
||||
This means that with ``--softvol-max=200``, ``--volume=100`` sets
|
||||
maximum amplification, i.e. amplify by 200%. The default volume (no
|
||||
change in volume) will be ``50`` in this case.
|
||||
``--softvol-max=<100.0-1000.0>``
|
||||
Set the maximum amplification level in percent (default: 130). A value of
|
||||
130 will allow you to adjust the volume up to about double the normal level.
|
||||
|
||||
``--audio-file-auto=<no|exact|fuzzy|all>``, ``--no-audio-file-auto``
|
||||
Load additional audio files matching the video filename. The parameter
|
||||
@@ -1136,7 +1214,7 @@ Subtitles
|
||||
the top of the screen) alongside the normal subtitle, and provides a way
|
||||
to render two subtitles at once.
|
||||
|
||||
there are some caveats associated with this feature. For example, bitmap
|
||||
There are some caveats associated with this feature. For example, bitmap
|
||||
subtitles will always be rendered in their usual position, so selecting a
|
||||
bitmap subtitle as secondary subtitle will result in overlapping subtitles.
|
||||
Secondary subtitles are never shown on the terminal if video is disabled.
|
||||
@@ -1352,6 +1430,18 @@ Subtitles
|
||||
|
||||
Disabled by default.
|
||||
|
||||
``--stretch-image-subs-to-screen=<yes|no>``
|
||||
Stretch DVD and other image subtitles to the screen, ignoring the video
|
||||
margins. This has a similar effect as ``--sub-use-margins`` for text
|
||||
subtitles, except that the text itself will be stretched, not only just
|
||||
repositioned. (At least in general it is unavoidable, as an image bitmap
|
||||
can in theory consist of a single bitmap covering the whole screen, and
|
||||
the player won't know where exactly the text parts are located.)
|
||||
|
||||
This option does not display subtitles correctly. Use with care.
|
||||
|
||||
Disabled by default.
|
||||
|
||||
``--sub-ass``, ``--no-sub-ass``
|
||||
Render ASS subtitles natively (enabled by default).
|
||||
|
||||
@@ -1381,10 +1471,11 @@ Subtitles
|
||||
|
||||
``--sub-codepage=<codepage>``
|
||||
If your system supports ``iconv(3)``, you can use this option to specify
|
||||
the subtitle codepage. By default, ENCA will be used to guess the charset.
|
||||
If mpv is not compiled with ENCA, ``UTF-8:UTF-8-BROKEN`` is the default,
|
||||
which means it will try to use UTF-8, otherwise the ``UTF-8-BROKEN``
|
||||
pseudo codepage (see below).
|
||||
the subtitle codepage. By default, uchardet will be used to guess the
|
||||
charset. If mpv is not compiled with uchardet, enca will be used.
|
||||
If mpv is compiled with neither uchardet nor enca, ``UTF-8:UTF-8-BROKEN``
|
||||
is the default, which means it will try to use UTF-8, otherwise the
|
||||
``UTF-8-BROKEN`` pseudo codepage (see below).
|
||||
|
||||
The default value for this option is ``auto``, whose actual effect depends
|
||||
on whether ENCA is compiled.
|
||||
@@ -1434,6 +1525,12 @@ Subtitles
|
||||
mode. Use ``--sub-codepage=guess:help`` to get a list of
|
||||
languages subject to the same caveat as with ENCA above.
|
||||
|
||||
If the player was compiled with uchardet support you can use it with:
|
||||
|
||||
``--sub-codepage=uchardet``
|
||||
|
||||
This mode doesn't take language or fallback codepage.
|
||||
|
||||
``--sub-fix-timing``, ``--no-sub-fix-timing``
|
||||
By default, external text subtitles are preprocessed to remove minor gaps
|
||||
or overlaps between subtitles (if the difference is smaller than 200 ms,
|
||||
@@ -1586,7 +1683,7 @@ Window
|
||||
file.mkv normally, then fail to open ``/dev/null``, then exit). (In
|
||||
mpv 0.8.0, ``always`` was introduced, which restores the old behavior.)
|
||||
|
||||
``--force-window``
|
||||
``--force-window=<yes|no|immediate>``
|
||||
Create a video output window even if there is no video. This can be useful
|
||||
when pretending that mpv is a GUI application. Currently, the window
|
||||
always has the size 640x480, and is subject to ``--geometry``,
|
||||
@@ -1598,7 +1695,9 @@ Window
|
||||
window placement still works if the video size is different from the
|
||||
``--force-window`` default window size). This can be a problem if
|
||||
initialization doesn't work perfectly, such as when opening URLs with
|
||||
bad network connection, or opening broken video files.
|
||||
bad network connection, or opening broken video files. The ``immediate``
|
||||
mode can be used to create the window always on program start, but this
|
||||
may cause other issues.
|
||||
|
||||
``--ontop``
|
||||
Makes the player window stay on top of other windows.
|
||||
@@ -1849,7 +1948,7 @@ Window
|
||||
``intptr_t``. mpv will create its own window, and set the wid window as
|
||||
parent, like with X11.
|
||||
|
||||
On OSX/Cocoa. the ID is interpreted as ``NSView*``. Pass it as value cast
|
||||
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
|
||||
support window embedding of foreign processes, this works only with libmpv,
|
||||
and will crash when used from the command line.
|
||||
@@ -1943,7 +2042,7 @@ Disc Devices
|
||||
(Never) accept imperfect data reconstruction.
|
||||
|
||||
``--cdda-cdtext=<yes|no>``
|
||||
Print CD text. This is disabled by default, because it ruins perfomance
|
||||
Print CD text. This is disabled by default, because it ruins performance
|
||||
with CD-ROM drives for unknown reasons.
|
||||
|
||||
``--dvd-speed=<speed>``
|
||||
@@ -2074,7 +2173,7 @@ Demuxer
|
||||
seeks only.
|
||||
|
||||
You can use the ``--demuxer-mkv-subtitle-preroll-secs`` option to specify
|
||||
how mach data the demuxer should pre-read at most in order to find subtitle
|
||||
how much data the demuxer should pre-read at most in order to find subtitle
|
||||
packets that may overlap. Setting this to 0 will effectively disable this
|
||||
preroll mechanism. Setting a very large value can make seeking very slow,
|
||||
and an extremely large value would completely reread the entire file from
|
||||
@@ -2099,15 +2198,20 @@ Demuxer
|
||||
``--demuxer-mkv-subtitle-preroll-secs=<value>``
|
||||
See ``--demuxer-mkv-subtitle-preroll``.
|
||||
|
||||
``--demuxer-mkv-probe-video-duration``
|
||||
``--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
|
||||
for compatibility with Haali only. In this mode, it's possible that opening
|
||||
will be slower (especially when playing over http), or that behavior with
|
||||
broken files is much worse. So don't use this option.
|
||||
|
||||
The ``yes`` mode merely uses the index and reads a small number of blocks
|
||||
from the end of the file. The ``full`` mode actually traverses the entire
|
||||
file and can make a reliable estimate even without an index present (such
|
||||
as partial files).
|
||||
|
||||
``--demuxer-mkv-fix-timestamps=<yes|no>``
|
||||
Fix rounded Matroska timestamps (enabled by default). Matroska usually
|
||||
Fix rounded Matroska timestamps (disabled by default). Matroska usually
|
||||
stores timestamps rounded to milliseconds. This means timestamps jitter
|
||||
by some amount around the intended timestamp. mpv can correct the timestamps
|
||||
based on the framerate value stored in the file: the timestamp is rounded
|
||||
@@ -2140,7 +2244,7 @@ Demuxer
|
||||
Play a raw YUV sample::
|
||||
|
||||
mpv sample-720x576.yuv --demuxer=rawvideo \
|
||||
--demuxer-rawvideo=w=720:h=576
|
||||
--demuxer-rawvideo-w=720 --demuxer-rawvideo-h=576
|
||||
|
||||
``--demuxer-rawvideo-format=<value>``
|
||||
Color space (fourcc) in hex or string for ``--demuxer=rawvideo``
|
||||
@@ -2158,6 +2262,20 @@ Demuxer
|
||||
``--demuxer-rawvideo-size=<value>``
|
||||
Frame size in bytes when using ``--demuxer=rawvideo``.
|
||||
|
||||
``--demuxer-max-packets=<packets>``, ``--demuxer-max-bytes=<bytes>``
|
||||
This controls how much the demuxer is allowed to buffer ahead. The demuxer
|
||||
will normally try to read ahead as much as necessary, or as much is
|
||||
requested with ``--demuxer-readahead-secs``. The ``--demuxer-max-...``
|
||||
options can be used to restrict the maximum readahead. This limits excessive
|
||||
readahead in case of broken files or desynced playback. The demuxer will
|
||||
stop reading additional packets as soon as one of the limits is reached.
|
||||
(The limits still can be slightly overstepped due to technical reasons.)
|
||||
|
||||
Set these limits highher if you get a packet queue overflow warning, and
|
||||
you think normal playback would be possible with a larger packet queue.
|
||||
|
||||
See ``--list-options`` for defaults and value range.
|
||||
|
||||
``--demuxer-thread=<yes|no>``
|
||||
Run the demuxer in a separate thread, and let it prefetch a certain amount
|
||||
of packets (default: yes). Having this enabled may lead to smoother
|
||||
@@ -2176,21 +2294,11 @@ Demuxer
|
||||
(This value tends to be fuzzy, because many file formats don't store linear
|
||||
timestamps.)
|
||||
|
||||
``--demuxer-readahead-packets=<packets>``
|
||||
If ``--demuxer-thread`` is enabled, this controls how much the demuxer
|
||||
should buffer ahead. As long as the number of packets in the packet queue
|
||||
doesn't exceed ``--demuxer-readahead-packets``, and the total number of
|
||||
bytes doesn't exceed ``--demuxer-readahead-bytes``, the thread keeps
|
||||
reading ahead.
|
||||
|
||||
Note that if you set these options near the maximum, you might get a
|
||||
packet queue overflow warning.
|
||||
|
||||
See ``--list-options`` for defaults and value range.
|
||||
|
||||
``--demuxer-readahead-bytes=<bytes>``
|
||||
See ``--demuxer-readahead-packets``.
|
||||
|
||||
``--force-seekable=<yes|no>``
|
||||
If the player thinks that the media is not seekable (e.g. playing from a
|
||||
pipe, or it's a http stream with a server that doesn't support range
|
||||
requests), seeking will be disabled. This option can forcibly enable it.
|
||||
For seeks within the cache, there's a good chance of success.
|
||||
|
||||
Input
|
||||
-----
|
||||
@@ -2247,8 +2355,6 @@ Input
|
||||
This can also specify a direct file descriptor with ``fd://N`` (UNIX only).
|
||||
In this case, JSON replies will be written if the FD is writable.
|
||||
|
||||
See also ``--slave-broken``.
|
||||
|
||||
.. note::
|
||||
|
||||
When the given file is a FIFO mpv opens both ends, so you can do several
|
||||
@@ -2562,14 +2668,19 @@ Screenshot
|
||||
|
||||
Note that not all formats are supported.
|
||||
|
||||
Default: ``yes``.
|
||||
Default: ``no``.
|
||||
|
||||
``--screenshot-high-bit-depth=<yes|no>``
|
||||
If possible, write screenshots with a bit depth similar to the source
|
||||
video (default: yes). This is interesting in particular for PNG, as this
|
||||
sometimes triggers writing 16 bit PNGs with huge file sizes.
|
||||
|
||||
``--screenshot-template=<template>``
|
||||
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 ``shot%n``, which results in filenames like
|
||||
``shot0012.png`` for example.
|
||||
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
|
||||
specify a directory location where screenshots should be saved.
|
||||
@@ -2612,7 +2723,7 @@ Screenshot
|
||||
|
||||
.. note::
|
||||
|
||||
This is a simple way for getting unique per-frame timestamps. Frame
|
||||
This is a simple way for getting unique per-frame timestamps. (Frame
|
||||
numbers would be more intuitive, but are not easily implementable
|
||||
because container formats usually use time stamps for identifying
|
||||
frames.)
|
||||
@@ -2651,9 +2762,26 @@ Screenshot
|
||||
``%%``
|
||||
Replaced with the ``%`` character itself.
|
||||
|
||||
``--screenshot-directory=<path>``
|
||||
Store screenshots in this directory. This path is joined with the filename
|
||||
generated by ``--screenshot-template``. If the template filename is already
|
||||
absolute, the directory is ignored.
|
||||
|
||||
If the directory does not exist, it is created on the first screenshot. If
|
||||
it is not a directory, an error is generated when trying to write a
|
||||
screenshot.
|
||||
|
||||
This option is not set by default, and thus will write screenshots to the
|
||||
directory from which mpv was started. In pseudo-gui mode
|
||||
(see `PSEUDO GUI MODE`_), this is set to the desktop.
|
||||
|
||||
``--screenshot-jpeg-quality=<0-100>``
|
||||
Set the JPEG quality level. Higher means better quality. The default is 90.
|
||||
|
||||
``--screenshot-jpeg-source-chroma=<yes|no>``
|
||||
Write JPEG files with the same chroma subsampling as the video
|
||||
(default: yes). If disabled, the libjpeg default is used.
|
||||
|
||||
``--screenshot-png-compression=<0-9>``
|
||||
Set the PNG compression level. Higher means better compression. This will
|
||||
affect the file size of the written screenshot file and the time it takes
|
||||
@@ -2984,13 +3112,13 @@ Cache
|
||||
seeking, such as MP4.
|
||||
|
||||
Note that half the cache size will be used to allow fast seeking back. This
|
||||
is also the reason why a full cache is usually reported as 50% full. The
|
||||
cache fill display does not include the part of the cache reserved for
|
||||
seeking back. Likewise, when starting a file the cache will be at 100%,
|
||||
because no space is reserved for seeking back yet.
|
||||
is also the reason why a full cache is usually not reported as 100% full.
|
||||
The cache fill display does not include the part of the cache reserved for
|
||||
seeking back. The actual maximum percentage will usually be the ratio
|
||||
between readahead and backbuffer sizes.
|
||||
|
||||
``--cache-default=<kBytes|no>``
|
||||
Set the size of the cache in kilobytes (default: 150000 KB). Using ``no``
|
||||
Set the size of the cache in kilobytes (default: 75000 KB). Using ``no``
|
||||
will not automatically enable the cache e.g. when playing from a network
|
||||
stream. Note that using ``--cache`` will always override this option.
|
||||
|
||||
@@ -3010,6 +3138,12 @@ Cache
|
||||
on the situation, either of these might be slower than the other method.
|
||||
This option allows control over this.
|
||||
|
||||
``--cache-backbuffer=<kBytes>``
|
||||
Size of the cache back buffer (default: 75000 KB). This will add to the total
|
||||
cache size, and reserved the amount for seeking back. The reserved amount
|
||||
will not be used for readahead, and instead preserves already read data to
|
||||
enable fast seeking back.
|
||||
|
||||
``--cache-file=<TMP|path>``
|
||||
Create a cache file on the filesystem.
|
||||
|
||||
@@ -3124,7 +3258,7 @@ Network
|
||||
network transport when playing ``rtsp://...`` URLs. The value ``lavf``
|
||||
leaves the decision to libavformat.
|
||||
|
||||
``--hls-bitrate=<no|min|max>``
|
||||
``--hls-bitrate=<no|min|max|<rate>>``
|
||||
If HLS streams are played, this option controls what streams are selected
|
||||
by default. The option allows the following parameters:
|
||||
|
||||
@@ -3133,6 +3267,9 @@ Network
|
||||
:min: Pick the streams with the lowest bitrate.
|
||||
:max: Same, but highest bitrate. (Default.)
|
||||
|
||||
Additionally, if the option is a number, the stream with the highest rate
|
||||
equal or below the option value is selected.
|
||||
|
||||
The bitrate as used is sent by the server, and there's no guarantee it's
|
||||
actually meaningful.
|
||||
|
||||
@@ -3258,6 +3395,72 @@ Miscellaneous
|
||||
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.
|
||||
|
||||
``--video-sync=<audio|...>``
|
||||
How the player synchronizes audio and video.
|
||||
|
||||
The modes starting with ``display-`` try to output video frames completely
|
||||
synchronously to the display, using the detected display vertical refresh
|
||||
rate as a hint how fast frames will be displayed on average. These modes
|
||||
change video speed slightly to match the display. See ``--video-sync-...``
|
||||
options for fine tuning. The robustness of this mode is further reduced by
|
||||
making a some idealized assumptions, which may not always apply in reality.
|
||||
Behavior can depend on the VO and the system's video and audio drivers.
|
||||
Media files must use constant framerate. Section-wise VFR might work as well
|
||||
with some container formats (but not e.g. mkv). If the sync code detects
|
||||
severe A/V desync, or the framerate cannot be detected, the player
|
||||
automatically reverts to ``audio`` mode for some time or permanently.
|
||||
|
||||
The modes with ``desync`` in their names do not attempt to keep audio/video
|
||||
in sync. They will slowly (or quickly) desync, until e.g. the next seek
|
||||
happens. These modes are meant for testing, not serious use.
|
||||
|
||||
:audio: Time video frames to audio. This is the most robust
|
||||
mode, because the player doesn't have to assume anything
|
||||
about how the display behaves. The disadvantage is that
|
||||
it can lead to occasional frame drops or repeats. If
|
||||
audio is disabled, this uses the system clock. This is
|
||||
the default mode.
|
||||
:display-resample: Resample audio to match the video. This mode will also
|
||||
try to adjust audio speed to compensate for other drift.
|
||||
(This means it will play the audio at a different speed
|
||||
every once in a while to reduce the A/V difference.)
|
||||
:display-resample-vdrop: Resample audio to match the video. Drop video
|
||||
frames to compensate for drift.
|
||||
:display-resample-desync: Like the previous mode, but no A/V compensation.
|
||||
:display-vdrop: Drop or repeat video frames to compensate desyncing
|
||||
video. (Although it should have the same effects as
|
||||
``audio``, the implementation is very different.)
|
||||
:display-desync: Sync video to display, and let audio play on its own.
|
||||
:desync: Sync video according to system clock, and let audio play
|
||||
on its own.
|
||||
|
||||
``--video-sync-max-video-change=<value>``
|
||||
Maximum speed difference in percent that is applied to video with
|
||||
``--video-sync=display-...`` (default: 1). Display sync mode will be
|
||||
disabled if the monitor and video refresh way do not match within the
|
||||
given range. It tries multiples as well: playing 30 fps video on a 60 Hz
|
||||
screen will duplicate every second frame. Playing 24 fps video on a 60 Hz
|
||||
screen will play video in a 2-3-2-3-... pattern.
|
||||
|
||||
The default settings are not loose enough to speed up 23.976 fps video to
|
||||
25 fps. We consider the pitch change too extreme to allow this behavior
|
||||
by default. Set this option to a value of ``5`` to enable it.
|
||||
|
||||
Note that in the ``--video-sync=display-resample`` mode, audio speed will
|
||||
additionally be changed by a small amount if necessary for A/V sync. See
|
||||
``--video-sync-max-audio-change``.
|
||||
|
||||
``--video-sync-max-audio-change=<value>``
|
||||
Maximum *additional* speed difference in percent that is applied to audio
|
||||
with ``--video-sync=display-...`` (default: 0.125). Normally, the player
|
||||
play the audio at the speed of the video. But if the difference between
|
||||
audio and video position is too high, e.g. due to drift or other timing
|
||||
errors, it will attempt to speed up or slow down audio by this additional
|
||||
factor. Too low values could lead to video frame dropping or repeating if
|
||||
the A/V desync cannot be compensated, too high values could lead to chaotic
|
||||
frame dropping due to the audio "overshooting" and skipping multiple video
|
||||
frames before the sync logic can react.
|
||||
|
||||
``--mf-fps=<value>``
|
||||
Framerate used when decoding from multiple PNG or JPEG files with ``mf://``
|
||||
(default: 1).
|
||||
@@ -3305,7 +3508,7 @@ Miscellaneous
|
||||
you should not need to change this option.
|
||||
|
||||
:decoder: Use decoder reordering functionality. Unlike in classic MPlayer
|
||||
and mplayer2, this includes a dTS fallback. (Default.)
|
||||
and mplayer2, this includes a DTS fallback. (Default.)
|
||||
:sort: Maintain a buffer of unused pts values and use the lowest value
|
||||
for the frame.
|
||||
:auto: Try to pick a working mode from the ones above automatically.
|
||||
@@ -3313,25 +3516,7 @@ Miscellaneous
|
||||
You can also try to use ``--no-correct-pts`` for files with completely
|
||||
broken timestamps.
|
||||
|
||||
``--media-title=<string>``
|
||||
``--force-media-title=<string>``
|
||||
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``.
|
||||
|
||||
``--slave-broken``
|
||||
Switches on the old slave mode. This is for testing only, and incompatible
|
||||
to the removed ``--slave`` switch.
|
||||
|
||||
.. attention::
|
||||
Changes incompatible to slave mode applications have been made. In
|
||||
particular, the status line output was changed, which is used by some
|
||||
applications to determine the current playback position. This switch
|
||||
has been renamed to prevent these applications from working with this
|
||||
version of mpv, because it would lead to buggy and confusing behavior
|
||||
only. Moreover, the slave mode protocol is so horribly bad that it
|
||||
should not be used for new programs, nor should existing programs
|
||||
attempt to adapt to the changed output and use the ``--slave-broken``
|
||||
switch. Instead, a new, saner protocol should be developed (and will be,
|
||||
if there is enough interest).
|
||||
|
||||
This affects most third-party GUI frontends.
|
||||
+20
-11
@@ -5,7 +5,7 @@ The On Screen Controller (short: OSC) is a minimal GUI integrated with mpv to
|
||||
offer basic mouse-controllability. It is intended to make interaction easier
|
||||
for new users and to enable precise and direct seeking.
|
||||
|
||||
The OSC is enabled by default if mpv was compiled with lua support. It can be
|
||||
The OSC is enabled by default if mpv was compiled with Lua support. It can be
|
||||
disabled entirely using the ``--osc=no`` option.
|
||||
|
||||
Using the OSC
|
||||
@@ -169,34 +169,35 @@ Configurable Options
|
||||
|
||||
``showwindowed``
|
||||
| Default: yes
|
||||
| Show OSC when windowed?
|
||||
| Enable the OSC when windowed
|
||||
|
||||
``showfullscreen``
|
||||
| Default: yes
|
||||
| Show OSC when fullscreen?
|
||||
| Enable the OSC when fullscreen
|
||||
|
||||
``scalewindowed``
|
||||
| Default: 1
|
||||
| Scaling of the controller when windowed
|
||||
| Default: 1.0
|
||||
| Scale factor of the OSC when windowed
|
||||
|
||||
``scalefullscreen``
|
||||
| Default: 1
|
||||
| Scaling of the controller when fullscreen
|
||||
| Default: 1.0
|
||||
| Scale factor of the OSC when fullscreen
|
||||
|
||||
``scaleforcedwindow``
|
||||
| Default: 2
|
||||
| Scaling of the controller when rendered on a forced (dummy) window
|
||||
| Default: 2.0
|
||||
| Scale factor of the OSC when rendered on a forced (dummy) window
|
||||
|
||||
``vidscale``
|
||||
| Default: yes
|
||||
| Scale the controller with the video?
|
||||
| Scale the OSC with the video
|
||||
| ``no`` tries to keep the OSC size constant as much as the window size allows
|
||||
|
||||
``valign``
|
||||
| Default: 0.8
|
||||
| Vertical alignment, -1 (top) to 1 (bottom)
|
||||
|
||||
``halign``
|
||||
| Default: 0
|
||||
| Default: 0.0
|
||||
| Horizontal alignment, -1 (left) to 1 (right)
|
||||
|
||||
``boxalpha``
|
||||
@@ -234,6 +235,14 @@ Configurable Options
|
||||
| Default: slider
|
||||
| Sets the style of the seekbar, slider (diamond marker) or bar (fill)
|
||||
|
||||
``timetotal``
|
||||
| Default: no
|
||||
| Show total time instead of time remaining
|
||||
|
||||
``timems``
|
||||
| Default: no
|
||||
| Display timecodes with milliseconds
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+28
-77
@@ -17,6 +17,10 @@ normal filter parameters.
|
||||
|
||||
To get a full list of available video filters, see ``--vf=help``.
|
||||
|
||||
Also, keep in mind that most actual filters are available via the ``lavfi``
|
||||
wrapper, which gives you access to most of libavfilter's filters. This
|
||||
includes all filters that have been ported from MPlayer to libavfilter.
|
||||
|
||||
Video filters are managed in lists. There are a few commands to manage the
|
||||
filter list.
|
||||
|
||||
@@ -97,7 +101,7 @@ Available filters are:
|
||||
|
||||
All parameters are optional.
|
||||
|
||||
``<w>,<h>``
|
||||
``<w>:<h>``
|
||||
scaled width/height (default: original width/height)
|
||||
|
||||
:0: scaled d_width/d_height
|
||||
@@ -152,7 +156,7 @@ Available filters are:
|
||||
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
|
||||
just affects what later scalers (software or hardware) will do when
|
||||
auto-scaling to correct aspect.
|
||||
auto-scaling to the correct aspect.
|
||||
|
||||
``<w>,<h>``
|
||||
New display width and height.
|
||||
@@ -198,7 +202,7 @@ Available filters are:
|
||||
``<aspect>``
|
||||
Force an aspect ratio.
|
||||
|
||||
``format[=fmt[:outfmt]]``
|
||||
``format=fmt=<value>:colormatrix=<value>:...``
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Use together with the scale filter for a real conversion.
|
||||
|
||||
@@ -409,41 +413,6 @@ Available filters are:
|
||||
``'--vf=lavfi=yadif:o="threads=2,thread_type=slice"'``
|
||||
forces a specific threading configuration.
|
||||
|
||||
``noise[=<strength>[:averaged][:pattern][:temporal][:uniform][:hq]``
|
||||
Adds noise.
|
||||
|
||||
``strength``
|
||||
Set the noise for all components. If you want different strength
|
||||
values for luma and chroma, use libavfilter's noise filter directly
|
||||
(using ``--vf=lavfi=[noise=...]``), or tell the libavfilter developers
|
||||
to stop being stupid.
|
||||
|
||||
``averaged``
|
||||
averaged temporal noise (smoother, but a lot slower)
|
||||
|
||||
``pattern``
|
||||
mix random noise with a (semi)regular pattern
|
||||
|
||||
``temporal``
|
||||
temporal noise (noise pattern changes between frames)
|
||||
|
||||
``uniform``
|
||||
uniform noise (Gaussian otherwise)
|
||||
|
||||
``hqdn3d[=luma_spatial:chroma_spatial:luma_tmp:chroma_tmp]``
|
||||
This filter aims to reduce image noise producing smooth images and making
|
||||
still images really still (This should enhance compressibility.).
|
||||
|
||||
``<luma_spatial>``
|
||||
spatial luma strength (default: 4)
|
||||
``<chroma_spatial>``
|
||||
spatial chroma strength (default: 3)
|
||||
``<luma_tmp>``
|
||||
luma temporal strength (default: 6)
|
||||
``<chroma_tmp>``
|
||||
chroma temporal strength (default:
|
||||
``luma_tmp*chroma_spatial/luma_spatial``)
|
||||
|
||||
``eq[=gamma:contrast:brightness:saturation:rg:gg:bg:weight]``
|
||||
Software equalizer that uses lookup tables (slow), allowing gamma correction
|
||||
in addition to simple brightness and contrast adjustment. The parameters are
|
||||
@@ -470,23 +439,6 @@ Available filters are:
|
||||
and just plain white. A value of 0.0 turns the gamma correction all
|
||||
the way down while 1.0 leaves it at its full strength (default: 1.0).
|
||||
|
||||
``unsharp[=lx:ly:la:cx:cy:ca]``
|
||||
unsharp mask / Gaussian blur
|
||||
|
||||
``l`` is for the luma component, ``c`` for the chroma component. ``x``/``y``
|
||||
is the filter size. ``a`` is the amount.
|
||||
|
||||
``lx``, ``ly``, ``cx``, ``cy``
|
||||
width and height of the matrix, odd sized in both directions (min =
|
||||
3:3, max = 13:11 or 11:13, usually something between 3:3 and 7:7)
|
||||
|
||||
``la``, ``ca``
|
||||
Relative amount of sharpness/blur to add to the image (a sane range
|
||||
should be -1.5-1.5).
|
||||
|
||||
:<0: blur
|
||||
:>0: sharpen
|
||||
|
||||
``pullup[=jl:jr:jt:jb:sb:mp]``
|
||||
Pulldown reversal (inverse telecine) filter, capable of handling mixed
|
||||
hard-telecine, 24000/1001 fps progressive, and 30000/1001 fps progressive
|
||||
@@ -518,7 +470,7 @@ Available filters are:
|
||||
video. The main purpose of setting ``mp`` to a chroma plane is to reduce
|
||||
CPU load and make pullup usable in realtime on slow machines.
|
||||
|
||||
``yadif=[mode[:enabled=yes|no]]``
|
||||
``yadif=[mode:interlaced-only]``
|
||||
Yet another deinterlacing filter
|
||||
|
||||
``<mode>``
|
||||
@@ -527,10 +479,10 @@ Available filters are:
|
||||
:frame-nospatial: Like ``frame`` but skips spatial interlacing check.
|
||||
:field-nospatial: Like ``field`` but skips spatial interlacing check.
|
||||
|
||||
``<enabled>``
|
||||
:yes: Filter is active (default).
|
||||
:no: Filter is not active, but can be activated with the ``D`` key
|
||||
(or any other key that toggles the ``deinterlace`` property).
|
||||
``<interlaced-only>``
|
||||
:no: Deinterlace all frames (default).
|
||||
:yes: Only deinterlace frames marked as interlaced (default if this
|
||||
filter is inserted via ``deinterlace`` property).
|
||||
|
||||
This filter, is automatically inserted when using the ``D`` key (or any
|
||||
other key that toggles the ``deinterlace`` property or when using the
|
||||
@@ -545,22 +497,6 @@ Available filters are:
|
||||
when inserting yadif with ``--vf``, so using the above methods is
|
||||
recommended.
|
||||
|
||||
``delogo[=x:y:w:h:t:show]``
|
||||
Suppresses a TV station logo by a simple interpolation of the surrounding
|
||||
pixels. Just set a rectangle covering the logo and watch it disappear (and
|
||||
sometimes something even uglier appear - your mileage may vary).
|
||||
|
||||
``<x>,<y>``
|
||||
top left corner of the logo
|
||||
``<w>,<h>``
|
||||
width and height of the cleared rectangle
|
||||
``<t>``
|
||||
Thickness of the fuzzy edge of the rectangle (added to ``w`` and
|
||||
``h``). When set to -1, a green rectangle is drawn on the screen to
|
||||
simplify finding the right ``x``,``y``,``w``,``h`` parameters.
|
||||
``show``
|
||||
Draw a rectangle showing the area defined by x/y/w/h.
|
||||
|
||||
``sub=[=bottom-margin:top-margin]``
|
||||
Moves subtitle rendering to an arbitrary point in the filter chain, or force
|
||||
subtitle rendering in the video filter as opposed to using video output OSD
|
||||
@@ -669,6 +605,8 @@ Available filters are:
|
||||
Loads an external library to filter the image. The library interface
|
||||
is the ``vf_dlopen`` interface specified using ``libmpcodecs/vf_dlopen.h``.
|
||||
|
||||
.. warning:: This filter is deprecated.
|
||||
|
||||
``dll=<library>``
|
||||
Specify the library to load. This may require a full file system path
|
||||
in some cases. This argument is required.
|
||||
@@ -778,7 +716,7 @@ Available filters are:
|
||||
``vapoursynth-lazy``
|
||||
The same as ``vapoursynth``, but doesn't load Python scripts. Instead, a
|
||||
custom backend using Lua and the raw VapourSynth API is used. The syntax
|
||||
is completely different, and absolutely no conveniencve features are
|
||||
is completely different, and absolutely no convenience features are
|
||||
provided. There's no type checking either, and you can trigger crashes.
|
||||
|
||||
.. admonition:: Example:
|
||||
@@ -820,6 +758,10 @@ Available filters are:
|
||||
depends on the GPU hardware, the GPU drivers, driver bugs, and
|
||||
mpv bugs.
|
||||
|
||||
``<interlaced-only>``
|
||||
:no: Deinterlace all frames.
|
||||
:yes: Only deinterlace frames marked as interlaced (default).
|
||||
|
||||
``vdpaupp``
|
||||
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=opengl``
|
||||
only. This filter is automatically inserted if deinterlacing is requested
|
||||
@@ -864,12 +806,21 @@ Available filters are:
|
||||
``pullup``
|
||||
Try to apply inverse telecine, needs motion adaptive temporal
|
||||
deinterlacing.
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
``hqscaling=<0-9>``
|
||||
0
|
||||
Use default VDPAU scaling (default).
|
||||
1-9
|
||||
Apply high quality VDPAU scaling (needs capable hardware).
|
||||
|
||||
``vdpaurb``
|
||||
VDPAU video read back. Works with ``--vo=vdpau`` and ``--vo=opengl`` only.
|
||||
This filter will read back frames decoded by VDPAU so that other filters,
|
||||
which are not normally compatible with VDPAU, can be used like normal.
|
||||
This filter must be specified before ``vdpaupp`` in the filter chain if
|
||||
``vdpaupp`` is used.
|
||||
|
||||
``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
|
||||
|
||||
+169
-44
@@ -69,11 +69,11 @@ Available video output drivers are:
|
||||
``no-colorkey``
|
||||
Disables color-keying.
|
||||
|
||||
``x11`` (X11 only)
|
||||
Shared memory video output driver without hardware acceleration that works
|
||||
whenever X11 is present.
|
||||
|
||||
.. note:: This is a fallback only, and should not be normally used.
|
||||
``buffers=<number>``
|
||||
Number of image buffers to use for the internal ringbuffer (default: 2).
|
||||
Increasing this will use more memory, but might help with the X server
|
||||
not responding quickly enough if video FPS is close to or higher than
|
||||
the display refresh rate.
|
||||
|
||||
``vdpau`` (X11 only)
|
||||
Uses the VDPAU interface to display and optionally also decode video.
|
||||
@@ -290,6 +290,26 @@ Available video output drivers are:
|
||||
color space conversion and chroma upsampling is generally in the hand of
|
||||
the hardware decoder APIs.
|
||||
|
||||
``opengl`` makes use of FBOs by default. Sometimes you can achieve better
|
||||
quality or performance by changing the ``fbo-format`` suboption to
|
||||
``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include Mesa/Intel not
|
||||
accepting ``rgb16``, Mesa sometimes not being compiled with float texture
|
||||
support, and some OS X setups being very slow with ``rgb16`` but fast
|
||||
with ``rgb32f``. If you have problems, you can also try passing the
|
||||
``dumb-mode=yes`` sub-option.
|
||||
|
||||
``dumb-mode=<yes|no>``
|
||||
This mode is extremely restricted, and will disable most extended
|
||||
OpenGL features. This includes high quality scalers and custom
|
||||
shaders!
|
||||
|
||||
It is intended for hardware that does not support FBOs (including GLES,
|
||||
which supports it insufficiently), or to get some more performance out
|
||||
of bad or old hardware.
|
||||
|
||||
This mode is forced automatically if needed, and this option is mostly
|
||||
useful for debugging.
|
||||
|
||||
``scale=<filter>``
|
||||
|
||||
``bilinear``
|
||||
@@ -336,6 +356,9 @@ Available video output drivers are:
|
||||
exchange for adding some blur. This filter is good at temporal
|
||||
interpolation, and also known as "smoothmotion" (see ``tscale``).
|
||||
|
||||
``custom``
|
||||
A user-defined custom shader (see ``scale-shader``).
|
||||
|
||||
There are some more filters, but most are not as useful. For a complete
|
||||
list, pass ``help`` as value, e.g.::
|
||||
|
||||
@@ -452,6 +475,11 @@ Available video output drivers are:
|
||||
Unfortunately, this can lead to flicker on LCD displays, since these
|
||||
have a high reaction time.
|
||||
|
||||
``temporal-dither-period=<1-128>``
|
||||
Determines how often the dithering pattern is updated when
|
||||
``temporal-dither`` is in use. 1 (the default) will update on every
|
||||
video frame, 2 on every other frame, etc.
|
||||
|
||||
``debug``
|
||||
Check for OpenGL errors, i.e. call ``glGetError()``. Also request a
|
||||
debug OpenGL context (which does nothing with current graphics drivers
|
||||
@@ -494,10 +522,15 @@ Available video output drivers are:
|
||||
for ``tscale`` are separable convolution filters (use ``tscale=help``
|
||||
to get a list). The default is ``oversample``.
|
||||
|
||||
Note that the maximum supported filter radius is currently 3, and that
|
||||
using filters with larger radius may introduce issues when pausing or
|
||||
framestepping, proportional to the radius used. It is recommended to
|
||||
stick to a radius of 1 or 2.
|
||||
Note that the maximum supported filter radius is currently 3, due to
|
||||
limitations in the number of video textures that can be loaded
|
||||
simultaneously.
|
||||
|
||||
``tscale-clamp``
|
||||
Clamp the ``tscale`` filter kernel's value range to [0-1]. This reduces
|
||||
excessive ringing artifacts in the temporal domain (which typically
|
||||
manifest themselves as short flashes or fringes of black, mostly
|
||||
around moving edges) in exchange for potentially adding more blur.
|
||||
|
||||
``dscale-radius``, ``cscale-radius``, ``tscale-radius``, etc.
|
||||
Set filter parameters for ``dscale``, ``cscale`` and ``tscale``,
|
||||
@@ -506,10 +539,8 @@ Available video output drivers are:
|
||||
See the corresponding options for ``scale``.
|
||||
|
||||
``linear-scaling``
|
||||
Scale in linear light. This is automatically enabled if
|
||||
``target-prim``, ``target-trc``, ``icc-profile`` or
|
||||
``sigmoid-upscaling`` is set. It should only be used with a
|
||||
``fbo-format`` that has at least 16 bit precision.
|
||||
Scale in linear light. It should only be used with a ``fbo-format``
|
||||
that has at least 16 bit precision.
|
||||
|
||||
``fancy-downscaling``
|
||||
When using convolution based filters, extend the filter size
|
||||
@@ -519,9 +550,92 @@ Available video output drivers are:
|
||||
feature doesn't work correctly with different scale factors in
|
||||
different directions.
|
||||
|
||||
``pre-shaders=<files>``, ``post-shaders=<files>``, ``scale-shader=<file>``
|
||||
Custom GLSL fragment shaders.
|
||||
|
||||
pre-shaders (list)
|
||||
These get applied after conversion to RGB and before linearization
|
||||
and upscaling. Operates on non-linear RGB (same as input). This is
|
||||
the best place to put things like sharpen filters.
|
||||
scale-shader
|
||||
This gets used instead of scale/cscale when those options are set
|
||||
to ``custom``. The colorspace it operates on depends on the values
|
||||
of ``linear-scaling`` and ``sigmoid-upscaling``, so no assumptions
|
||||
should be made here.
|
||||
post-shaders (list)
|
||||
These get applied after upscaling and subtitle blending (when
|
||||
``blend-subtitles`` is enabled), but before color management.
|
||||
Operates on linear RGB if ``linear-scaling`` is in effect,
|
||||
otherwise non-linear RGB. This is the best place for colorspace
|
||||
transformations (eg. saturation mapping).
|
||||
|
||||
These files must define a function with the following signature::
|
||||
|
||||
vec4 sample(sampler2D tex, vec2 pos, vec2 tex_size)
|
||||
|
||||
The meanings of the parameters are as follows:
|
||||
|
||||
sampler2D tex
|
||||
The source texture for the shader.
|
||||
vec2 pos
|
||||
The position to be sampled, in coordinate space [0-1].
|
||||
vec2 tex_size
|
||||
The size of the texture, in pixels. This may differ from image_size,
|
||||
eg. for subsampled content or for post-shaders.
|
||||
|
||||
In addition to these parameters, the following uniforms are also
|
||||
globally available:
|
||||
|
||||
float random
|
||||
A random number in the range [0-1], different per frame.
|
||||
int frame
|
||||
A simple count of frames rendered, increases by one per frame and
|
||||
never resets (regardless of seeks).
|
||||
vec2 image_size
|
||||
The size in pixels of the input image.
|
||||
|
||||
For example, a shader that inverts the colors could look like this::
|
||||
|
||||
vec4 sample(sampler2D tex, vec2 pos, vec2 tex_size)
|
||||
{
|
||||
vec4 color = texture(tex, pos);
|
||||
return vec4(1.0 - color.rgb, color.a);
|
||||
}
|
||||
|
||||
``deband``
|
||||
Enable the debanding algorithm. This greatly reduces the amount of
|
||||
visible banding, blocking and other quantization artifacts, at the
|
||||
expensive of very slightly blurring some of the finest details. In
|
||||
practice, it's virtually always an improvement - the only reason to
|
||||
disable it would be for performance.
|
||||
|
||||
``deband-iterations=<1..16>``
|
||||
The number of debanding steps to perform per sample. Each step reduces
|
||||
a bit more banding, but takes time to compute. Note that the strength
|
||||
of each step falls off very quickly, so high numbers are practically
|
||||
useless. (Default 4)
|
||||
|
||||
If the performance hit of debanding is too great, you can reduce this
|
||||
to 2 or 1 with marginal visual quality loss.
|
||||
|
||||
``deband-threshold=<0..4096>``
|
||||
The debanding filter's cut-off threshold. Higher numbers increase the
|
||||
debanding strength dramatically but progressively diminish image
|
||||
details. (Default 64)
|
||||
|
||||
``deband-range=<1..64>``
|
||||
The debanding filter's initial radius. The radius increases linearly
|
||||
for each iteration. A higher radius will find more gradients, but
|
||||
a lower radius will smooth more aggressively. (Default 8)
|
||||
|
||||
``deband-grain=<0..4096>``
|
||||
Add some extra noise to the image. This significantly helps cover up
|
||||
remaining quantization artifacts. Higher numbers add more noise.
|
||||
(Default 48)
|
||||
|
||||
``sigmoid-upscaling``
|
||||
When upscaling, use a sigmoidal color transform to avoid emphasizing
|
||||
ringing artifacts. This also enables ``linear-scaling``.
|
||||
ringing artifacts. This also implies ``linear-scaling``.
|
||||
|
||||
``sigmoid-center``
|
||||
The center of the sigmoid curve used for ``sigmoid-upscaling``, must
|
||||
@@ -531,10 +645,6 @@ Available video output drivers are:
|
||||
The slope of the sigmoid curve used for ``sigmoid-upscaling``, must
|
||||
be a float between 1.0 and 20.0. Defaults to 6.5 if not specified.
|
||||
|
||||
``no-npot``
|
||||
Force use of power-of-2 texture sizes. For debugging only.
|
||||
Borders will be distorted due to filtering.
|
||||
|
||||
``glfinish``
|
||||
Call ``glFinish()`` before and after swapping buffers (default: disabled).
|
||||
Slower, but might help getting better results when doing framedropping.
|
||||
@@ -555,7 +665,8 @@ Available video output drivers are:
|
||||
full screen).
|
||||
This may help getting more consistent frame intervals, especially with
|
||||
high-fps clips - which might also reduce dropped frames. Typically a
|
||||
value of 1 should be enough since full screen may bypass the DWM.
|
||||
value of ``windowed`` should be enough since full screen may bypass the
|
||||
DWM.
|
||||
|
||||
Windows only.
|
||||
|
||||
@@ -573,20 +684,22 @@ Available video output drivers are:
|
||||
Cocoa/OS X
|
||||
win
|
||||
Win32/WGL
|
||||
x11, x11es
|
||||
X11/GLX (the ``es`` variant forces GLES)
|
||||
x11
|
||||
X11/GLX
|
||||
wayland
|
||||
Wayland/EGL
|
||||
x11egl, x11egles
|
||||
X11/EGL (the ``es`` variant forces GLES)
|
||||
x11egl
|
||||
X11/EGL
|
||||
|
||||
``es``
|
||||
Force or prefer GLES2/3 over desktop OpenGL, if supported.
|
||||
|
||||
``fbo-format=<fmt>``
|
||||
Selects the internal format of textures used for FBOs. The format can
|
||||
influence performance and quality of the video output. (FBOs are not
|
||||
always used, and typically only when using extended scalers.)
|
||||
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: rgba.
|
||||
Default: rgba16.
|
||||
|
||||
``gamma=<0.1..2.0>``
|
||||
Set a gamma value (default: 1.0). If gamma is adjusted in other ways
|
||||
@@ -660,8 +773,7 @@ Available video output drivers are:
|
||||
``icc-profile=<file>``
|
||||
Load an ICC profile and use it to transform linear RGB to screen output.
|
||||
Needs LittleCMS 2 support compiled in. This option overrides the
|
||||
``target-prim`` and ``target-trc`` options. It also enables
|
||||
``linear-scaling``.
|
||||
``target-prim``, ``target-trc`` and ``icc-profile-auto`` options.
|
||||
|
||||
``icc-profile-auto``
|
||||
Automatically select the ICC display profile currently specified by
|
||||
@@ -669,11 +781,14 @@ Available video output drivers are:
|
||||
|
||||
NOTE: Only implemented on OS X and X11
|
||||
|
||||
``icc-cache=<file>``
|
||||
Store and load the 3D LUT created from the ICC profile in this file.
|
||||
``icc-cache-dir=<dirname>``
|
||||
Store and load the 3D LUTs created from the ICC profile in this directory.
|
||||
This can be used to speed up loading, since LittleCMS 2 can take a while
|
||||
to create the 3D LUT. Note that this file contains an uncompressed LUT.
|
||||
Its size depends on the ``3dlut-size``, and can be very big.
|
||||
to create a 3D LUT. Note that these files contain uncompressed LUTs.
|
||||
Their size depends on the ``3dlut-size``, and can be very big.
|
||||
|
||||
NOTE: This is not cleaned automatically, so old, unused cache files
|
||||
may stick around indefinitely.
|
||||
|
||||
``icc-intent=<value>``
|
||||
Specifies the ICC intent used for the color transformation (when using
|
||||
@@ -697,8 +812,8 @@ Available video output drivers are:
|
||||
Blend subtitles directly onto upscaled video frames, before
|
||||
interpolation and/or color management (default: no). Enabling this
|
||||
causes subtitles to be affected by ``icc-profile``, ``target-prim``,
|
||||
``target-trc``, ``interpolation``, ``gamma`` and ``linear-scaling``.
|
||||
It also increases subtitle performance when using ``interpolation``.
|
||||
``target-trc``, ``interpolation``, ``gamma`` and ``post-shader``. It
|
||||
also increases subtitle performance when using ``interpolation``.
|
||||
|
||||
The downside of enabling this is that it restricts subtitles to the
|
||||
visible portion of the video, so you can't have subtitles exist in the
|
||||
@@ -744,18 +859,11 @@ Available video output drivers are:
|
||||
|
||||
This is equivalent to::
|
||||
|
||||
--vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:fbo-format=rgba16:fancy-downscaling:sigmoid-upscaling
|
||||
--vo=opengl:scale=spline36:cscale=spline36:dscale=mitchell:dither-depth=auto:fancy-downscaling:sigmoid-upscaling:pbo:deband
|
||||
|
||||
Note that some cheaper LCDs do dithering that gravely interferes with
|
||||
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.
|
||||
|
||||
Unlike ``opengl``, ``opengl-hq`` makes use of FBOs by default. Sometimes you
|
||||
can achieve better quality or performance by changing the ``fbo-format``
|
||||
suboption to ``rgb16f``, ``rgb32f`` or ``rgb``. Known problems include
|
||||
Mesa/Intel not accepting ``rgb16``, Mesa sometimes not being compiled with
|
||||
float texture support, and some OS X setups being very slow with ``rgb16``
|
||||
but fast with ``rgb32f``.
|
||||
|
||||
``sdl``
|
||||
SDL 2.0+ Render video output driver, depending on system with or without
|
||||
hardware acceleration. Should work on all platforms supported by SDL 2.0.
|
||||
@@ -816,6 +924,12 @@ Available video output drivers are:
|
||||
``null``
|
||||
Produces no video output. Useful for benchmarking.
|
||||
|
||||
Usually, it's better to disable video with ``--no-video`` instead.
|
||||
|
||||
``fps=<value>``
|
||||
Simulate display FPS. This artificially limits how many frames the
|
||||
VO accepts per second.
|
||||
|
||||
``caca``
|
||||
Color ASCII art video output driver that works on a text console.
|
||||
|
||||
@@ -895,13 +1009,15 @@ Available video output drivers are:
|
||||
``frame-queue-size=<1..100>``
|
||||
The maximum count of frames which the frame queue can hold (default: 1)
|
||||
|
||||
``frame-drop-mode=<pop|clear>``
|
||||
``frame-drop-mode=<pop|clear|block>``
|
||||
Select the behavior when the frame queue is full.
|
||||
|
||||
pop
|
||||
Drop the oldest frame in the frame queue. (default)
|
||||
Drop the oldest frame in the frame queue.
|
||||
clear
|
||||
Drop all frames in the frame queue.
|
||||
block
|
||||
Wait for a short time, behave like ``clear`` on timeout. (default)
|
||||
|
||||
This also supports many of the suboptions the ``opengl`` VO has. Run
|
||||
``mpv --vo=opengl-cb:help`` for a list.
|
||||
@@ -921,6 +1037,11 @@ Available video output drivers are:
|
||||
selected layer, to handle the window background and OSD. Actual video
|
||||
rendering will happen on the layer above the selected layer.
|
||||
|
||||
``background=<yes|no>``
|
||||
Whether to render a black background behind the video (default: no).
|
||||
Normally it's better to kill the console framebuffer instead, which
|
||||
gives better performance.
|
||||
|
||||
``drm`` (Direct Rendering Manager)
|
||||
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
|
||||
Does not support hardware acceleration. Should be used when one doesn't
|
||||
@@ -933,3 +1054,7 @@ Available video output drivers are:
|
||||
``devpath=<filename>``
|
||||
Path to graphic card device.
|
||||
(default: /dev/dri/card0)
|
||||
|
||||
``mode=<number>``
|
||||
Mode ID to use (resolution, bit depth and frame rate).
|
||||
(default: 0)
|
||||
@@ -49,8 +49,10 @@ Input
|
||||
* Classic LIRC support was removed. Install remotes as input devices instead.
|
||||
This way they will send X11 key events to the mpv window, which can be bound
|
||||
using the normal ``input.conf``.
|
||||
Also see: http://github.com/mpv-player/mpv/wiki/IR-remotes
|
||||
* Joystick support was removed. It was considered useless and was the cause
|
||||
of some problems (e.g. a laptop's accelerator being recognized as joystick).
|
||||
* Support for relative seeking by percentage.
|
||||
|
||||
Audio
|
||||
~~~~~
|
||||
@@ -92,6 +94,8 @@ Video
|
||||
|
||||
* Image subtitles (DVDs etc.) are rendered in color and use more correct
|
||||
positioning (color for image subs can be disabled with ``--sub-gray``).
|
||||
* Support for the X11 video output is removed, since it was considered
|
||||
deprecated. SDL video output can still be used as a fallback.
|
||||
|
||||
OSD and terminal
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -275,7 +279,7 @@ Command Line Switches
|
||||
``-xineramascreen`` ``--screen`` (different values)
|
||||
``-xy W`` ``--autofit=W``
|
||||
``-zoom`` Inverse available as ``--video-unscaled``
|
||||
``dvdnav://`` ``dvdnav://menu``
|
||||
``dvdnav://`` Removed.
|
||||
``dvd://1`` ``dvd://0`` (0-based offset)
|
||||
=========================== ========================================
|
||||
|
||||
@@ -395,7 +399,8 @@ Slave mode
|
||||
|
||||
Assuming the system supports ``/dev/stdin``.
|
||||
|
||||
(The option was readded in 0.5.1 and sets exactly these options.)
|
||||
(The option was added back in 0.5.1 and sets exactly these options. It was
|
||||
removed in 0.10.x again.)
|
||||
|
||||
* A JSON RPC protocol giving access to the client API is also supported. See
|
||||
`JSON IPC`_ for more information.
|
||||
|
||||
+69
-27
@@ -1,64 +1,106 @@
|
||||
Release Policy
|
||||
==============
|
||||
|
||||
Every few months, a new release is cut off of the master branch and is assigned
|
||||
a 0.X.0 version number.
|
||||
Once or twice a month, a new release is cut off of the master branch and is
|
||||
assigned a 0.X.Y version number, where X is incremented each time a release
|
||||
contains breaking changes, such as changed options or added/removed features,
|
||||
and Y is incremented if a release contains only bugfixes and other minor
|
||||
changes.
|
||||
|
||||
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.
|
||||
There is only one release branch that keeps track of the latest version and
|
||||
will not be maintained separately.
|
||||
|
||||
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 provide Linux distributions with something to
|
||||
package. If you want the newest features, just use the master branch.
|
||||
We try our best to keep it deployable at all times.
|
||||
|
||||
Releases other than the latest release are unsupported and unmaintained.
|
||||
|
||||
Release procedure
|
||||
-----------------
|
||||
|
||||
- Create branch release/0.X or cherry-pick commits to the relevant branch.
|
||||
- Merge master into branch release/current.
|
||||
|
||||
- Create and/or update the ``RELEASE_NOTES`` file.
|
||||
- Create and/or update the `RELEASE_NOTES` file.
|
||||
|
||||
- Create and/or update the ``VERSION`` file.
|
||||
- Create and/or update the `VERSION` file.
|
||||
|
||||
- Update ``DOCS/client-api-changes.rst`` (on major releases).
|
||||
- Update `DOCS/client-api-changes.rst` (on major releases).
|
||||
|
||||
- Create tag v0.X.Y.
|
||||
|
||||
- Push branch and tag to GitHub.
|
||||
|
||||
- Create a new GitHub release using the content of ``RELEASE_NOTES`` related to
|
||||
- Create a new GitHub release using the content of `RELEASE_NOTES` related to
|
||||
the new version.
|
||||
|
||||
Release notes template
|
||||
----------------------
|
||||
|
||||
Here is a template that can be used for writing the ``RELEASE_NOTES`` file.
|
||||
Here is a template that can be used for writing the `RELEASE_NOTES` file.
|
||||
|
||||
```markdown
|
||||
Release 0.X.Y
|
||||
=============
|
||||
|
||||
Changes
|
||||
-------
|
||||
Features
|
||||
--------
|
||||
|
||||
- List of changes.
|
||||
New
|
||||
~~~
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
- List of new features
|
||||
|
||||
- List of bug fixes.
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
New features
|
||||
------------
|
||||
- List of removed features
|
||||
|
||||
- List of new features.
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- List of deprecated features
|
||||
|
||||
Behavior
|
||||
--------
|
||||
|
||||
- List of user-visible changes in behavior
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- List of added options and commands
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- List of changed options and commands
|
||||
|
||||
Renamed
|
||||
~~~~~~~
|
||||
|
||||
- List of renamed options and commands
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- List of deprecated options and commands
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- List of removed options and commands
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- List of fixes and minor enhancements
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log <start>..<end>`` in
|
||||
complete change log can be viewed by running `git log <start>..<end>` in
|
||||
the git repository.
|
||||
```
|
||||
|
||||
@@ -66,6 +108,6 @@ Note that the "Release 0.X.Y" title should be removed when creating a new GitHub
|
||||
release.
|
||||
|
||||
When creating a new point release its changes should be added on top of the
|
||||
``RELEASE_NOTES`` file (with the appropriate title) so that all the changes in
|
||||
the current 0.X branch will be included. This way the ``RELEASE_NOTES`` file
|
||||
`RELEASE_NOTES` file (with the appropriate title) so that all the changes in
|
||||
the current 0.X branch will be included. This way the `RELEASE_NOTES` file
|
||||
can be used by distributors as changelog for point releases too.
|
||||
@@ -75,8 +75,7 @@ talloc.h & talloc.c:
|
||||
allocation call will never return NULL.
|
||||
|
||||
One very useful feature of talloc is fast tracking of memory leaks. ("Fast"
|
||||
as in it doesn't require valgrind.) You can enable it by passing the option
|
||||
--leak-report as first parameter, or better, setting the
|
||||
as in it doesn't require valgrind.) You can enable it by setting the
|
||||
MPV_LEAK_REPORT environment variable to "1":
|
||||
export MPV_LEAK_REPORT=1
|
||||
This will list all unfree'd allocations on exit.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
waf build system overview
|
||||
=========================
|
||||
|
||||
mpv's new build system is based on waf and it should completly replace the
|
||||
mpv's new build system is based on waf and it should completely replace the
|
||||
custom ./configure + Makefile based system inherited from MPlayer.
|
||||
|
||||
Goals and the choice of waf
|
||||
@@ -65,7 +65,7 @@ This defines a feature called ``vdpau`` which can be enabled or disabled by
|
||||
the users with configure flags (that's the meaning of ``--``). This feature
|
||||
depends on another feature whose name is ``x11``, and the autodetection check
|
||||
consists of running ``pkg-config`` and looking for ``vdpau`` with version
|
||||
``>= 0.2``. If the check succeds a ``#define HAVE_VDPAU 1`` will be added to
|
||||
``>= 0.2``. If the check succeeds a ``#define HAVE_VDPAU 1`` will be added to
|
||||
``config.h``, if not ``#define HAVE_VDPAU 0`` will be added.
|
||||
|
||||
The defines names are automatically prepended with ``HAVE_``, capitalized and
|
||||
|
||||
@@ -57,9 +57,6 @@ To build the software you can use `./waf build`: the result of the compilation
|
||||
will be located in `build/mpv`. You can use `./waf install` to install mpv
|
||||
to the *prefix* after it is compiled.
|
||||
|
||||
NOTE: Using the old build system (with `./old-configure`) should still work,
|
||||
but will be removed in a future version of mpv.
|
||||
|
||||
Essential dependencies (incomplete list):
|
||||
|
||||
- gcc or clang
|
||||
@@ -174,11 +171,9 @@ list of changes is located [here][mplayer-changes].
|
||||
Most activity happens on the IRC channel and the github issue tracker. The
|
||||
mailing lists are mostly unused.
|
||||
|
||||
- **Github issue tracker**: [issue tracker][issue-tracker] (report bugs here)
|
||||
- **User IRC Channel**: `#mpv-player` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-player-dev` on `irc.freenode.net`
|
||||
- **Users Mailing List**: `mpv-users@googlegroups.com` ([Archive / Subscribe][mpv-users]).
|
||||
- **Devel Mailing List**: `mpv-devel@googlegroups.com` ([Archive / Subscribe][mpv-devel])
|
||||
- **GitHub issue tracker**: [issue tracker][issue-tracker] (report bugs here)
|
||||
- **User IRC Channel**: `#mpv` on `irc.freenode.net`
|
||||
- **Developer IRC Channel**: `#mpv-devel` on `irc.freenode.net`
|
||||
|
||||
To contact the `mpv` team in private write to `mpv-team@googlegroups.com`. Use
|
||||
only if discretion is required.
|
||||
@@ -187,8 +182,6 @@ only if discretion is required.
|
||||
[mpv-build]: https://github.com/mpv-player/mpv-build
|
||||
[homebrew-mpv]: https://github.com/mpv-player/homebrew-mpv
|
||||
[issue-tracker]: https://github.com/mpv-player/mpv/issues
|
||||
[mpv-users]: https://groups.google.com/forum/?hl=en#!forum/mpv-users
|
||||
[mpv-devel]: https://groups.google.com/forum/?hl=en#!forum/mpv-devel
|
||||
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
|
||||
+1058
File diff suppressed because it is too large.
Load diff
+7
-7
@@ -47,7 +47,7 @@ judge()
|
||||
progressive=$((progressive + progressive1))
|
||||
undetermined=$((undetermined + undetermined1))
|
||||
done <<EOF
|
||||
$(testfun "$@")
|
||||
$(testfun "$@" | sed 's/:/: /g')
|
||||
EOF
|
||||
|
||||
interlaced=$((bff + tff))
|
||||
@@ -95,18 +95,18 @@ case "$verdict" in
|
||||
exit 0
|
||||
;;
|
||||
interlaced-tff)
|
||||
judge "$@" --vf-clr --vf-pre=pullup --field-dominance=top
|
||||
judge "$@" --vf-clr --vf-pre=lavfi=\[setfield=tff\],pullup
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=pullup --field-dominance=top
|
||||
$ILDETECT_MPV "$@" --vf-pre=lavfi=\[setfield=tff\],pullup
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=yadif --field-dominance=top
|
||||
$ILDETECT_MPV "$@" --vf-pre=lavfi=\[setfield=tff\],yadif
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
exit 2
|
||||
@@ -114,18 +114,18 @@ case "$verdict" in
|
||||
esac
|
||||
;;
|
||||
interlaced-bff)
|
||||
judge "$@" --vf-clr --vf-pre=pullup --field-dominance=bottom
|
||||
judge "$@" --vf-clr --vf-pre=lavfi=\[setfield=bff\],pullup
|
||||
case "$verdict" in
|
||||
progressive)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=pullup --field-dominance=bottom
|
||||
$ILDETECT_MPV "$@" --vf-pre=lavfi=\[setfield=bff\],pullup
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" --vf-pre=yadif --field-dominance=bottom
|
||||
$ILDETECT_MPV "$@" --vf-pre=lavfi=\[setfield=bff\],yadif
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $((r | 16))
|
||||
exit 2
|
||||
|
||||
@@ -84,7 +84,7 @@ sub elem_by_hexid {
|
||||
# used by elem when setting the 'valname' key
|
||||
use constant TYPE_MAP => {
|
||||
uint => 'uint64_t',
|
||||
str => 'struct bstr',
|
||||
str => 'char *',
|
||||
binary => 'struct bstr',
|
||||
ebml_id => 'uint32_t',
|
||||
float => 'double',
|
||||
|
||||
+26
-45
@@ -2,21 +2,16 @@
|
||||
-- appropriate deinterlacing filter based on a short section of the
|
||||
-- currently playing video.
|
||||
--
|
||||
-- It registers the key-binding ctrl+d, which when pressed, inserts
|
||||
-- the filter vf=lavfi=idet. After 4 second, it examines the results
|
||||
-- to determine whether the content progressive or interlaced and the
|
||||
-- interlacing field dominance. It immediately sets the latter within
|
||||
-- mpv.
|
||||
-- It registers the key-binding ctrl+d, which when pressed, inserts the filters
|
||||
-- ``vf=lavfi=idet,pullup,vf=lavfi=idet``. After 4 seconds, it removes these
|
||||
-- filters and decides whether the content is progressive, interlaced, or
|
||||
-- telecined and the interlacing field dominance.
|
||||
--
|
||||
-- If the content is judged not to be progressive, it may be either
|
||||
-- interlaced or telecined. To determine which one, the video is
|
||||
-- seeked back to the detection starting point with the "pullup"
|
||||
-- filter inserted. After another four seconds, if idet thinks that
|
||||
-- the pulled up viedo is "progressive", then pullup is the correct
|
||||
-- filter and this script leaves it in places while removing the idet
|
||||
-- filter. Otherwise, the content plain interlaced and mpv's deinterlace
|
||||
-- property is set. This will usually insert the yadif deinterlacing filter.
|
||||
-- Based on this information, it may set mpv's ``deinterlace`` property (which
|
||||
-- usually inserts the yadif filter), or insert the ``pullup`` filter if the
|
||||
-- content is telecined. It also sets mpv's ``field-dominance`` property.
|
||||
--
|
||||
-- OPTIONS:
|
||||
-- The default detection time may be overridden by adding
|
||||
--
|
||||
-- --script-opts=autodeint.detect_seconds=<number of seconds>
|
||||
@@ -37,6 +32,7 @@ require "mp.msg"
|
||||
script_name = mp.get_script_name()
|
||||
detect_label = string.format("%s-detect", script_name)
|
||||
pullup_label = string.format("%s", script_name)
|
||||
ivtc_detect_label = string.format("%s-ivtc-detect", script_name)
|
||||
|
||||
-- number of seconds to gather cropdetect data
|
||||
detect_seconds = tonumber(mp.get_opt(string.format("%s.detect_seconds", script_name)))
|
||||
@@ -69,30 +65,29 @@ function start_detect()
|
||||
mp.set_property("deinterlace","no")
|
||||
del_filter_if_present(pullup_label)
|
||||
|
||||
percent_pos = mp.get_property("percent-pos")
|
||||
|
||||
-- insert the detection filter
|
||||
local cmd = string.format('vf add @%s:lavfi=graph="idet"', detect_label)
|
||||
local cmd = string.format('vf add @%s:lavfi=graph="idet",@%s:pullup,@%s:lavfi=graph="idet"',
|
||||
detect_label, pullup_label, ivtc_detect_label)
|
||||
if not mp.command(cmd) then
|
||||
mp.msg.error("failed to insert detection filter")
|
||||
mp.msg.error("failed to insert detection filters")
|
||||
return
|
||||
end
|
||||
|
||||
-- wait to gather data
|
||||
timer = mp.add_timeout(detect_seconds, judge_field_dominance)
|
||||
timer = mp.add_timeout(detect_seconds, select_filter)
|
||||
end
|
||||
|
||||
function stop_detect()
|
||||
del_filter_if_present(detect_label)
|
||||
del_filter_if_present(ivtc_detect_label)
|
||||
timer = nil
|
||||
mp.set_property("playback-pos", percent_pos)
|
||||
end
|
||||
|
||||
progressive, interlaced_tff, interlaced_bff, interlaced = 0, 1, 2, 3, 4
|
||||
|
||||
function judge()
|
||||
function judge(label)
|
||||
-- get the metadata
|
||||
local result = mp.get_property_native(string.format("vf-metadata/%s", detect_label))
|
||||
local result = mp.get_property_native(string.format("vf-metadata/%s", label))
|
||||
num_tff = tonumber(result["lavfi.idet.multiple.tff"])
|
||||
num_bff = tonumber(result["lavfi.idet.multiple.bff"])
|
||||
num_progressive = tonumber(result["lavfi.idet.multiple.progressive"])
|
||||
@@ -100,10 +95,10 @@ function judge()
|
||||
num_interlaced = num_tff + num_bff
|
||||
num_determined = num_interlaced + num_progressive
|
||||
|
||||
mp.msg.verbose("progressive = "..num_progressive)
|
||||
mp.msg.verbose("interlaced-tff = "..num_tff)
|
||||
mp.msg.verbose("interlaced-bff = "..num_bff)
|
||||
mp.msg.verbose("undetermined = "..num_undetermined)
|
||||
mp.msg.verbose(label.." progressive = "..num_progressive)
|
||||
mp.msg.verbose(label.." interlaced-tff = "..num_tff)
|
||||
mp.msg.verbose(label.." interlaced-bff = "..num_bff)
|
||||
mp.msg.verbose(label.." undetermined = "..num_undetermined)
|
||||
|
||||
if num_determined < num_undetermined then
|
||||
mp.msg.warn("majority undetermined frames")
|
||||
@@ -119,11 +114,12 @@ function judge()
|
||||
end
|
||||
end
|
||||
|
||||
function judge_field_dominance()
|
||||
verdict = judge()
|
||||
function select_filter()
|
||||
-- handle the first detection filter results
|
||||
verdict = judge(detect_label)
|
||||
if verdict == progressive then
|
||||
stop_detect()
|
||||
mp.msg.info("progressive: doing nothing")
|
||||
stop_detect()
|
||||
return
|
||||
elseif verdict == interlaced_tff then
|
||||
mp.set_property("field-dominance", "top")
|
||||
@@ -133,21 +129,8 @@ function judge_field_dominance()
|
||||
mp.set_property("field-dominance", "auto")
|
||||
end
|
||||
|
||||
local cmd = string.format("vf pre @%s:pullup", pullup_label)
|
||||
if not mp.command(cmd) then
|
||||
mp.msg.error("failed to insert pullup filter")
|
||||
stop_detect()
|
||||
return
|
||||
end
|
||||
|
||||
-- redo the detection with the pullup filter to see if it's telecined
|
||||
mp.set_property("percent-pos", percent_pos)
|
||||
timer = mp.add_timeout(detect_seconds, judge_ivtc)
|
||||
end
|
||||
|
||||
function judge_ivtc()
|
||||
verdict = judge()
|
||||
|
||||
-- handle the ivtc detection filter results
|
||||
verdict = judge(ivtc_detect_label)
|
||||
if verdict == progressive then
|
||||
mp.msg.info(string.format("telecinied with %s field dominance: using pullup", mp.get_property("field-dominance")))
|
||||
stop_detect()
|
||||
@@ -157,8 +140,6 @@ function judge_ivtc()
|
||||
mp.set_property("deinterlace","yes")
|
||||
stop_detect()
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
mp.add_key_binding("ctrl+d", script_name, start_detect)
|
||||
@@ -4,8 +4,8 @@
|
||||
-- alphabetically, and adds entries before and after the current file to
|
||||
-- the internal playlist. (It stops if the it would add an already existing
|
||||
-- playlist entry at the same position - this makes it "stable".)
|
||||
-- Add at most 5 * 2 files when starting a file (before + after).
|
||||
MAXENTRIES = 5
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
MAXENTRIES = 5000
|
||||
|
||||
function Set (t)
|
||||
local set = {}
|
||||
@@ -47,6 +47,13 @@ function find_and_add_entries()
|
||||
if #dir == 0 then
|
||||
return
|
||||
end
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
if (pl_count > 1 and autoload == nil) or
|
||||
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
|
||||
return
|
||||
else
|
||||
autoload = true
|
||||
end
|
||||
|
||||
local files = mputils.readdir(dir, "files")
|
||||
if files == nil then
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
-- parameters. See the entry for "drc" under the "AUDIO FILTERS"
|
||||
-- section of the man page for a complete description of the filter.
|
||||
--
|
||||
-- This script registers the key-binding "\" to toggle the filter between
|
||||
-- This script registers the key-binding "\" to toggle the filter between
|
||||
--
|
||||
-- * off
|
||||
-- * method=1 (single-sample smoothing)
|
||||
@@ -92,7 +92,7 @@ function drc_quieter_handler()
|
||||
drc_scale_target(0.5)
|
||||
end
|
||||
|
||||
-- toggle between off, method 1 and method 2
|
||||
-- toggle between off, method 1 and method 2
|
||||
mp.add_key_binding("\\", "drc_toggle_method", drc_toggle_method_handler)
|
||||
-- increase or decrease target volume
|
||||
mp.add_key_binding("ctrl+9", "drc_quieter", drc_quieter_handler)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
-- Rebuild the terminal status line as a lua script
|
||||
-- Be aware that this will require more cpu power!
|
||||
-- Also, this is based on a rather old version of the
|
||||
-- builtin mpv status line.
|
||||
|
||||
-- Add a string to the status line
|
||||
function atsl(s)
|
||||
@@ -28,7 +30,7 @@ function update_status_line()
|
||||
atsl(mp.get_property_osd("time-pos"))
|
||||
|
||||
atsl(" / ");
|
||||
atsl(mp.get_property_osd("length"));
|
||||
atsl(mp.get_property_osd("duration"));
|
||||
|
||||
atsl(" (")
|
||||
atsl(mp.get_property_osd("percent-pos", -1))
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
--sets the startime of a youtube video as specified in the "t=HHhMMmSSs" part of the url
|
||||
--NOTE: This might become obsolete once youtube-dl adds the functionality
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
|
||||
function youtube_starttime()
|
||||
url = mp.get_property("path", "")
|
||||
start = 0
|
||||
|
||||
if string.find(url, "youtu%.?be") and
|
||||
((url:find("http://") == 1) or (url:find("https://") == 1)) then
|
||||
time = string.match(url, "[#&%?]t=%d*h?%d*m?%d+s?m?h?")
|
||||
--the time-string can start with #, & or ? followed by t= and the timing parameters
|
||||
--at least one number needs to be present after t=, followed by h, m, s or nothing (>implies s)
|
||||
|
||||
if time then
|
||||
for pos in string.gmatch(time,"%d+%a?") do
|
||||
if string.match(pos,"%d+h") then --find out multiplier for
|
||||
multiplier = 60*60 --hours
|
||||
elseif string.match(pos,"%d+m") then
|
||||
multiplier = 60 --minutes
|
||||
else multiplier = 1 end --seconds
|
||||
|
||||
start = start + (string.match(pos,"%d+") * multiplier)
|
||||
end
|
||||
|
||||
msg.info("parsed '" .. time .. "' into '" .. start .. "' seconds")
|
||||
end
|
||||
|
||||
mp.set_property("file-local-options/start",start)
|
||||
end
|
||||
end
|
||||
|
||||
mp.add_hook("on_load", 50, youtube_starttime)
|
||||
@@ -0,0 +1,74 @@
|
||||
-- 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)
|
||||
@@ -4,6 +4,13 @@
|
||||
# Cleanups all over the place (c) 2001 pl
|
||||
# Rewritten for mpv in 2014.
|
||||
#
|
||||
#
|
||||
# Warning: this is not officially supported. Use on your own risk. Might require
|
||||
# modifications when used on systems other than Linux. Contributors
|
||||
# are not expected to update these files when making changes to the
|
||||
# normal/preferred waf build system.
|
||||
#
|
||||
#
|
||||
# This configure script is *not* autoconf-based and has different semantics.
|
||||
# It attempts to autodetect all settings and options where possible. It is
|
||||
# possible to override autodetection with the --enable-option/--disable-option
|
||||
@@ -176,6 +183,7 @@ options_state_machine() {
|
||||
opt_yes_no _dvdread "libdvdread"
|
||||
opt_yes_no _dvdnav "libdvdnav"
|
||||
opt_yes_no _enca "ENCA charset oracle library"
|
||||
opt_yes_no _uchardet "uchardet charset detection library"
|
||||
opt_yes_no _libass "subtitle rendering with libass"
|
||||
opt_yes_no _libavdevice "libavdevice demuxers"
|
||||
opt_yes_no _libavfilter "libavfilter"
|
||||
@@ -183,8 +191,6 @@ options_state_machine() {
|
||||
opt_yes_no _libcdio "libcdio support"
|
||||
opt_yes_no _librubberband "librubberband support"
|
||||
opt_yes_no _ffmpeg "skip FFmpeg/Libav autodetection"
|
||||
opt_yes_no _ladspa "LADSPA plugin support"
|
||||
opt_yes_no _libbs2b "libbs2b audio filter support"
|
||||
opt_yes_no _libavresample "libavresample (preferred over libswresample)"
|
||||
opt_yes_no _libswresample "libswresample"
|
||||
opt_yes_no _caca "CACA video output"
|
||||
@@ -208,6 +214,7 @@ options_state_machine() {
|
||||
opt_yes_no _lua "Lua scripting"
|
||||
opt_yes_no _vapoursynth "VapourSynth filter bridge (Python)"
|
||||
opt_yes_no _vapoursynth_lazy "VapourSynth filter bridge (Lua)"
|
||||
opt_yes_no _libarchive "libarchive"
|
||||
opt_yes_no _encoding "encoding functionality" yes
|
||||
opt_yes_no _build_man "building manpage"
|
||||
}
|
||||
@@ -376,7 +383,7 @@ addcflags() { cflag_check "$@" && OURCFLAGS="$OURCFLAGS $@" ; }
|
||||
OURCFLAGS="-std=c99 -Wall $_opt"
|
||||
|
||||
addcflags -g -g3 -ggdb
|
||||
addcflags -Wundef -Wmissing-prototypes -Wshadow -Wno-switch -Wparentheses -Wpointer-arith -Wredundant-decls -Wno-pointer-sign -Werror=implicit-function-declaration -Wno-error=deprecated-declarations -Wno-error=unused-function
|
||||
addcflags -Wundef -Wmissing-prototypes -Wshadow -Wno-switch -Wparentheses -Wpointer-arith -Wno-redundant-decls -Wno-pointer-sign -Werror=implicit-function-declaration -Wno-error=deprecated-declarations -Wno-error=unused-function
|
||||
# clang
|
||||
addcflags -Wno-logical-op-parentheses -fcolor-diagnostics -Wno-tautological-compare -Wno-tautological-constant-out-of-range-compare
|
||||
# extra
|
||||
@@ -506,9 +513,12 @@ check_statement_libs "compiler support for __sync built-ins" $_sync SYNC_BUILTIN
|
||||
stdint.h 'int64_t test = 0; test = __sync_add_and_fetch(&test, 1)'
|
||||
_sync=$(defretval)
|
||||
|
||||
_any_atomic=yes
|
||||
if test "$_atomic" = no && test "$_sync" = no && test "$_stdatomic" = no ; then
|
||||
echo "your compiler must support either stdatomic.h, or __atomic, or __sync built-ins."
|
||||
_any_atomic=no
|
||||
fi
|
||||
define_yes_no $_any_atomic HAVE_ATOMICS
|
||||
|
||||
check_compile "iconv" $_iconv ICONV waftools/fragments/iconv.c " " "-liconv" "-liconv $_ld_dl"
|
||||
_iconv=$(defretval)
|
||||
@@ -709,7 +719,7 @@ check_pkg_config "OpenAL" $_openal OPENAL 'openal >= 1.13'
|
||||
|
||||
check_pkg_config "ALSA audio" $_alsa ALSA 'alsa >= 1.0.9'
|
||||
|
||||
check_pkg_config "Blu-ray support" $_bluray LIBBLURAY 'libbluray >= 0.2.1'
|
||||
check_pkg_config "Blu-ray support" $_bluray LIBBLURAY 'libbluray >= 0.3.0'
|
||||
|
||||
check_pkg_config "dvdread" $_dvdread DVDREAD 'dvdread >= 4.1.0'
|
||||
|
||||
@@ -732,15 +742,11 @@ echo "LIBASS_OSD = $_libass" >> $CONFIG_MAK
|
||||
echo "DUMMY_OSD = $_dummy_osd" >> $CONFIG_MAK
|
||||
|
||||
check_pkg_config "ENCA" $_enca ENCA 'enca'
|
||||
check_pkg_config "uchardet" $_uchardet UCHARDET 'uchardet'
|
||||
|
||||
check_pkg_config "zlib" auto ZLIB 'zlib'
|
||||
test $(defretval) = no && die "Unable to find development files for zlib."
|
||||
|
||||
test "$_dl" = no && _ladspa=no
|
||||
check_statement_libs "LADSPA plugin support" $_ladspa LADSPA ladspa.h 'LADSPA_Descriptor ld = {0}'
|
||||
|
||||
check_pkg_config "libbs2b audio filter support" $_libbs2b LIBBS2B 'libbs2b'
|
||||
|
||||
check_pkg_config "LCMS2 support" $_lcms2 LCMS2 'lcms2 >= 2.6'
|
||||
|
||||
check_pkg_config "FFmpeg/Libav" $_ffmpeg FFMPEG \
|
||||
@@ -786,6 +792,18 @@ api_statement_check \
|
||||
libavutil/frame.h \
|
||||
'enum AVFrameSideDataType type = AV_FRAME_DATA_SKIP_SAMPLES'
|
||||
|
||||
api_statement_check \
|
||||
"libavutil av_version_info()" \
|
||||
HAVE_AV_VERSION_INFO \
|
||||
libavutil/avutil.h \
|
||||
'const char *x = av_version_info()'
|
||||
|
||||
api_statement_check \
|
||||
"libavutil new pixdesc fields" \
|
||||
HAVE_AV_NEW_PIXDESC \
|
||||
libavutil/pixdesc.h \
|
||||
'AVComponentDescriptor d; int x = d.depth'
|
||||
|
||||
check_pkg_config "libavfilter" $_libavfilter LIBAVFILTER 'libavfilter >= 5.0.0'
|
||||
|
||||
check_pkg_config "libavdevice" $_libavdevice LIBAVDEVICE 'libavdevice >= 55.0.0'
|
||||
@@ -817,9 +835,9 @@ test_lua "lua >= 5.1.0 lua < 5.2.0"
|
||||
test_lua "lua5.1 >= 5.1.0" # debian
|
||||
test_lua "lua51 >= 5.1.0" # OpenBSD
|
||||
test_lua "luajit >= 2.0.0"
|
||||
test_lua "lua >= 5.2.0"
|
||||
test_lua "lua5.2 >= 5.2.0" # debian
|
||||
test_lua "lua52 >= 5.2.0" # OpenBSD
|
||||
test_lua "lua >= 5.2.0"
|
||||
|
||||
test "$_lua" != yes && check_yes_no no LUA
|
||||
|
||||
@@ -841,6 +859,8 @@ if test "$_vapoursynth" = no && test "$_vapoursynth_lazy" = no ; then
|
||||
fi
|
||||
check_trivial "VapourSynth core" $_vapoursynth_core VAPOURSYNTH_CORE
|
||||
|
||||
check_pkg_config "libarchive support" $_libarchive LIBARCHIVE 'libarchive >= 3.0.0'
|
||||
|
||||
check_trivial "encoding" $_encoding ENCODING
|
||||
|
||||
# needs dlopen on unix
|
||||
@@ -858,7 +878,7 @@ CFLAGS="$CFLAGS -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE
|
||||
# in-tree builds.
|
||||
|
||||
if test ! -f Makefile ; then
|
||||
ln -s old-makefile Makefile
|
||||
ln -s TOOLS/old-makefile Makefile
|
||||
fi
|
||||
|
||||
cat > old_build/config.mak << EOF
|
||||
@@ -905,7 +925,7 @@ cat > $TMPC << EOF
|
||||
/* we didn't bother to add actual config checks for this, or they are
|
||||
for platforms not supported by this configure script */
|
||||
#define HAVE_BSD_FSTATFS 0
|
||||
#define HAVE_LINUX_FSTATFS 0
|
||||
#define HAVE_LINUX_FSTATFS 1
|
||||
#define HAVE_VDA_AV_VDA_ALLOC_CONTEXT 0
|
||||
#define HAVE_VDA_HWACCEL 0
|
||||
#define HAVE_VDA_LIBAVCODEC_REFCOUNTING 0
|
||||
@@ -936,6 +956,9 @@ cat > $TMPC << EOF
|
||||
#define HAVE_RPI_GLES 0
|
||||
#define HAVE_AV_PIX_FMT_MMAL 0
|
||||
#define HAVE_DRM 0
|
||||
#define HAVE_VIDEOTOOLBOX_HWACCEL 0
|
||||
#define HAVE_VIDEOTOOLBOX_GL 0
|
||||
#define HAVE_VIDEOTOOLBOX_VDA_GL 0
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
#define DEFAULT_CDROM_DEVICE "/dev/rcd0c"
|
||||
@@ -35,13 +35,11 @@ SOURCES-$(DVDREAD) += stream/stream_dvd.c \
|
||||
SOURCES-$(DVDNAV) += stream/stream_dvdnav.c \
|
||||
stream/stream_dvd_common.c
|
||||
|
||||
SOURCES-$(LADSPA) += audio/filter/af_ladspa.c
|
||||
SOURCES-$(RUBBERBAND) += audio/filter/af_rubberband.c
|
||||
SOURCES-$(LIBASS) += sub/ass_mp.c sub/sd_ass.c \
|
||||
demux/demux_libass.c
|
||||
|
||||
SOURCES-$(LIBBLURAY) += stream/stream_bluray.c
|
||||
SOURCES-$(LIBBS2B) += audio/filter/af_bs2b.c
|
||||
|
||||
SOURCES-$(LIBSMBCLIENT) += stream/stream_smb.c
|
||||
|
||||
@@ -58,19 +56,24 @@ 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/gl_common.c video/out/gl_osd.c \
|
||||
video/out/vo_opengl.c video/out/gl_lcms.c \
|
||||
video/out/gl_video.c video/out/dither.c \
|
||||
video/out/gl_hwdec.c video/out/gl_utils.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/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/gl_x11.c
|
||||
SOURCES-$(EGL_X11) += video/out/x11_common.c video/out/gl_x11egl.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/gl_wayland.c
|
||||
video/out/opengl/wayland.c
|
||||
|
||||
SOURCES-$(JACK) += audio/out/ao_jack.c
|
||||
SOURCES-$(OPENAL) += audio/out/ao_openal.c
|
||||
@@ -80,34 +83,32 @@ 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
|
||||
SOURCES-$(VDPAU_GL_X11) += video/out/gl_hwdec_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/vaapi.c
|
||||
SOURCES-$(VAAPI_VPP) += video/filter/vf_vavpp.c
|
||||
SOURCES-$(VAAPI_GLX) += video/out/gl_hwdec_vaglx.c
|
||||
SOURCES-$(VAAPI_GLX) += video/out/opengl/hwdec_vaglx.c
|
||||
|
||||
SOURCES-$(X11) += video/out/vo_x11.c video/out/x11_common.c
|
||||
SOURCES-$(XV) += video/out/vo_xv.c
|
||||
SOURCES-$(XV) += video/out/x11_common.c 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_delogo.c \
|
||||
video/filter/vf_gradfun.c \
|
||||
video/filter/vf_hqdn3d.c \
|
||||
video/filter/vf_noise.c \
|
||||
video/filter/vf_pullup.c \
|
||||
video/filter/vf_rotate.c \
|
||||
video/filter/vf_unsharp.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 \
|
||||
@@ -121,31 +122,17 @@ SOURCES = audio/audio.c \
|
||||
audio/decode/ad_spdif.c \
|
||||
audio/decode/dec_audio.c \
|
||||
audio/filter/af.c \
|
||||
audio/filter/af_center.c \
|
||||
audio/filter/af_channels.c \
|
||||
audio/filter/af_convert24.c \
|
||||
audio/filter/af_convertsignendian.c \
|
||||
audio/filter/af_delay.c \
|
||||
audio/filter/af_dummy.c \
|
||||
audio/filter/af_equalizer.c \
|
||||
audio/filter/af_export.c \
|
||||
audio/filter/af_extrastereo.c \
|
||||
audio/filter/af_format.c \
|
||||
audio/filter/af_hrtf.c \
|
||||
audio/filter/af_karaoke.c \
|
||||
audio/filter/af_lavcac3enc.c \
|
||||
audio/filter/af_lavrresample.c \
|
||||
audio/filter/af_pan.c \
|
||||
audio/filter/af_scaletempo.c \
|
||||
audio/filter/af_sinesuppress.c \
|
||||
audio/filter/af_sub.c \
|
||||
audio/filter/af_surround.c \
|
||||
audio/filter/af_sweep.c \
|
||||
audio/filter/af_drc.c \
|
||||
audio/filter/af_volume.c \
|
||||
audio/filter/filter.c \
|
||||
audio/filter/tools.c \
|
||||
audio/filter/window.c \
|
||||
audio/out/ao.c \
|
||||
audio/out/ao_null.c \
|
||||
audio/out/ao_pcm.c \
|
||||
@@ -160,6 +147,7 @@ SOURCES = audio/audio.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 \
|
||||
@@ -195,6 +183,7 @@ SOURCES = audio/audio.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 \
|
||||
@@ -206,7 +195,7 @@ SOURCES = audio/audio.c \
|
||||
player/client.c \
|
||||
player/configfiles.c \
|
||||
player/command.c \
|
||||
player/discnav.c \
|
||||
player/external_files.c \
|
||||
player/loadfile.c \
|
||||
player/main.c \
|
||||
player/misc.c \
|
||||
@@ -231,7 +220,6 @@ SOURCES = audio/audio.c \
|
||||
stream/stream_rar.c \
|
||||
sub/dec_sub.c \
|
||||
sub/draw_bmp.c \
|
||||
sub/find_subfiles.c \
|
||||
sub/img_convert.c \
|
||||
sub/osd.c \
|
||||
sub/sd_lavc.c \
|
||||
@@ -334,7 +322,7 @@ all: $(ALL_TARGETS)
|
||||
%.o: %.c
|
||||
$(CC) $(DEPFLAGS) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
mpv: $(OBJECTS) player/main-fn-unix.o
|
||||
mpv: $(OBJECTS) osdep/main-fn-unix.o
|
||||
$(CC) -o $@ $^ $(EXTRALIBS)
|
||||
|
||||
input/input.c: input/input_conf.h
|
||||
@@ -381,8 +369,8 @@ config.mak: configure
|
||||
@echo "####### Please run ./configure again - it's changed! #######"
|
||||
@echo "############################################################"
|
||||
|
||||
version.h .version: version.sh
|
||||
./$<
|
||||
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
|
||||
@@ -393,7 +381,7 @@ version.h .version: version.sh
|
||||
|
||||
###### dependency declarations / specific CFLAGS ######
|
||||
|
||||
common/version.c: version.h
|
||||
common/version.c: old_build/version.h
|
||||
|
||||
DOCS/man/mpv.1: DOCS/man/af.rst \
|
||||
DOCS/man/ao.rst \
|
||||
@@ -464,7 +452,7 @@ clean:
|
||||
-$(RM) $(call ADD_ALL_DIRS,/*.o /*.a /*.ho /*~)
|
||||
-$(RM) mpv
|
||||
-$(RM) DOCS/man/*/mpv.1
|
||||
-$(RM) version.h
|
||||
-$(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
|
||||
+1
-1
@@ -40,7 +40,7 @@ def apply_plist_template(plist_file, version):
|
||||
print (line.rstrip().replace('${VERSION}', version))
|
||||
|
||||
def main():
|
||||
version = sh("./version.sh --print").strip()
|
||||
version = sh("./version.sh").strip()
|
||||
|
||||
usage = "usage: %prog [options] arg"
|
||||
parser = OptionParser(usage)
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
screenshot-template=~/Desktop/shot%n
|
||||
profile=pseudo-gui
|
||||
+15
-3
@@ -5,7 +5,10 @@ import re
|
||||
|
||||
filename = sys.argv[1]
|
||||
|
||||
event_regex = re.compile(".*")
|
||||
events = ".*"
|
||||
if len(sys.argv) > 2:
|
||||
events = sys.argv[2]
|
||||
event_regex = re.compile(events)
|
||||
|
||||
"""
|
||||
This script is meant to display stats written by mpv --dump-stats=filename.
|
||||
@@ -26,8 +29,8 @@ Currently, the following event types are supported:
|
||||
'end' <name> end of the named event
|
||||
'value' <float> <name> a normal value (as opposed to event)
|
||||
'event-timed' <ts> <name> singular event at the given timestamp
|
||||
'value-timed' <ts> <float> <name>
|
||||
a value for an event at the given timestamp
|
||||
'value-timed' <ts> <float> <name> a value for an event at the given timestamp
|
||||
'range-timed' <ts1> <ts2> <name> like start/end, but explicit times
|
||||
<name> singular event (same as 'signal')
|
||||
|
||||
"""
|
||||
@@ -91,6 +94,15 @@ for line in [line.split("#")[0].strip() for line in open(filename, "r")]:
|
||||
val = int(val) / SCALE - G.start
|
||||
e = get_event(name, "event-signal")
|
||||
e.vals.append((val, 1))
|
||||
elif event.startswith("range-timed "):
|
||||
_, ts1, ts2, name = event.split(" ", 3)
|
||||
ts1 = int(ts1) / SCALE - G.start
|
||||
ts2 = int(ts2) / SCALE - G.start
|
||||
e = get_event(name, "event")
|
||||
e.vals.append((ts1, 0))
|
||||
e.vals.append((ts1, 1))
|
||||
e.vals.append((ts2, 1))
|
||||
e.vals.append((ts2, 0))
|
||||
elif event.startswith("value-timed "):
|
||||
_, tsval, val, name = event.split(" ", 3)
|
||||
tsval = int(tsval) / SCALE - G.start
|
||||
|
||||
+13
-2
@@ -141,7 +141,7 @@ sub parse_main_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = split /\n/, `"$mpv" --no-config $cmd`;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
my ($name, $desc) = ($line =~ /^$regex/) or next;
|
||||
@@ -206,7 +206,7 @@ sub parse_opts {
|
||||
my ($cmd, $regex) = @_;
|
||||
|
||||
my @list;
|
||||
my @lines = split /\n/, `"$mpv" --no-config $cmd`;
|
||||
my @lines = call_mpv($cmd);
|
||||
|
||||
foreach my $line (@lines) {
|
||||
if ($line !~ /^$regex/) {
|
||||
@@ -226,3 +226,14 @@ sub parse_opts {
|
||||
|
||||
return @list;
|
||||
}
|
||||
|
||||
sub call_mpv {
|
||||
my ($cmd) = @_;
|
||||
my $output = `"$mpv" --no-config $cmd`;
|
||||
if ($? == -1) {
|
||||
die "Could not run mpv: $!";
|
||||
} elsif ((my $exit_code = $? >> 8) != 0) {
|
||||
die "mpv returned $exit_code with output:\n$output";
|
||||
}
|
||||
return split /\n/, $output;
|
||||
}
|
||||
+22
-15
@@ -34,8 +34,8 @@ static void update_redundant_info(struct mp_audio *mpa)
|
||||
assert(mp_chmap_is_empty(&mpa->channels) ||
|
||||
mp_chmap_is_valid(&mpa->channels));
|
||||
mpa->nch = mpa->channels.num;
|
||||
mpa->bps = af_fmt2bps(mpa->format);
|
||||
if (AF_FORMAT_IS_PLANAR(mpa->format)) {
|
||||
mpa->bps = af_fmt_to_bytes(mpa->format);
|
||||
if (af_fmt_is_planar(mpa->format)) {
|
||||
mpa->spf = 1;
|
||||
mpa->num_planes = mpa->nch;
|
||||
mpa->sstride = mpa->bps;
|
||||
@@ -58,13 +58,6 @@ void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels)
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
// Use old MPlayer/ALSA channel layout.
|
||||
void mp_audio_set_channels_old(struct mp_audio *mpa, int num_channels)
|
||||
{
|
||||
mp_chmap_from_channels_alsa(&mpa->channels, num_channels);
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap)
|
||||
{
|
||||
mpa->channels = *chmap;
|
||||
@@ -93,15 +86,19 @@ bool mp_audio_config_valid(const struct mp_audio *mpa)
|
||||
|
||||
char *mp_audio_config_to_str_buf(char *buf, size_t buf_sz, struct mp_audio *mpa)
|
||||
{
|
||||
char ch[128];
|
||||
mp_chmap_to_str_buf(ch, sizeof(ch), &mpa->channels);
|
||||
char *hr_ch = mp_chmap_to_str_hr(&mpa->channels);
|
||||
if (strcmp(hr_ch, ch) != 0)
|
||||
mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
|
||||
snprintf(buf, buf_sz, "%dHz %s %dch %s", mpa->rate,
|
||||
mp_chmap_to_str(&mpa->channels), mpa->channels.num,
|
||||
af_fmt_to_str(mpa->format));
|
||||
ch, mpa->channels.num, af_fmt_to_str(mpa->format));
|
||||
return buf;
|
||||
}
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
|
||||
{
|
||||
if (AF_FORMAT_IS_PLANAR(mpa->format))
|
||||
if (af_fmt_is_planar(mpa->format))
|
||||
mp_audio_set_format(mpa, af_fmt_from_planar(mpa->format));
|
||||
}
|
||||
|
||||
@@ -159,8 +156,8 @@ void mp_audio_realloc(struct mp_audio *mpa, int samples)
|
||||
if (!mpa->allocated[n] || size != mpa->allocated[n]->size) {
|
||||
if (av_buffer_realloc(&mpa->allocated[n], size) < 0)
|
||||
abort(); // OOM
|
||||
mpa->planes[n] = mpa->allocated[n]->data;
|
||||
}
|
||||
mpa->planes[n] = mpa->allocated[n]->data;
|
||||
}
|
||||
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
@@ -191,8 +188,18 @@ int mp_audio_get_allocated_size(struct mp_audio *mpa)
|
||||
{
|
||||
int size = 0;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
int s = mpa->allocated[n] ? mpa->allocated[n]->size / mpa->sstride : 0;
|
||||
size = n == 0 ? s : MPMIN(size, s);
|
||||
for (int i = 0; i < MP_NUM_CHANNELS && mpa->allocated[i]; i++) {
|
||||
uint8_t *start = mpa->allocated[i]->data;
|
||||
uint8_t *end = start + mpa->allocated[i]->size;
|
||||
uint8_t *plane = mpa->planes[n];
|
||||
if (plane >= start && plane < end) {
|
||||
int s = MPMIN((end - plane) / mpa->sstride, INT_MAX);
|
||||
size = n == 0 ? s : MPMIN(size, s);
|
||||
goto next;
|
||||
}
|
||||
}
|
||||
return 0; // plane is not covered by any buffer
|
||||
next: ;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
+5
-3
@@ -35,15 +35,17 @@ struct mp_audio {
|
||||
int nch; // number of channels (redundant with chmap)
|
||||
int spf; // sub-samples per sample on each plane
|
||||
int num_planes; // number of planes
|
||||
int bps; // size of sub-samples (af_fmt2bps(format))
|
||||
int bps; // size of sub-samples (af_fmt_to_bytes(format))
|
||||
|
||||
// private
|
||||
// --- private
|
||||
// These do not necessarily map directly to planes[]. They can have
|
||||
// different order or count. There shouldn't be more buffers than planes.
|
||||
// If allocated[n] is NULL, allocated[n+1] must also be NULL.
|
||||
struct AVBufferRef *allocated[MP_NUM_CHANNELS];
|
||||
};
|
||||
|
||||
void mp_audio_set_format(struct mp_audio *mpa, int format);
|
||||
void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels);
|
||||
void mp_audio_set_channels_old(struct mp_audio *mpa, int num_channels);
|
||||
void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap);
|
||||
void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src);
|
||||
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b);
|
||||
|
||||
+86
-59
@@ -18,6 +18,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "chmap.h"
|
||||
@@ -50,6 +52,7 @@ static const char *const speaker_names[MP_SPEAKER_ID_COUNT][2] = {
|
||||
[MP_SPEAKER_ID_SDL] = {"sdl", "surround direct left"},
|
||||
[MP_SPEAKER_ID_SDR] = {"sdr", "surround direct right"},
|
||||
[MP_SPEAKER_ID_LFE2] = {"lfe2", "low frequency 2"},
|
||||
[MP_SPEAKER_ID_NA] = {"na", "not available"},
|
||||
};
|
||||
|
||||
// Names taken from libavutil/channel_layout.c (Not accessible by API.)
|
||||
@@ -67,6 +70,7 @@ static const char *const std_layout_names[][2] = {
|
||||
{"quad", "fl-fr-bl-br"},
|
||||
{"quad(side)", "fl-fr-sl-sr"},
|
||||
{"3.1", "fl-fr-fc-lfe"},
|
||||
{"3.1(back)", "fl-fr-lfe-bc"}, // not in lavc
|
||||
{"5.0", "fl-fr-fc-bl-br"},
|
||||
{"5.0(alsa)", "fl-fr-bl-br-fc"}, // not in lavc
|
||||
{"5.0(side)", "fl-fr-fc-sl-sr"},
|
||||
@@ -80,9 +84,11 @@ static const char *const std_layout_names[][2] = {
|
||||
{"hexagonal", "fl-fr-fc-bl-br-bc"},
|
||||
{"6.1", "fl-fr-fc-lfe-bc-sl-sr"},
|
||||
{"6.1(back)", "fl-fr-fc-lfe-bl-br-bc"}, // lavc calls this "6.1" too
|
||||
{"6.1(top)", "fl-fr-fc-lfe-bl-br-tc"}, // not in lavc
|
||||
{"6.1(front)", "fl-fr-lfe-flc-frc-sl-sr"},
|
||||
{"7.0", "fl-fr-fc-bl-br-sl-sr"},
|
||||
{"7.0(front)", "fl-fr-fc-flc-frc-sl-sr"},
|
||||
{"7.0(rear)", "fl-fr-fc-bl-br-sdl-sdr"}, // not in lavc
|
||||
{"7.1", "fl-fr-fc-lfe-bl-br-sl-sr"},
|
||||
{"7.1(alsa)", "fl-fr-bl-br-fc-lfe-sl-sr"}, // not in lavc
|
||||
{"7.1(wide)", "fl-fr-fc-lfe-bl-br-flc-frc"},
|
||||
@@ -94,7 +100,7 @@ static const char *const std_layout_names[][2] = {
|
||||
{0}
|
||||
};
|
||||
|
||||
static const struct mp_chmap default_layouts[MP_NUM_CHANNELS + 1] = {
|
||||
static const struct mp_chmap default_layouts[] = {
|
||||
{0}, // empty
|
||||
MP_CHMAP_INIT_MONO, // mono
|
||||
MP_CHMAP2(FL, FR), // stereo
|
||||
@@ -127,7 +133,8 @@ bool mp_chmap_is_valid(const struct mp_chmap *src)
|
||||
int sp = src->speaker[n];
|
||||
if (sp >= MP_SPEAKER_ID_COUNT || mapped[sp])
|
||||
return false;
|
||||
mapped[sp] = true;
|
||||
if (sp != MP_SPEAKER_ID_NA)
|
||||
mapped[sp] = true;
|
||||
}
|
||||
return src->num > 0;
|
||||
}
|
||||
@@ -142,12 +149,10 @@ bool mp_chmap_is_empty(const struct mp_chmap *src)
|
||||
bool mp_chmap_is_unknown(const struct mp_chmap *src)
|
||||
{
|
||||
for (int n = 0; n < src->num; n++) {
|
||||
int speaker = src->speaker[n];
|
||||
if (speaker >= MP_SPEAKER_ID_UNKNOWN0 &&
|
||||
speaker <= MP_SPEAKER_ID_UNKNOWN_LAST)
|
||||
return true;
|
||||
if (src->speaker[n] != MP_SPEAKER_ID_NA)
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
return mp_chmap_is_valid(src);
|
||||
}
|
||||
|
||||
// Note: empty channel maps compare as equal. Invalid ones can equal too.
|
||||
@@ -171,15 +176,6 @@ bool mp_chmap_equals_reordered(const struct mp_chmap *a, const struct mp_chmap *
|
||||
return mp_chmap_equals(&t1, &t2);
|
||||
}
|
||||
|
||||
bool mp_chmap_is_compatible(const struct mp_chmap *a, const struct mp_chmap *b)
|
||||
{
|
||||
if (mp_chmap_equals(a, b))
|
||||
return true;
|
||||
if (a->num == b->num && (mp_chmap_is_unknown(a) || mp_chmap_is_unknown(b)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mp_chmap_is_stereo(const struct mp_chmap *src)
|
||||
{
|
||||
static const struct mp_chmap stereo = MP_CHMAP_INIT_STEREO;
|
||||
@@ -199,16 +195,36 @@ void mp_chmap_reorder_norm(struct mp_chmap *map)
|
||||
qsort(arr, map->num, 1, comp_uint8);
|
||||
}
|
||||
|
||||
// Remove silent (NA) channels, if any.
|
||||
void mp_chmap_remove_na(struct mp_chmap *map)
|
||||
{
|
||||
struct mp_chmap new = {0};
|
||||
for (int n = 0; n < map->num; n++) {
|
||||
int sp = map->speaker[n];
|
||||
if (sp != MP_SPEAKER_ID_NA)
|
||||
new.speaker[new.num++] = map->speaker[n];
|
||||
}
|
||||
*map = new;
|
||||
}
|
||||
|
||||
// Add silent (NA) channels to map until map->num >= num.
|
||||
void mp_chmap_fill_na(struct mp_chmap *map, int num)
|
||||
{
|
||||
assert(num <= MP_NUM_CHANNELS);
|
||||
while (map->num < num)
|
||||
map->speaker[map->num++] = MP_SPEAKER_ID_NA;
|
||||
}
|
||||
|
||||
// Set *dst to a standard layout with the given number of channels.
|
||||
// If the number of channels is invalid, an invalid map is set, and
|
||||
// mp_chmap_is_valid(dst) will return false.
|
||||
void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels)
|
||||
{
|
||||
if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) {
|
||||
*dst = (struct mp_chmap) {0};
|
||||
} else {
|
||||
*dst = (struct mp_chmap) {0};
|
||||
if (num_channels >= 0 && num_channels < MP_ARRAY_SIZE(default_layouts))
|
||||
*dst = default_layouts[num_channels];
|
||||
}
|
||||
if (!dst->num)
|
||||
mp_chmap_set_unknown(dst, num_channels);
|
||||
}
|
||||
|
||||
// Try to do what mplayer/mplayer2/mpv did before channel layouts were
|
||||
@@ -227,6 +243,8 @@ void mp_chmap_from_channels_alsa(struct mp_chmap *dst, int num_channels)
|
||||
// Set *dst to an unknown layout for the given numbers of channels.
|
||||
// If the number of channels is invalid, an invalid map is set, and
|
||||
// mp_chmap_is_valid(dst) will return false.
|
||||
// A mp_chmap with all entries set to NA is treated specially in some
|
||||
// contexts (watch out for mp_chmap_is_unknown()).
|
||||
void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels)
|
||||
{
|
||||
if (num_channels < 0 || num_channels > MP_NUM_CHANNELS) {
|
||||
@@ -234,7 +252,7 @@ void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels)
|
||||
} else {
|
||||
dst->num = num_channels;
|
||||
for (int n = 0; n < dst->num; n++)
|
||||
dst->speaker[n] = MP_SPEAKER_ID_UNKNOWN0 + n;
|
||||
dst->speaker[n] = MP_SPEAKER_ID_NA;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +295,7 @@ void mp_chmap_remove_useless_channels(struct mp_chmap *map,
|
||||
}
|
||||
|
||||
// Return the ffmpeg/libav channel layout as in <libavutil/channel_layout.h>.
|
||||
// Speakers not representable by ffmpeg/libav are dropped.
|
||||
// Warning: this ignores the order of the channels, and will return a channel
|
||||
// mask even if the order is different from libavcodec's.
|
||||
uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src)
|
||||
@@ -286,8 +305,10 @@ uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src)
|
||||
if (mp_chmap_is_unknown(&t))
|
||||
mp_chmap_from_channels(&t, t.num);
|
||||
uint64_t mask = 0;
|
||||
for (int n = 0; n < t.num; n++)
|
||||
mask |= 1ULL << t.speaker[n];
|
||||
for (int n = 0; n < t.num; n++) {
|
||||
if (t.speaker[n] < 64) // ignore MP_SPEAKER_ID_NA etc.
|
||||
mask |= 1ULL << t.speaker[n];
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
@@ -348,56 +369,43 @@ void mp_chmap_reorder_to_lavc(struct mp_chmap *map)
|
||||
|
||||
// Get reordering array for from->to reordering. from->to must have the same set
|
||||
// of speakers (i.e. same number and speaker IDs, just different order). Then,
|
||||
// for each speaker n, dst[n] will be set such that:
|
||||
// to->speaker[dst[n]] = from->speaker[n]
|
||||
// (dst[n] gives the source channel for destination channel n)
|
||||
void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
|
||||
// for each speaker n, src[n] will be set such that:
|
||||
// to->speaker[n] = from->speaker[src[n]]
|
||||
// (src[n] gives the source channel for destination channel n)
|
||||
// If *from and *to don't contain the same set of speakers, then the above
|
||||
// invariant is not guaranteed. Instead, src[n] can be set to -1 if the channel
|
||||
// at to->speaker[n] is unmapped.
|
||||
void mp_chmap_get_reorder(int src[MP_NUM_CHANNELS], const struct mp_chmap *from,
|
||||
const struct mp_chmap *to)
|
||||
{
|
||||
assert(from->num == to->num);
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
src[n] = -1;
|
||||
|
||||
if (mp_chmap_is_unknown(from) || mp_chmap_is_unknown(to)) {
|
||||
for (int n = 0; n < from->num; n++)
|
||||
dst[n] = n;
|
||||
for (int n = 0; n < to->num; n++)
|
||||
src[n] = n < from->num ? n : -1;
|
||||
return;
|
||||
}
|
||||
// Same set of speakers required
|
||||
assert(mp_chmap_equals_reordered(from, to));
|
||||
for (int n = 0; n < from->num; n++) {
|
||||
int src = from->speaker[n];
|
||||
dst[n] = -1;
|
||||
for (int i = 0; i < to->num; i++) {
|
||||
if (src == to->speaker[i]) {
|
||||
dst[n] = i;
|
||||
|
||||
for (int n = 0; n < to->num; n++) {
|
||||
for (int i = 0; i < from->num; i++) {
|
||||
if (to->speaker[n] == from->speaker[i]) {
|
||||
src[n] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(dst[n] != -1);
|
||||
}
|
||||
for (int n = 0; n < from->num; n++)
|
||||
assert(to->speaker[dst[n]] == from->speaker[n]);
|
||||
|
||||
for (int n = 0; n < to->num; n++)
|
||||
assert(src[n] < 0 || (to->speaker[n] == from->speaker[src[n]]));
|
||||
}
|
||||
|
||||
// Performs the difference between a and b, and store it in diff. If b has
|
||||
// channels that do not appear in a, those will not appear in the difference.
|
||||
// To get to those the argument ordering in the function call has to be
|
||||
// inverted. For the same reason, the diff with a superset will return no
|
||||
// speakers.
|
||||
void mp_chmap_diff(const struct mp_chmap *a, const struct mp_chmap *b,
|
||||
struct mp_chmap *diff)
|
||||
// Return the number of channels only in a.
|
||||
int mp_chmap_diffn(const struct mp_chmap *a, const struct mp_chmap *b)
|
||||
{
|
||||
uint64_t a_mask = mp_chmap_to_lavc_unchecked(a);
|
||||
uint64_t b_mask = mp_chmap_to_lavc_unchecked(b);
|
||||
mp_chmap_from_lavc(diff, (a_mask ^ b_mask) & a_mask);
|
||||
}
|
||||
|
||||
// Checks whether a contains all the speakers in b
|
||||
bool mp_chmap_contains(const struct mp_chmap *a, const struct mp_chmap *b)
|
||||
{
|
||||
struct mp_chmap d1;
|
||||
struct mp_chmap d2;
|
||||
mp_chmap_diff(a, b, &d1);
|
||||
mp_chmap_diff(b, a, &d2);
|
||||
return a->num >= b->num && d1.num >= 0 && d2.num == 0;
|
||||
return av_popcount64((a_mask ^ b_mask) & a_mask);
|
||||
}
|
||||
|
||||
// Returns something like "fl-fr-fc". If there's a standard layout in lavc
|
||||
@@ -500,6 +508,25 @@ bool mp_chmap_from_str(struct mp_chmap *dst, bstr src)
|
||||
return true;
|
||||
}
|
||||
|
||||
// Output a human readable "canonical" channel map string. Converting this from
|
||||
// a string back to a channel map can yield a different map, but the string
|
||||
// looks nicer. E.g. "fc-fl-fr-na" becomes "3.0".
|
||||
char *mp_chmap_to_str_hr_buf(char *buf, size_t buf_size, const struct mp_chmap *src)
|
||||
{
|
||||
struct mp_chmap map = *src;
|
||||
mp_chmap_remove_na(&map);
|
||||
for (int n = 0; std_layout_names[n][0]; n++) {
|
||||
struct mp_chmap s;
|
||||
if (mp_chmap_from_str(&s, bstr0(std_layout_names[n][0])) &&
|
||||
mp_chmap_equals_reordered(&s, &map))
|
||||
{
|
||||
map = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return mp_chmap_to_str_buf(buf, buf_size, &map);
|
||||
}
|
||||
|
||||
void mp_chmap_print_help(struct mp_log *log)
|
||||
{
|
||||
mp_info(log, "Speakers:\n");
|
||||
|
||||
+14
-11
@@ -47,7 +47,7 @@ enum mp_speaker_id {
|
||||
MP_SPEAKER_ID_TBL, // TOP_BACK_LEFT
|
||||
MP_SPEAKER_ID_TBC, // TOP_BACK_CENTER
|
||||
MP_SPEAKER_ID_TBR, // TOP_BACK_RIGHT
|
||||
// Inofficial/libav* extensions
|
||||
// Unofficial/libav* extensions
|
||||
MP_SPEAKER_ID_DL = 29, // STEREO_LEFT (stereo downmix special speakers)
|
||||
MP_SPEAKER_ID_DR, // STEREO_RIGHT
|
||||
MP_SPEAKER_ID_WL, // WIDE_LEFT
|
||||
@@ -56,10 +56,12 @@ enum mp_speaker_id {
|
||||
MP_SPEAKER_ID_SDR, // SURROUND_DIRECT_RIGHT
|
||||
MP_SPEAKER_ID_LFE2, // LOW_FREQUENCY_2
|
||||
|
||||
// Special mpv-specific speaker entries reserved for channels which have no
|
||||
// known meaning.
|
||||
MP_SPEAKER_ID_UNKNOWN0 = 64,
|
||||
MP_SPEAKER_ID_UNKNOWN_LAST = MP_SPEAKER_ID_UNKNOWN0 + MP_NUM_CHANNELS - 1,
|
||||
// Speaker IDs >= 64 are not representable in WAVEFORMATEXTENSIBLE or libav*.
|
||||
|
||||
// "Silent" channels. These are sometimes used to insert padding for
|
||||
// unused channels. Unlike other speaker types, multiple of these can
|
||||
// occur in a single mp_chmap.
|
||||
MP_SPEAKER_ID_NA = 64,
|
||||
|
||||
// Including the unassigned IDs in between. This is not a valid ID anymore,
|
||||
// but is still within uint8_t.
|
||||
@@ -98,10 +100,11 @@ bool mp_chmap_is_empty(const struct mp_chmap *src);
|
||||
bool mp_chmap_is_unknown(const struct mp_chmap *src);
|
||||
bool mp_chmap_equals(const struct mp_chmap *a, const struct mp_chmap *b);
|
||||
bool mp_chmap_equals_reordered(const struct mp_chmap *a, const struct mp_chmap *b);
|
||||
bool mp_chmap_is_compatible(const struct mp_chmap *a, const struct mp_chmap *b);
|
||||
bool mp_chmap_is_stereo(const struct mp_chmap *src);
|
||||
|
||||
void mp_chmap_reorder_norm(struct mp_chmap *map);
|
||||
void mp_chmap_remove_na(struct mp_chmap *map);
|
||||
void mp_chmap_fill_na(struct mp_chmap *map, int num);
|
||||
|
||||
void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels);
|
||||
void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels);
|
||||
@@ -117,17 +120,17 @@ void mp_chmap_from_lavc(struct mp_chmap *dst, uint64_t src);
|
||||
bool mp_chmap_is_lavc(const struct mp_chmap *src);
|
||||
void mp_chmap_reorder_to_lavc(struct mp_chmap *map);
|
||||
|
||||
void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
|
||||
void mp_chmap_get_reorder(int src[MP_NUM_CHANNELS], const struct mp_chmap *from,
|
||||
const struct mp_chmap *to);
|
||||
|
||||
void mp_chmap_diff(const struct mp_chmap *a, const struct mp_chmap *b,
|
||||
struct mp_chmap *diff);
|
||||
|
||||
bool mp_chmap_contains(const struct mp_chmap *a, const struct mp_chmap *b);
|
||||
int mp_chmap_diffn(const struct mp_chmap *a, const struct mp_chmap *b);
|
||||
|
||||
char *mp_chmap_to_str_buf(char *buf, size_t buf_size, const struct mp_chmap *src);
|
||||
#define mp_chmap_to_str(m) mp_chmap_to_str_buf((char[64]){0}, 64, (m))
|
||||
|
||||
char *mp_chmap_to_str_hr_buf(char *buf, size_t buf_size, const struct mp_chmap *src);
|
||||
#define mp_chmap_to_str_hr(m) mp_chmap_to_str_hr_buf((char[128]){0}, 128, (m))
|
||||
|
||||
bool mp_chmap_from_str(struct mp_chmap *dst, bstr src);
|
||||
|
||||
struct mp_log;
|
||||
|
||||
+114
-71
@@ -58,6 +58,29 @@ static bool replace_speakers(struct mp_chmap *map, struct mp_chmap list[2])
|
||||
return false;
|
||||
}
|
||||
|
||||
// These go strictly from the first to the second entry and always use the
|
||||
// full layout (possibly reordered and/or padding channels added).
|
||||
static const struct mp_chmap preferred_remix[][2] = {
|
||||
// mono can be perfectly played as stereo
|
||||
{ MP_CHMAP_INIT_MONO, MP_CHMAP_INIT_STEREO },
|
||||
};
|
||||
|
||||
// Conversion from src to dst is explicitly encouraged and should be preferred
|
||||
// over "mathematical" upmixes or downmixes (which minimize lost channels).
|
||||
static bool test_preferred_remix(const struct mp_chmap *src,
|
||||
const struct mp_chmap *dst)
|
||||
{
|
||||
struct mp_chmap src_p = *src, dst_p = *dst;
|
||||
mp_chmap_remove_na(&src_p);
|
||||
mp_chmap_remove_na(&dst_p);
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(preferred_remix); n++) {
|
||||
if (mp_chmap_equals_reordered(&src_p, &preferred_remix[n][0]) &&
|
||||
mp_chmap_equals_reordered(&dst_p, &preferred_remix[n][1]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allow all channel layouts that can be expressed with mp_chmap.
|
||||
// (By default, all layouts are rejected.)
|
||||
void mp_chmap_sel_add_any(struct mp_chmap_sel *s)
|
||||
@@ -176,6 +199,10 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (mp_chmap_sel_fallback(s, map))
|
||||
return true;
|
||||
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(speaker_replacements); i++) {
|
||||
struct mp_chmap t = *map;
|
||||
struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
|
||||
@@ -185,9 +212,6 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
|
||||
}
|
||||
}
|
||||
|
||||
if (mp_chmap_sel_fallback(s, map))
|
||||
return true;
|
||||
|
||||
// Fallback to mono/stereo as last resort
|
||||
*map = (struct mp_chmap) MP_CHMAP_INIT_STEREO;
|
||||
if (test_layout(s, map))
|
||||
@@ -199,64 +223,95 @@ bool mp_chmap_sel_adjust(const struct mp_chmap_sel *s, struct mp_chmap *map)
|
||||
return false;
|
||||
}
|
||||
|
||||
#define UPMIX_IDX 0
|
||||
#define DOWNMIX_IDX 1
|
||||
#define FALLBACK_IDX 2
|
||||
|
||||
static bool test_fallbacks(struct mp_chmap *a, struct mp_chmap *b,
|
||||
int best_diffs[2], struct mp_chmap best[2])
|
||||
// Like mp_chmap_diffn(), but find the minimum difference with all possible
|
||||
// speaker replacements considered.
|
||||
static int mp_chmap_diffn_r(const struct mp_chmap *a, const struct mp_chmap *b)
|
||||
{
|
||||
struct mp_chmap diff1, diff2;
|
||||
int mindiff = INT_MAX;
|
||||
|
||||
mp_chmap_diff(a, b, &diff1);
|
||||
if (mp_chmap_contains(a, b) && best_diffs[UPMIX_IDX] > diff1.num) {
|
||||
best[UPMIX_IDX] = *a;
|
||||
best_diffs[UPMIX_IDX] = diff1.num;
|
||||
return true;
|
||||
for (int i = -1; i < (int)MP_ARRAY_SIZE(speaker_replacements); i++) {
|
||||
struct mp_chmap ar = *a;
|
||||
if (i >= 0) {
|
||||
struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
|
||||
if (!replace_speakers(&ar, r))
|
||||
continue;
|
||||
}
|
||||
int d = mp_chmap_diffn(&ar, b);
|
||||
if (d < mindiff)
|
||||
mindiff = d;
|
||||
}
|
||||
|
||||
mp_chmap_diff(b, a, &diff2);
|
||||
if (mp_chmap_contains(b, a) && best_diffs[DOWNMIX_IDX] > diff2.num) {
|
||||
best[DOWNMIX_IDX] = *a;
|
||||
best_diffs[DOWNMIX_IDX] = diff2.num;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (diff1.num > 0 && best_diffs[FALLBACK_IDX] > diff1.num) {
|
||||
best[FALLBACK_IDX] = *a;
|
||||
best_diffs[FALLBACK_IDX] = diff1.num;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (diff2.num > 0 && best_diffs[FALLBACK_IDX] > diff2.num) {
|
||||
best[FALLBACK_IDX] = *a;
|
||||
best_diffs[FALLBACK_IDX] = diff2.num;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine which channel map to fallback to given a source channel map. It
|
||||
// uses the following heuristic:
|
||||
// 1) If mono is required always prefer stereo to a multichannel upmix.
|
||||
// 2) Search for an upmix that is an exact superset of the required chmap.
|
||||
// 3) Search for a downmix that is the exact subset of the required chmap.
|
||||
// 4) Search for either an upmix or downmix that is the closest (minimum
|
||||
// difference of speakers) to the required chmap.
|
||||
bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map)
|
||||
{
|
||||
// special case: if possible always fallback mono to stereo (instead of
|
||||
// looking for a multichannel upmix)
|
||||
// Special-case: we consider stereo a replacement for mono. (This is not
|
||||
// true in the other direction. Also, fl-fr is generally not a replacement
|
||||
// for fc. Thus it's not part of the speaker replacement list.)
|
||||
struct mp_chmap mono = MP_CHMAP_INIT_MONO;
|
||||
struct mp_chmap stereo = MP_CHMAP_INIT_STEREO;
|
||||
if (mp_chmap_equals(&mono, map) && test_layout(s, &stereo)) {
|
||||
*map = stereo;
|
||||
return true;
|
||||
}
|
||||
if (mp_chmap_equals(&mono, b) && mp_chmap_equals(&stereo, a))
|
||||
mindiff = 0;
|
||||
|
||||
int best_diffs[] = { INT_MAX, INT_MAX, INT_MAX };
|
||||
struct mp_chmap best[] = { {0}, {0}, {0} };
|
||||
return mindiff;
|
||||
}
|
||||
|
||||
// Decide whether we should prefer old or new for the requested layout.
|
||||
// Return true if new should be used, false if old should be used.
|
||||
// If old is empty, always return new (initial case).
|
||||
static bool mp_chmap_is_better(struct mp_chmap *req, struct mp_chmap *old,
|
||||
struct mp_chmap *new)
|
||||
{
|
||||
// Initial case
|
||||
if (!old->num)
|
||||
return true;
|
||||
|
||||
// Exact pick - this also ensures that the best layout is chosen if the
|
||||
// layouts are the same, but with different order of channels.
|
||||
if (mp_chmap_equals(req, old))
|
||||
return false;
|
||||
if (mp_chmap_equals(req, new))
|
||||
return true;
|
||||
|
||||
// If there's no exact match, strictly do a preferred conversion.
|
||||
bool old_pref = test_preferred_remix(req, old);
|
||||
bool new_pref = test_preferred_remix(req, new);
|
||||
if (old_pref && !new_pref)
|
||||
return false;
|
||||
if (!old_pref && new_pref)
|
||||
return true;
|
||||
|
||||
int old_lost_r = mp_chmap_diffn_r(req, old); // num. channels only in req
|
||||
int new_lost_r = mp_chmap_diffn_r(req, new);
|
||||
|
||||
// Imperfect upmix (no real superset) - minimize lost channels
|
||||
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);
|
||||
|
||||
// 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
|
||||
// padding channels isn't really meaningful.
|
||||
if (new_p.num != old_p.num)
|
||||
return new_p.num < old_p.num;
|
||||
|
||||
// Again, with physical channels (minimizes number of NA channels).
|
||||
return new->num < old->num;
|
||||
}
|
||||
|
||||
// Determine which channel map to fallback to given a source channel map.
|
||||
bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map)
|
||||
{
|
||||
struct mp_chmap best = {0};
|
||||
|
||||
for (int n = 0; n < s->num_chmaps; n++) {
|
||||
struct mp_chmap e = s->chmaps[n];
|
||||
@@ -264,25 +319,13 @@ bool mp_chmap_sel_fallback(const struct mp_chmap_sel *s, struct mp_chmap *map)
|
||||
if (mp_chmap_is_unknown(&e))
|
||||
continue;
|
||||
|
||||
if (test_fallbacks(&e, map, best_diffs, best))
|
||||
continue;
|
||||
|
||||
// in case we didn't match any fallback retry after replacing speakers
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(speaker_replacements); i++) {
|
||||
struct mp_chmap t = e;
|
||||
struct mp_chmap *r = (struct mp_chmap *)speaker_replacements[i];
|
||||
if (replace_speakers(&t, r)) {
|
||||
if (test_fallbacks(&t, map, best_diffs, best))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (mp_chmap_is_better(map, &best, &e))
|
||||
best = e;
|
||||
}
|
||||
|
||||
for (int i = UPMIX_IDX; i < MP_ARRAY_SIZE(best); i++) {
|
||||
if (best_diffs[i] < INT_MAX) {
|
||||
*map = best[i];
|
||||
return true;
|
||||
}
|
||||
if (best.num) {
|
||||
*map = best;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -116,7 +116,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
lavc_context->codec_tag = sh->format;
|
||||
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;
|
||||
@@ -126,11 +126,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&sh_audio->channels);
|
||||
|
||||
// demux_mkv
|
||||
if (sh_audio->codecdata_len && sh_audio->codecdata &&
|
||||
!lavc_context->extradata) {
|
||||
mp_lavc_set_extradata(lavc_context, sh_audio->codecdata,
|
||||
sh_audio->codecdata_len);
|
||||
}
|
||||
mp_lavc_set_extradata(lavc_context, sh->extradata, sh->extradata_size);
|
||||
|
||||
if (sh->lav_headers)
|
||||
mp_copy_lav_codec_headers(lavc_context, sh->lav_headers);
|
||||
|
||||
+88
-17
@@ -34,10 +34,12 @@
|
||||
|
||||
struct spdifContext {
|
||||
struct mp_log *log;
|
||||
enum AVCodecID codec_id;
|
||||
AVFormatContext *lavf_ctx;
|
||||
int out_buffer_len;
|
||||
uint8_t out_buffer[OUTBUF_SIZE];
|
||||
bool need_close;
|
||||
bool use_dts_hd;
|
||||
struct mp_audio fmt;
|
||||
};
|
||||
|
||||
@@ -76,17 +78,78 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
struct spdifContext *spdif_ctx = talloc_zero(NULL, struct spdifContext);
|
||||
da->priv = spdif_ctx;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->use_dts_hd = da->opts->dtshd;
|
||||
|
||||
if (strcmp(decoder, "dts-hd") == 0) {
|
||||
decoder = "dts";
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
}
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(decoder);
|
||||
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
int profile = FF_PROFILE_UNKNOWN;
|
||||
AVCodecContext *ctx = NULL;
|
||||
AVFrame *frame = NULL;
|
||||
|
||||
AVCodec *codec = avcodec_find_decoder(spdif_ctx->codec_id);
|
||||
if (!codec)
|
||||
goto done;
|
||||
|
||||
frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
goto done;
|
||||
|
||||
ctx = avcodec_alloc_context3(codec);
|
||||
if (!ctx)
|
||||
goto done;
|
||||
|
||||
if (avcodec_open2(ctx, codec, NULL) < 0) {
|
||||
av_free(ctx); // don't attempt to avcodec_close() an unopened ctx
|
||||
ctx = NULL;
|
||||
goto done;
|
||||
}
|
||||
|
||||
int got_frame = 0;
|
||||
if (avcodec_decode_audio4(ctx, frame, &got_frame, pkt) < 1 || !got_frame)
|
||||
goto done;
|
||||
|
||||
profile = ctx->profile;
|
||||
|
||||
done:
|
||||
av_frame_free(&frame);
|
||||
if (ctx)
|
||||
avcodec_close(ctx);
|
||||
avcodec_free_context(&ctx);
|
||||
|
||||
if (profile == FF_PROFILE_UNKNOWN)
|
||||
MP_WARN(da, "Failed to parse codec profile.\n");
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
int profile = FF_PROFILE_UNKNOWN;
|
||||
if (spdif_ctx->codec_id == AV_CODEC_ID_DTS)
|
||||
profile = determine_codec_profile(da, pkt);
|
||||
|
||||
AVFormatContext *lavf_ctx = avformat_alloc_context();
|
||||
if (!lavf_ctx)
|
||||
goto fail;
|
||||
|
||||
spdif_ctx->lavf_ctx = lavf_ctx;
|
||||
|
||||
lavf_ctx->oformat = av_guess_format("spdif", NULL, NULL);
|
||||
if (!lavf_ctx->oformat)
|
||||
goto fail;
|
||||
|
||||
spdif_ctx->lavf_ctx = lavf_ctx;
|
||||
|
||||
void *buffer = av_mallocz(OUTBUF_SIZE);
|
||||
if (!buffer)
|
||||
abort();
|
||||
@@ -106,14 +169,14 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
if (!stream)
|
||||
goto fail;
|
||||
|
||||
stream->codec->codec_id = mp_codec_to_av_codec_id(decoder);
|
||||
stream->codec->codec_id = spdif_ctx->codec_id;
|
||||
|
||||
AVDictionary *format_opts = NULL;
|
||||
|
||||
int num_channels = 0;
|
||||
int sample_format = 0;
|
||||
int samplerate = 0;
|
||||
switch (stream->codec->codec_id) {
|
||||
switch (spdif_ctx->codec_id) {
|
||||
case AV_CODEC_ID_AAC:
|
||||
sample_format = AF_FORMAT_S_AAC;
|
||||
samplerate = 48000;
|
||||
@@ -124,18 +187,21 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_DTS:
|
||||
if (da->opts->dtshd) {
|
||||
case AV_CODEC_ID_DTS: {
|
||||
bool is_hd = profile == FF_PROFILE_DTS_HD_HRA ||
|
||||
profile == FF_PROFILE_DTS_HD_MA;
|
||||
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;
|
||||
samplerate = 192000;
|
||||
num_channels = 2*4;
|
||||
sample_format = AF_FORMAT_S_DTSHD;
|
||||
samplerate = 192000;
|
||||
num_channels = 2*4;
|
||||
} else {
|
||||
sample_format = AF_FORMAT_S_DTS;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
sample_format = AF_FORMAT_S_DTS;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case AV_CODEC_ID_EAC3:
|
||||
sample_format = AF_FORMAT_S_EAC3;
|
||||
samplerate = 192000;
|
||||
@@ -167,17 +233,16 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
|
||||
spdif_ctx->need_close = true;
|
||||
|
||||
return 1;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
uninit(da);
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
|
||||
@@ -195,9 +260,13 @@ static int decode_packet(struct dec_audio *da, struct mp_audio **out)
|
||||
da->pts = mpkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
int ret = av_write_frame(lavf_ctx, &pkt);
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
return AD_ERR;
|
||||
}
|
||||
int ret = av_write_frame(spdif_ctx->lavf_ctx, &pkt);
|
||||
talloc_free(mpkt);
|
||||
avio_flush(lavf_ctx->pb);
|
||||
avio_flush(spdif_ctx->lavf_ctx->pb);
|
||||
if (ret < 0)
|
||||
return AD_ERR;
|
||||
|
||||
@@ -235,6 +304,8 @@ static void add_decoders(struct mp_decoder_list *list)
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
}
|
||||
}
|
||||
mp_add_decoder(list, "spdif", "dts", "dts-hd",
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
}
|
||||
|
||||
const struct ad_functions ad_spdif = {
|
||||
|
||||
@@ -52,6 +52,7 @@ static const struct ad_functions * const ad_drivers[] = {
|
||||
|
||||
static void uninit_decoder(struct dec_audio *d_audio)
|
||||
{
|
||||
audio_reset_decoding(d_audio);
|
||||
if (d_audio->ad_driver) {
|
||||
MP_VERBOSE(d_audio, "Uninit audio decoder.\n");
|
||||
d_audio->ad_driver->uninit(d_audio);
|
||||
@@ -59,6 +60,8 @@ 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)
|
||||
@@ -81,11 +84,21 @@ struct mp_decoder_list *audio_decoder_list(void)
|
||||
return list;
|
||||
}
|
||||
|
||||
static struct mp_decoder_list *audio_select_decoders(const char *codec,
|
||||
char *selection)
|
||||
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;
|
||||
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new = mp_select_decoders(list, codec, selection);
|
||||
struct mp_decoder_list *new =
|
||||
mp_select_decoders(list, codec, opts->audio_decoders);
|
||||
if (d_audio->spdif_passthrough) {
|
||||
struct mp_decoder_list *spdif =
|
||||
mp_select_decoder_list(list, codec, "spdif", opts->audio_spdif);
|
||||
mp_append_decoders(spdif, new);
|
||||
talloc_free(new);
|
||||
new = spdif;
|
||||
}
|
||||
talloc_free(list);
|
||||
return new;
|
||||
}
|
||||
@@ -99,14 +112,13 @@ static const struct ad_functions *find_driver(const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders)
|
||||
int audio_init_best_codec(struct dec_audio *d_audio)
|
||||
{
|
||||
uninit_decoder(d_audio);
|
||||
assert(!d_audio->ad_driver);
|
||||
audio_reset_decoding(d_audio);
|
||||
|
||||
struct mp_decoder_entry *decoder = NULL;
|
||||
struct mp_decoder_list *list =
|
||||
audio_select_decoders(d_audio->header->codec, audio_decoders);
|
||||
struct mp_decoder_list *list = audio_select_decoders(d_audio);
|
||||
|
||||
mp_print_decoders(d_audio->log, MSGL_V, "Codec list:", list);
|
||||
|
||||
@@ -145,8 +157,8 @@ void audio_uninit(struct dec_audio *d_audio)
|
||||
if (!d_audio)
|
||||
return;
|
||||
MP_VERBOSE(d_audio, "Uninit audio filters...\n");
|
||||
af_destroy(d_audio->afilter);
|
||||
uninit_decoder(d_audio);
|
||||
af_destroy(d_audio->afilter);
|
||||
talloc_free(d_audio->waiting);
|
||||
talloc_free(d_audio);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
bool spdif_passthrough;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct af_stream *afilter;
|
||||
@@ -57,7 +58,7 @@ enum {
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders);
|
||||
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);
|
||||
|
||||
+39
-137
@@ -31,70 +31,37 @@
|
||||
#include "af.h"
|
||||
|
||||
// Static list of filters
|
||||
extern const struct af_info af_info_dummy;
|
||||
extern const struct af_info af_info_delay;
|
||||
extern const struct af_info af_info_channels;
|
||||
extern const struct af_info af_info_format;
|
||||
extern const struct af_info af_info_force;
|
||||
extern const struct af_info af_info_volume;
|
||||
extern const struct af_info af_info_equalizer;
|
||||
extern const struct af_info af_info_pan;
|
||||
extern const struct af_info af_info_surround;
|
||||
extern const struct af_info af_info_sub;
|
||||
extern const struct af_info af_info_export;
|
||||
extern const struct af_info af_info_drc;
|
||||
extern const struct af_info af_info_extrastereo;
|
||||
extern const struct af_info af_info_lavcac3enc;
|
||||
extern const struct af_info af_info_lavrresample;
|
||||
extern const struct af_info af_info_sweep;
|
||||
extern const struct af_info af_info_hrtf;
|
||||
extern const struct af_info af_info_ladspa;
|
||||
extern const struct af_info af_info_center;
|
||||
extern const struct af_info af_info_sinesuppress;
|
||||
extern const struct af_info af_info_karaoke;
|
||||
extern const struct af_info af_info_scaletempo;
|
||||
extern const struct af_info af_info_bs2b;
|
||||
extern const struct af_info af_info_lavfi;
|
||||
extern const struct af_info af_info_convert24;
|
||||
extern const struct af_info af_info_convertsignendian;
|
||||
extern const struct af_info af_info_rubberband;
|
||||
|
||||
static const struct af_info *const filter_list[] = {
|
||||
&af_info_dummy,
|
||||
&af_info_delay,
|
||||
&af_info_channels,
|
||||
&af_info_format,
|
||||
&af_info_volume,
|
||||
&af_info_equalizer,
|
||||
&af_info_pan,
|
||||
&af_info_surround,
|
||||
&af_info_sub,
|
||||
&af_info_export,
|
||||
&af_info_drc,
|
||||
&af_info_extrastereo,
|
||||
&af_info_lavcac3enc,
|
||||
&af_info_lavrresample,
|
||||
&af_info_sweep,
|
||||
&af_info_hrtf,
|
||||
#if HAVE_LADSPA
|
||||
&af_info_ladspa,
|
||||
#endif
|
||||
#if HAVE_RUBBERBAND
|
||||
&af_info_rubberband,
|
||||
#endif
|
||||
&af_info_center,
|
||||
&af_info_sinesuppress,
|
||||
&af_info_karaoke,
|
||||
&af_info_scaletempo,
|
||||
#if HAVE_LIBBS2B
|
||||
&af_info_bs2b,
|
||||
#endif
|
||||
#if HAVE_LIBAVFILTER
|
||||
&af_info_lavfi,
|
||||
#endif
|
||||
// Must come last, because they're fallback format conversion filter
|
||||
&af_info_convert24,
|
||||
&af_info_convertsignendian,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -192,24 +159,11 @@ static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
MP_ERR(s, "Couldn't find audio filter '%s'.\n", name);
|
||||
return NULL;
|
||||
}
|
||||
const struct af_info *info = desc.p;
|
||||
/* Make sure that the filter is not already in the list if it is
|
||||
non-reentrant */
|
||||
if (info->flags & AF_FLAGS_NOT_REENTRANT) {
|
||||
for (struct af_instance *cur = s->first; cur; cur = cur->next) {
|
||||
if (cur->info == info) {
|
||||
MP_ERR(s, "There can only be one "
|
||||
"instance of the filter '%s' in each stream\n", name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MP_VERBOSE(s, "Adding filter %s \n", name);
|
||||
|
||||
struct af_instance *af = talloc_zero(NULL, struct af_instance);
|
||||
*af = (struct af_instance) {
|
||||
.info = info,
|
||||
.info = desc.p,
|
||||
.data = talloc_zero(af, struct mp_audio),
|
||||
.log = mp_log_new(af, s->log, name),
|
||||
.replaygain_data = s->replaygain_data,
|
||||
@@ -334,89 +288,9 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
}
|
||||
|
||||
static int af_count_filters(struct af_stream *s)
|
||||
{
|
||||
int count = 0;
|
||||
for (struct af_instance *af = s->first; af; af = af->next)
|
||||
count++;
|
||||
return count;
|
||||
}
|
||||
|
||||
// Finds the first conversion filter on the way from srcfmt to dstfmt.
|
||||
// Conversions form a DAG: each node is a format/filter pair, and possible
|
||||
// conversions are edges. We search the DAG for the shortest path.
|
||||
// Some cases visit the same filter multiple times, but with different formats
|
||||
// (like u24le->s8), so one node per format or filter separate is not enough.
|
||||
// Returns the filter and dest. format for the first conversion step.
|
||||
// (So we know what conversion filter with what format to insert next.)
|
||||
static char *af_find_conversion_filter(int srcfmt, int *dstfmt)
|
||||
{
|
||||
#define MAX_NODES (64 * 32)
|
||||
int num_fmt = 0, num_filt = 0;
|
||||
for (int n = 0; filter_list[n]; n++)
|
||||
num_filt = n + 1;
|
||||
for (int n = 0; af_fmtstr_table[n].format; n++)
|
||||
num_fmt = n + 1;
|
||||
|
||||
int num_nodes = num_fmt * num_filt;
|
||||
assert(num_nodes < MAX_NODES);
|
||||
|
||||
bool visited[MAX_NODES] = {0};
|
||||
unsigned char distance[MAX_NODES];
|
||||
short previous[MAX_NODES] = {0};
|
||||
for (int n = 0; n < num_nodes; n++) {
|
||||
distance[n] = 255;
|
||||
if (af_fmtstr_table[n % num_fmt].format == srcfmt)
|
||||
distance[n] = 0;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
int next = -1;
|
||||
for (int n = 0; n < num_nodes; n++) {
|
||||
if (!visited[n] && (next < 0 || (distance[n] < distance[next])))
|
||||
next = n;
|
||||
}
|
||||
if (next < 0 || distance[next] == 255)
|
||||
return NULL;
|
||||
visited[next] = true;
|
||||
|
||||
int fmt = next % num_fmt;
|
||||
if (af_fmtstr_table[fmt].format == *dstfmt) {
|
||||
// Best match found
|
||||
for (int cur = next; cur >= 0; cur = previous[cur] - 1) {
|
||||
if (distance[cur] == 1) {
|
||||
*dstfmt = af_fmtstr_table[cur % num_fmt].format;
|
||||
return (char *)filter_list[cur / num_fmt]->name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int n = 0; filter_list[n]; n++) {
|
||||
const struct af_info *af = filter_list[n];
|
||||
if (!af->test_conversion)
|
||||
continue;
|
||||
for (int i = 0; af_fmtstr_table[i].format; i++) {
|
||||
if (i != fmt && af->test_conversion(af_fmtstr_table[fmt].format,
|
||||
af_fmtstr_table[i].format))
|
||||
{
|
||||
int other = n * num_fmt + i;
|
||||
int ndist = distance[next] + 1;
|
||||
if (ndist < distance[other]) {
|
||||
distance[other] = ndist;
|
||||
previous[other] = next + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0);
|
||||
#undef MAX_NODES
|
||||
}
|
||||
|
||||
static bool af_is_conversion_filter(struct af_instance *af)
|
||||
{
|
||||
return af && af->info->test_conversion != NULL;
|
||||
return af && strcmp(af->info->name, "lavrresample") == 0;
|
||||
}
|
||||
|
||||
// in is what af can take as input - insert a conversion filter if the actual
|
||||
@@ -436,21 +310,24 @@ static int af_fix_format_conversion(struct af_stream *s,
|
||||
if (actual.format == in.format)
|
||||
return AF_FALSE;
|
||||
int dstfmt = in.format;
|
||||
char *filter = af_find_conversion_filter(actual.format, &dstfmt);
|
||||
if (!filter)
|
||||
char *filter = "lavrresample";
|
||||
if (!af_lavrresample_test_conversion(actual.format, dstfmt))
|
||||
return AF_ERROR;
|
||||
if (strcmp(filter, prev->info->name) == 0) {
|
||||
if (prev->control(prev, AF_CONTROL_SET_FORMAT, &dstfmt) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_ERROR;
|
||||
}
|
||||
struct af_instance *new = af_prepend(s, af, filter, NULL);
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_FORMAT, &dstfmt)))
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_FORMAT, &dstfmt))) {
|
||||
af_remove(s, new);
|
||||
return rv;
|
||||
}
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
}
|
||||
@@ -528,8 +405,8 @@ static int af_reinit(struct af_stream *s)
|
||||
// Start with the second filter, as the first filter is the special input
|
||||
// filter which needs no initialization.
|
||||
struct af_instance *af = s->first->next;
|
||||
// Up to 7 retries per filter (channel, rate, 5x format conversions)
|
||||
int max_retry = af_count_filters(s) * 7;
|
||||
// Up to 4 retries per filter (channel, rate, format conversions)
|
||||
int max_retry = 4;
|
||||
int retry = 0;
|
||||
while (af) {
|
||||
if (retry >= max_retry)
|
||||
@@ -575,9 +452,9 @@ static int af_reinit(struct af_stream *s)
|
||||
int fmt_in1 = af->prev->data->format;
|
||||
int fmt_in2 = in.format;
|
||||
if (af_fmt_is_valid(fmt_in1) && af_fmt_is_valid(fmt_in2)) {
|
||||
bool spd1 = AF_FORMAT_IS_IEC61937(fmt_in1);
|
||||
bool spd2 = AF_FORMAT_IS_IEC61937(fmt_in2);
|
||||
if (spd1 != spd2) {
|
||||
bool spd1 = af_fmt_is_spdif(fmt_in1);
|
||||
bool spd2 = af_fmt_is_spdif(fmt_in2);
|
||||
if (spd1 != spd2 && af->next) {
|
||||
MP_WARN(af, "Filter %s apparently cannot be used due to "
|
||||
"spdif passthrough - removing it.\n",
|
||||
af->info->name);
|
||||
@@ -601,6 +478,8 @@ static int af_reinit(struct af_stream *s)
|
||||
af->info->name, rv);
|
||||
goto error;
|
||||
}
|
||||
if (af && !af->auto_inserted)
|
||||
retry = 0;
|
||||
}
|
||||
|
||||
/* Set previously unset fields in s->output to those of the filter chain
|
||||
@@ -733,7 +612,7 @@ struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
// Reinitalize the filter list
|
||||
// Reinitialize the filter list
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_remove_by_label(s, label);
|
||||
return NULL;
|
||||
@@ -803,6 +682,18 @@ void af_control_all(struct af_stream *s, int cmd, void *arg)
|
||||
af->control(af, cmd, arg);
|
||||
}
|
||||
|
||||
int af_control_by_label(struct af_stream *s, int cmd, void *arg, bstr label)
|
||||
{
|
||||
char *label_str = bstrdup0(NULL, label);
|
||||
struct af_instance *cur = af_find_by_label(s, label_str);
|
||||
talloc_free(label_str);
|
||||
if (cur) {
|
||||
return cur->control ? cur->control(cur, cmd, arg) : CONTROL_NA;
|
||||
} else {
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
@@ -832,6 +723,16 @@ static struct mp_audio *af_dequeue_output_frame(struct af_instance *af)
|
||||
return res;
|
||||
}
|
||||
|
||||
static void read_remaining(struct af_instance *af)
|
||||
{
|
||||
int num_frames;
|
||||
do {
|
||||
num_frames = af->num_out_queued;
|
||||
if (!af->filter_out || af->filter_out(af) < 0)
|
||||
break;
|
||||
} while (num_frames != af->num_out_queued);
|
||||
}
|
||||
|
||||
static int af_do_filter(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame)
|
||||
@@ -871,6 +772,7 @@ int af_output_frame(struct af_stream *s, bool eof)
|
||||
// Flush remaining frames on EOF, but do that only if the previous
|
||||
// filters have been flushed (i.e. they have no more output).
|
||||
if (eof && !last) {
|
||||
read_remaining(cur);
|
||||
int r = af_do_filter(cur, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
+11
-14
@@ -27,6 +27,7 @@
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/audio.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
|
||||
struct af_instance;
|
||||
struct mpv_global;
|
||||
@@ -34,10 +35,6 @@ struct mpv_global;
|
||||
// Number of channels
|
||||
#define AF_NCH MP_NUM_CHANNELS
|
||||
|
||||
// Flags used for defining the behavior of an audio filter
|
||||
#define AF_FLAGS_REENTRANT 0x00000000
|
||||
#define AF_FLAGS_NOT_REENTRANT 0x00000001
|
||||
|
||||
// Flags for af->filter()
|
||||
#define AF_FILTER_FLAG_EOF 1
|
||||
|
||||
@@ -46,9 +43,7 @@ struct mpv_global;
|
||||
struct af_info {
|
||||
const char *info;
|
||||
const char *name;
|
||||
const int flags;
|
||||
int (*open)(struct af_instance *vf);
|
||||
bool (*test_conversion)(int src_format, int dst_format);
|
||||
int priv_size;
|
||||
const void *priv_defaults;
|
||||
const struct m_option *options;
|
||||
@@ -105,13 +100,12 @@ struct af_stream {
|
||||
};
|
||||
|
||||
// Return values
|
||||
#define AF_DETACH 2
|
||||
#define AF_OK 1
|
||||
#define AF_TRUE 1
|
||||
#define AF_FALSE 0
|
||||
#define AF_UNKNOWN -1
|
||||
#define AF_ERROR -2
|
||||
#define AF_FATAL -3
|
||||
#define AF_DETACH (CONTROL_OK + 1)
|
||||
#define AF_OK CONTROL_OK
|
||||
#define AF_TRUE CONTROL_TRUE
|
||||
#define AF_FALSE CONTROL_FALSE
|
||||
#define AF_UNKNOWN CONTROL_UNKNOWN
|
||||
#define AF_ERROR CONTROL_ERROR
|
||||
|
||||
// Parameters for af_control_*
|
||||
enum af_control {
|
||||
@@ -128,6 +122,7 @@ enum af_control {
|
||||
AF_CONTROL_GET_PAN_BALANCE,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
|
||||
AF_CONTROL_GET_METADATA,
|
||||
};
|
||||
|
||||
// Argument for AF_CONTROL_SET_PAN_LEVEL
|
||||
@@ -146,6 +141,7 @@ int af_remove_by_label(struct af_stream *s, char *label);
|
||||
struct af_instance *af_find_by_label(struct af_stream *s, char *label);
|
||||
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);
|
||||
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame);
|
||||
@@ -158,8 +154,9 @@ double af_calc_delay(struct af_stream *s);
|
||||
|
||||
int af_test_output(struct af_instance *af, struct mp_audio *out);
|
||||
|
||||
int af_from_dB(int n, float *in, float *out, float k, float mi, float ma);
|
||||
int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
|
||||
float af_softclip(float a);
|
||||
|
||||
bool af_lavrresample_test_conversion(int src_format, int dst_format);
|
||||
|
||||
#endif /* MPLAYER_AF_H */
|
||||
@@ -1,172 +0,0 @@
|
||||
/*
|
||||
* The Bauer stereophonic-to-binaural DSP using bs2b library:
|
||||
* http://bs2b.sourceforge.net/
|
||||
*
|
||||
* Copyright (c) 2009 Andrew Savchenko
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <bs2b.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "af.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
/// Internal specific data of the filter
|
||||
struct af_bs2b {
|
||||
int fcut; ///< cut frequency in Hz
|
||||
int feed; ///< feed level for low frequencies in 0.1*dB
|
||||
int profile ; ///< profile (available crossfeed presets)
|
||||
t_bs2bdp filter; ///< instance of a library filter
|
||||
};
|
||||
|
||||
#define DEF_FILTER(fmt, name) \
|
||||
static int filter_##name(struct af_instance *af, struct mp_audio *data) \
|
||||
{ \
|
||||
if (!data) \
|
||||
return 0; \
|
||||
if (af_make_writeable(af, data) < 0) { \
|
||||
talloc_free(data); \
|
||||
return -1; \
|
||||
} \
|
||||
bs2b_cross_feed_##name(((struct af_bs2b*)(af->priv))->filter, \
|
||||
(data->planes[0]), data->samples); \
|
||||
af_add_output_frame(af, data); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
#define GET_FILTER(fmt, name) \
|
||||
case AF_FORMAT_##fmt: return filter_##name;
|
||||
|
||||
#define FILTERS \
|
||||
FILTER(FLOAT, f) \
|
||||
FILTER(S32, s32) \
|
||||
FILTER(U32, u32) \
|
||||
FILTER(S24, s24) \
|
||||
FILTER(U24, u24) \
|
||||
FILTER(S16, s16) \
|
||||
FILTER(U16, u16) \
|
||||
FILTER(S8, s8) \
|
||||
FILTER(U8, u8)
|
||||
|
||||
#define FILTER DEF_FILTER
|
||||
FILTERS
|
||||
#undef FILTER
|
||||
|
||||
typedef int (*filter)(struct af_instance *af, struct mp_audio *d);
|
||||
static filter get_filter(int fmt)
|
||||
{
|
||||
switch (fmt) {
|
||||
#define FILTER GET_FILTER
|
||||
FILTERS
|
||||
#undef FILTER
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct af_bs2b *s = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
int format;
|
||||
// Sanity check
|
||||
if (!arg) return AF_ERROR;
|
||||
|
||||
format = ((struct mp_audio*)arg)->format;
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_num_channels(af->data, 2); // bs2b is useful only for 2ch audio
|
||||
mp_audio_set_format(af->data, format);
|
||||
|
||||
/* check for formats supported by libbs2b
|
||||
and assign corresponding handlers */
|
||||
af->filter_frame = get_filter(format);
|
||||
if (!af->filter_frame) {
|
||||
af->filter_frame = filter_f;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
|
||||
// bs2b have srate limits, try to resample if needed
|
||||
if (af->data->rate > BS2B_MAXSRATE || af->data->rate < BS2B_MINSRATE) {
|
||||
af->data->rate = BS2B_DEFAULT_SRATE;
|
||||
MP_WARN(af, "Requested sample rate %d Hz is out of bounds [%d..%d] Hz.\n"
|
||||
"Trying to resample to %d Hz.\n",
|
||||
af->data->rate, BS2B_MINSRATE, BS2B_MAXSRATE, BS2B_DEFAULT_SRATE);
|
||||
}
|
||||
bs2b_set_srate(s->filter, (long)af->data->rate);
|
||||
MP_VERBOSE(af, "using format %s\n",
|
||||
af_fmt_to_str(af->data->format));
|
||||
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
/// Deallocate memory and close library
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct af_bs2b *s = af->priv;
|
||||
if (s->filter)
|
||||
bs2b_close(s->filter);
|
||||
}
|
||||
|
||||
/// Allocate memory, set function pointers and init library
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct af_bs2b *s = af->priv;
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
|
||||
// NULL means failed initialization
|
||||
if (!(s->filter = bs2b_open())) {
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->profile)
|
||||
bs2b_set_level(s->filter, s->profile);
|
||||
// set fcut and feed only if specified, otherwise defaults will be used
|
||||
if (s->fcut)
|
||||
bs2b_set_level_fcut(s->filter, s->fcut);
|
||||
if (s->feed)
|
||||
bs2b_set_level_feed(s->filter, s->feed);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct af_bs2b
|
||||
|
||||
/// Description of this filter
|
||||
const struct af_info af_info_bs2b = {
|
||||
.info = "Bauer stereophonic-to-binaural audio filter",
|
||||
.name = "bs2b",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_bs2b),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("fcut", fcut, 0, BS2B_MINFCUT, BS2B_MAXFCUT),
|
||||
OPT_INTRANGE("feed", feed, 0, BS2B_MINFEED, BS2B_MAXFEED),
|
||||
OPT_CHOICE("profile", profile, 0,
|
||||
({"unset", 0},
|
||||
{"default", BS2B_DEFAULT_CLEVEL},
|
||||
{"cmoy", BS2B_CMOY_CLEVEL},
|
||||
{"jmeier", BS2B_JMEIER_CLEVEL})),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* This filter adds a center channel to the audio stream by
|
||||
* averaging the left and right channel.
|
||||
* There are two runtime controls one for setting which channel
|
||||
* to insert the center-audio into called AF_CONTROL_SUB_CH.
|
||||
*
|
||||
* FIXME: implement a high-pass filter for better results.
|
||||
*
|
||||
* copyright (c) 2005 Alex Beregszaszi
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_center_s
|
||||
{
|
||||
int ch; // Channel number which to insert the filtered data
|
||||
}af_center_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_center_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_channels_old(af->data, MPMAX(s->ch+1,((struct mp_audio*)arg)->nch));
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_center_t* s = af->priv; // Setup for this instance
|
||||
float* a = c->planes[0]; // Audio data
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = c->samples*c->nch; // Number of samples in current audio block
|
||||
int ch = s->ch; // Channel in which to insert the center audio
|
||||
register int i;
|
||||
|
||||
// Run filter
|
||||
for(i=0;i<len;i+=nch){
|
||||
// Average left and right
|
||||
a[i+ch] = (a[i]/2) + (a[i+1]/2);
|
||||
}
|
||||
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = filter_frame;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_center_t
|
||||
const struct af_info af_info_center = {
|
||||
.info = "Audio filter for adding a center channel",
|
||||
.name = "center",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_center_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("channel", ch, 0, 0, AF_NCH - 1, OPTDEF_INT(1)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,122 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
static bool test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
return (src_format == AF_FORMAT_U24 && dst_format == AF_FORMAT_U32) ||
|
||||
(src_format == AF_FORMAT_S24 && dst_format == AF_FORMAT_S32) ||
|
||||
(src_format == AF_FORMAT_U32 && dst_format == AF_FORMAT_U24) ||
|
||||
(src_format == AF_FORMAT_S32 && dst_format == AF_FORMAT_S24);
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (!test_conversion(in->format, out->format))
|
||||
return AF_DETACH;
|
||||
|
||||
if ((in->format & AF_FORMAT_BITS_MASK) == AF_FORMAT_24BIT) {
|
||||
mp_audio_set_format(out, af_fmt_change_bits(in->format, 32));
|
||||
} else if ((in->format & AF_FORMAT_BITS_MASK) == AF_FORMAT_32BIT) {
|
||||
mp_audio_set_format(out, af_fmt_change_bits(in->format, 24));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
|
||||
out->rate = in->rate;
|
||||
mp_audio_set_channels(out, &in->channels);
|
||||
|
||||
assert(test_conversion(in->format, out->format));
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_SET_FORMAT: {
|
||||
mp_audio_set_format(af->data, *(int*)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// The LSB is always ignored.
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
#define SHIFT(x) ((3-(x))*8)
|
||||
#else
|
||||
#define SHIFT(x) (((x)+1)*8)
|
||||
#endif
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, data->samples);
|
||||
if (!out) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(out, data);
|
||||
|
||||
size_t len = mp_audio_psize(data) / data->bps;
|
||||
if (data->bps == 4) {
|
||||
for (int s = 0; s < len; s++) {
|
||||
uint32_t val = *((uint32_t *)data->planes[0] + s);
|
||||
uint8_t *ptr = (uint8_t *)out->planes[0] + s * 3;
|
||||
ptr[0] = val >> SHIFT(0);
|
||||
ptr[1] = val >> SHIFT(1);
|
||||
ptr[2] = val >> SHIFT(2);
|
||||
}
|
||||
} else {
|
||||
for (int s = 0; s < len; s++) {
|
||||
uint8_t *ptr = (uint8_t *)data->planes[0] + s * 3;
|
||||
uint32_t val = ptr[0] << SHIFT(0)
|
||||
| ptr[1] << SHIFT(1)
|
||||
| ptr[2] << SHIFT(2);
|
||||
*((uint32_t *)out->planes[0] + s) = val;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(data);
|
||||
af_add_output_frame(af, out);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
const struct af_info af_info_convert24 = {
|
||||
.info = "Convert between 24 and 32 bit sample format",
|
||||
.name = "convert24",
|
||||
.open = af_open,
|
||||
.test_conversion = test_conversion,
|
||||
};
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "af.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
static bool test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
if ((src_format & AF_FORMAT_PLANAR) ||
|
||||
(dst_format & AF_FORMAT_PLANAR))
|
||||
return false;
|
||||
if (((src_format & ~AF_FORMAT_SIGN_MASK) ==
|
||||
(dst_format & ~AF_FORMAT_SIGN_MASK)) &&
|
||||
((src_format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_I))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (!test_conversion(in->format, out->format))
|
||||
return AF_DETACH;
|
||||
|
||||
out->rate = in->rate;
|
||||
mp_audio_set_channels(out, &in->channels);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_SET_FORMAT: {
|
||||
mp_audio_set_format(af->data, *(int*)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void si2us(void *data, int len, int bps)
|
||||
{
|
||||
ptrdiff_t i = -(len * bps);
|
||||
uint8_t *p = &((uint8_t *)data)[len * bps];
|
||||
if (BYTE_ORDER == LITTLE_ENDIAN && bps > 1)
|
||||
p += bps - 1;
|
||||
if (len <= 0)
|
||||
return;
|
||||
do {
|
||||
p[i] ^= 0x80;
|
||||
} while (i += bps);
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int infmt = data->format;
|
||||
int outfmt = af->data->format;
|
||||
size_t len = data->samples * data->nch;
|
||||
|
||||
if ((infmt & AF_FORMAT_SIGN_MASK) != (outfmt & AF_FORMAT_SIGN_MASK))
|
||||
si2us(data->planes[0], len, data->bps);
|
||||
|
||||
mp_audio_set_format(data, outfmt);
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
const struct af_info af_info_convertsignendian = {
|
||||
.info = "Convert between sample format sign",
|
||||
.name = "convertsign",
|
||||
.open = af_open,
|
||||
.test_conversion = test_conversion,
|
||||
};
|
||||
@@ -131,7 +131,7 @@ static void method1_int16(af_drc_t *s, struct mp_audio *c)
|
||||
data[i] = tmp;
|
||||
}
|
||||
|
||||
// Evaulation of newavg (not 100% accurate because of values clamping)
|
||||
// Evaluation of newavg (not 100% accurate because of values clamping)
|
||||
newavg = s->mul * curavg;
|
||||
|
||||
// Stores computed values for future smoothing
|
||||
@@ -168,7 +168,7 @@ static void method1_float(af_drc_t *s, struct mp_audio *c)
|
||||
for (i = 0; i < len; i++)
|
||||
data[i] *= s->mul;
|
||||
|
||||
// Evaulation of newavg (not 100% accurate because of values clamping)
|
||||
// Evaluation of newavg (not 100% accurate because of values clamping)
|
||||
newavg = s->mul * curavg;
|
||||
|
||||
// Stores computed values for future smoothing
|
||||
@@ -216,7 +216,7 @@ static void method2_int16(af_drc_t *s, struct mp_audio *c)
|
||||
data[i] = tmp;
|
||||
}
|
||||
|
||||
// Evaulation of newavg (not 100% accurate because of values clamping)
|
||||
// Evaluation of newavg (not 100% accurate because of values clamping)
|
||||
newavg = s->mul * curavg;
|
||||
|
||||
// Stores computed values for future smoothing
|
||||
@@ -262,7 +262,7 @@ static void method2_float(af_drc_t *s, struct mp_audio *c)
|
||||
for (i = 0; i < len; i++)
|
||||
data[i] *= s->mul;
|
||||
|
||||
// Evaulation of newavg (not 100% accurate because of values clamping)
|
||||
// Evaluation of newavg (not 100% accurate because of values clamping)
|
||||
newavg = s->mul * curavg;
|
||||
|
||||
// Stores computed values for future smoothing
|
||||
@@ -324,7 +324,6 @@ static int af_open(struct af_instance* af){
|
||||
const struct af_info af_info_drc = {
|
||||
.info = "Dynamic range compression filter",
|
||||
.name = "drc",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_drc_t),
|
||||
.options = (const struct m_option[]) {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* The name speaks for itself. This filter is a dummy and will
|
||||
* not blow up regardless of what you do with it.
|
||||
*
|
||||
* Original author: Anders
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "af.h"
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT: ;
|
||||
*af->data = *(struct mp_audio*)arg;
|
||||
MP_VERBOSE(af, "Was reinitialized: %iHz/%ich/%s\n",
|
||||
af->data->rate,af->data->nch,af_fmt_to_str(af->data->format));
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static int filter(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame=filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
const struct af_info af_info_dummy = {
|
||||
.info = "dummy",
|
||||
.name = "dummy",
|
||||
.open = af_open,
|
||||
};
|
||||
@@ -203,7 +203,6 @@ static int af_open(struct af_instance* af){
|
||||
const struct af_info af_info_equalizer = {
|
||||
.info = "Equalizer audio filter",
|
||||
.name = "equalizer",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_equalizer_t),
|
||||
.options = (const struct m_option[]) {
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
* This audio filter exports the incoming signal to other processes
|
||||
* using memory mapping. The memory mapped area contains a header:
|
||||
* int nch,
|
||||
* int size,
|
||||
* unsigned long long counter (updated every time the contents of
|
||||
* the area changes),
|
||||
* the rest is payload (non-interleaved).
|
||||
*
|
||||
* Authors: Anders; Gustavo Sverzut Barbieri <gustavo.barbieri@ic.unicamp.br>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "osdep/io.h"
|
||||
|
||||
#include "talloc.h"
|
||||
#include "af.h"
|
||||
#include "options/path.h"
|
||||
|
||||
#define DEF_SZ 512 // default buffer size (in samples)
|
||||
#define SHARED_FILE "mpv-af_export" /* default file name
|
||||
(relative to ~/.mpv/ */
|
||||
|
||||
#define SIZE_HEADER (2 * sizeof(int) + sizeof(unsigned long long))
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_export_s
|
||||
{
|
||||
unsigned long long count; // Used for sync
|
||||
void* buf[AF_NCH]; // Buffers for storing the data before it is exported
|
||||
int sz; // Size of buffer in samples
|
||||
int wi; // Write index
|
||||
int fd; // File descriptor to shared memory area
|
||||
char* filename; // File to export data
|
||||
uint8_t *mmap_area; // MMap shared area
|
||||
} af_export_t;
|
||||
|
||||
|
||||
/* Initialization and runtime control
|
||||
af audio filter instance
|
||||
cmd control command
|
||||
arg argument
|
||||
*/
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_export_t* s = af->priv;
|
||||
switch (cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
int i=0;
|
||||
int mapsize;
|
||||
|
||||
// Free previous buffers
|
||||
free(s->buf[0]);
|
||||
|
||||
// unmap previous area
|
||||
if(s->mmap_area)
|
||||
munmap(s->mmap_area, SIZE_HEADER + (af->data->bps*s->sz*af->data->nch));
|
||||
// close previous file descriptor
|
||||
if(s->fd)
|
||||
close(s->fd);
|
||||
|
||||
// Accept only int16_t as input format (which sucks)
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
|
||||
// Allocate new buffers (as one continuous block)
|
||||
s->buf[0] = calloc(s->sz*af->data->nch, af->data->bps);
|
||||
if(NULL == s->buf[0]) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
for(i = 1; i < af->data->nch; i++)
|
||||
s->buf[i] = (uint8_t *)s->buf[0] + i*s->sz*af->data->bps;
|
||||
|
||||
if (!s->filename) {
|
||||
MP_FATAL(af, "No filename set.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
// Init memory mapping
|
||||
s->fd = open(s->filename, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0640);
|
||||
MP_INFO(af, "Exporting to file: %s\n", s->filename);
|
||||
if(s->fd < 0) {
|
||||
MP_FATAL(af, "Could not open/create file: %s\n",
|
||||
s->filename);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
// header + buffer
|
||||
mapsize = (SIZE_HEADER + (af->data->bps * s->sz * af->data->nch));
|
||||
|
||||
// grow file to needed size
|
||||
for(i = 0; i < mapsize; i++){
|
||||
char null = 0;
|
||||
write(s->fd, (void*) &null, 1);
|
||||
}
|
||||
|
||||
// mmap size
|
||||
s->mmap_area = mmap(0, mapsize, PROT_READ|PROT_WRITE,MAP_SHARED, s->fd, 0);
|
||||
if(s->mmap_area == NULL)
|
||||
MP_FATAL(af, "Could not mmap file %s\n", s->filename);
|
||||
MP_INFO(af, "Memory mapped to file: %s (%p)\n",
|
||||
s->filename, s->mmap_area);
|
||||
|
||||
// Initialize header
|
||||
*((int*)s->mmap_area) = af->data->nch;
|
||||
*((int*)s->mmap_area + 1) = s->sz * af->data->bps * af->data->nch;
|
||||
msync(s->mmap_area, mapsize, MS_ASYNC);
|
||||
|
||||
// Use test_output to return FALSE if necessary
|
||||
return af_test_output(af, (struct mp_audio*)arg);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Free allocated memory and clean up other stuff too.
|
||||
af audio filter instance
|
||||
*/
|
||||
static void uninit( struct af_instance* af )
|
||||
{
|
||||
af_export_t* s = af->priv;
|
||||
|
||||
free(s->buf[0]);
|
||||
|
||||
// Free mmaped area
|
||||
if(s->mmap_area)
|
||||
munmap(s->mmap_area, sizeof(af_export_t));
|
||||
|
||||
if(s->fd > -1)
|
||||
close(s->fd);
|
||||
}
|
||||
|
||||
/* Filter data through filter
|
||||
af audio filter instance
|
||||
data audio data
|
||||
*/
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_export_t* s = af->priv; // Setup for this instance
|
||||
int16_t* a = c->planes[0]; // Incomming sound
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = c->samples*c->nch; // Number of sample in data chunk
|
||||
int sz = s->sz; // buffer size (in samples)
|
||||
int flag = 0; // Set to 1 if buffer is filled
|
||||
|
||||
int ch, i;
|
||||
|
||||
// Fill all buffers
|
||||
for(ch = 0; ch < nch; ch++){
|
||||
int wi = s->wi; // Reset write index
|
||||
int16_t* b = s->buf[ch]; // Current buffer
|
||||
|
||||
// Copy data to export buffers
|
||||
for(i = ch; i < len; i += nch){
|
||||
b[wi++] = a[i];
|
||||
if(wi >= sz){ // Don't write outside the end of the buffer
|
||||
flag = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s->wi = wi % s->sz;
|
||||
}
|
||||
|
||||
// Export buffer to mmaped area
|
||||
if(flag){
|
||||
// update buffer in mapped area
|
||||
memcpy(s->mmap_area + SIZE_HEADER, s->buf[0], sz * c->bps * nch);
|
||||
s->count++; // increment counter (to sync)
|
||||
memcpy(s->mmap_area + SIZE_HEADER - sizeof(s->count),
|
||||
&(s->count), sizeof(s->count));
|
||||
}
|
||||
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocate memory and set function pointers
|
||||
af audio filter instance
|
||||
returns AF_OK or AF_ERROR
|
||||
*/
|
||||
static int af_open( struct af_instance* af )
|
||||
{
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
af_export_t *priv = af->priv;
|
||||
|
||||
if (!priv->filename || !priv->filename[0]) {
|
||||
MP_FATAL(af, "no export filename given");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_export_t
|
||||
const struct af_info af_info_export = {
|
||||
.info = "Sound export filter",
|
||||
.name = "export",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_export_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("filename", filename, 0),
|
||||
OPT_INTRANGE("buffersamples", sz, 0, 1, 2048, OPTDEF_INT(DEF_SZ)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2004 Alex Beregszaszi & Pierre Lombard
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_extrastereo_s
|
||||
{
|
||||
float mul;
|
||||
}af_extrastereo_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_force_interleaved_format(af->data);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
if (af->data->format != AF_FORMAT_FLOAT)
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static void play_s16(af_extrastereo_t *s, struct mp_audio* data)
|
||||
{
|
||||
register int i = 0;
|
||||
int16_t *a = (int16_t*)data->planes[0]; // Audio data
|
||||
int len = data->samples*data->nch; // Number of samples
|
||||
int avg, l, r;
|
||||
|
||||
for (i = 0; i < len; i+=2)
|
||||
{
|
||||
avg = (a[i] + a[i + 1]) / 2;
|
||||
|
||||
l = avg + (int)(s->mul * (a[i] - avg));
|
||||
r = avg + (int)(s->mul * (a[i + 1] - avg));
|
||||
|
||||
a[i] = MPCLAMP(l, SHRT_MIN, SHRT_MAX);
|
||||
a[i + 1] = MPCLAMP(r, SHRT_MIN, SHRT_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
static void play_float(af_extrastereo_t *s, struct mp_audio* data)
|
||||
{
|
||||
register int i = 0;
|
||||
float *a = (float*)data->planes[0]; // Audio data
|
||||
int len = data->samples * data->nch; // Number of samples
|
||||
float avg, l, r;
|
||||
|
||||
for (i = 0; i < len; i+=2)
|
||||
{
|
||||
avg = (a[i] + a[i + 1]) / 2;
|
||||
|
||||
l = avg + (s->mul * (a[i] - avg));
|
||||
r = avg + (s->mul * (a[i + 1] - avg));
|
||||
|
||||
a[i] = af_softclip(l);
|
||||
a[i + 1] = af_softclip(r);
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
if (data->format == AF_FORMAT_FLOAT) {
|
||||
play_float(af->priv, data);
|
||||
} else {
|
||||
play_s16(af->priv, data);
|
||||
}
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = filter_frame;
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_extrastereo_t
|
||||
const struct af_info af_info_extrastereo = {
|
||||
.info = "Increase difference between audio channels",
|
||||
.name = "extrastereo",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_extrastereo_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("mul", mul, 0, OPTDEF_FLOAT(2.5)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,670 +0,0 @@
|
||||
/*
|
||||
* Experimental audio filter that mixes 5.1 and 5.1 with matrix
|
||||
* encoded rear channels into headphone signal using FIR filtering
|
||||
* with HRTF.
|
||||
*
|
||||
* Author: ylai
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include <math.h>
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "af.h"
|
||||
#include "dsp.h"
|
||||
|
||||
/* HRTF filter coefficients and adjustable parameters */
|
||||
#include "af_hrtf.h"
|
||||
|
||||
typedef struct af_hrtf_s {
|
||||
/* Lengths */
|
||||
int dlbuflen, hrflen, basslen;
|
||||
/* L, C, R, Ls, Rs channels */
|
||||
float *lf, *rf, *lr, *rr, *cf, *cr;
|
||||
const float *cf_ir, *af_ir, *of_ir, *ar_ir, *or_ir, *cr_ir;
|
||||
int cf_o, af_o, of_o, ar_o, or_o, cr_o;
|
||||
/* Bass */
|
||||
float *ba_l, *ba_r;
|
||||
float *ba_ir;
|
||||
/* Whether to matrix decode the rear center channel */
|
||||
int matrix_mode;
|
||||
/* How to decode the input:
|
||||
0 = 5/5+1 channels
|
||||
1 = 2 channels
|
||||
2 = matrix encoded 2 channels */
|
||||
int decode_mode;
|
||||
/* Full wave rectified (FWR) amplitudes and gain used to steer the
|
||||
active matrix decoding of front channels (variable names
|
||||
lpr/lmr means Lt + Rt, Lt - Rt) */
|
||||
float l_fwr, r_fwr, lpr_fwr, lmr_fwr;
|
||||
float adapt_l_gain, adapt_r_gain, adapt_lpr_gain, adapt_lmr_gain;
|
||||
/* Matrix input decoding require special FWR buffer, since the
|
||||
decoding is done in place. */
|
||||
float *fwrbuf_l, *fwrbuf_r, *fwrbuf_lr, *fwrbuf_rr;
|
||||
/* Rear channel delay buffer for matrix decoding */
|
||||
float *rear_dlbuf;
|
||||
/* Full wave rectified amplitude and gain used to steer the active
|
||||
matrix decoding of center rear channel */
|
||||
float lr_fwr, rr_fwr, lrprr_fwr, lrmrr_fwr;
|
||||
float adapt_lr_gain, adapt_rr_gain;
|
||||
float adapt_lrprr_gain, adapt_lrmrr_gain;
|
||||
/* Cyclic position on the ring buffer */
|
||||
int cyc_pos;
|
||||
int print_flag;
|
||||
int mode;
|
||||
} af_hrtf_t;
|
||||
|
||||
/* Convolution on a ring buffer
|
||||
* nx: length of the ring buffer
|
||||
* nk: length of the convolution kernel
|
||||
* sx: ring buffer
|
||||
* sk: convolution kernel
|
||||
* offset: offset on the ring buffer, can be
|
||||
*/
|
||||
static float conv(const int nx, const int nk, const float *sx, const float *sk,
|
||||
const int offset)
|
||||
{
|
||||
/* k = reminder of offset / nx */
|
||||
int k = offset >= 0 ? offset % nx : nx + (offset % nx);
|
||||
|
||||
if(nk + k <= nx)
|
||||
return af_filter_fir(nk, sx + k, sk);
|
||||
else
|
||||
return af_filter_fir(nk + k - nx, sx, sk + nx - k) +
|
||||
af_filter_fir(nx - k, sx + k, sk);
|
||||
}
|
||||
|
||||
/* Detect when the impulse response starts (significantly) */
|
||||
static int pulse_detect(const float *sx)
|
||||
{
|
||||
/* nmax must be the reference impulse response length (128) minus
|
||||
s->hrflen */
|
||||
const int nmax = 128 - HRTFFILTLEN;
|
||||
const float thresh = IRTHRESH;
|
||||
int i;
|
||||
|
||||
for(i = 0; i < nmax; i++)
|
||||
if(fabs(sx[i]) > thresh)
|
||||
return i;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Fuzzy matrix coefficient transfer function to "lock" the matrix on
|
||||
a effectively passive mode if the gain is approximately 1 */
|
||||
static inline float passive_lock(float x)
|
||||
{
|
||||
const float x1 = x - 1;
|
||||
const float ax1s = fabs(x - 1) * (1.0 / MATAGCLOCK);
|
||||
|
||||
return x1 - x1 / (1 + ax1s * ax1s) + 1;
|
||||
}
|
||||
|
||||
/* Unified active matrix decoder for 2 channel matrix encoded surround
|
||||
sources */
|
||||
static inline void matrix_decode(short *in, const int k, const int il,
|
||||
const int ir, const int decode_rear,
|
||||
const int dlbuflen,
|
||||
float l_fwr, float r_fwr,
|
||||
float lpr_fwr, float lmr_fwr,
|
||||
float *adapt_l_gain, float *adapt_r_gain,
|
||||
float *adapt_lpr_gain, float *adapt_lmr_gain,
|
||||
float *lf, float *rf, float *lr,
|
||||
float *rr, float *cf)
|
||||
{
|
||||
const int kr = (k + MATREARDELAY) % dlbuflen;
|
||||
float l_gain = (l_fwr + r_fwr) /
|
||||
(1 + l_fwr + l_fwr);
|
||||
float r_gain = (l_fwr + r_fwr) /
|
||||
(1 + r_fwr + r_fwr);
|
||||
/* The 2nd axis has strong gain fluctuations, and therefore require
|
||||
limits. The factor corresponds to the 1 / amplification of (Lt
|
||||
- Rt) when (Lt, Rt) is strongly correlated. (e.g. during
|
||||
dialogues). It should be bigger than -12 dB to prevent
|
||||
distortion. */
|
||||
float lmr_lim_fwr = lmr_fwr > M9_03DB * lpr_fwr ?
|
||||
lmr_fwr : M9_03DB * lpr_fwr;
|
||||
float lpr_gain = (lpr_fwr + lmr_lim_fwr) /
|
||||
(1 + lpr_fwr + lpr_fwr);
|
||||
float lmr_gain = (lpr_fwr + lmr_lim_fwr) /
|
||||
(1 + lmr_lim_fwr + lmr_lim_fwr);
|
||||
float lmr_unlim_gain = (lpr_fwr + lmr_fwr) /
|
||||
(1 + lmr_fwr + lmr_fwr);
|
||||
float lpr, lmr;
|
||||
float l_agc, r_agc, lpr_agc, lmr_agc;
|
||||
float f, d_gain, c_gain, c_agc_cfk;
|
||||
|
||||
#if 0
|
||||
static int counter = 0;
|
||||
static FILE *fp_out;
|
||||
|
||||
if(counter == 0)
|
||||
fp_out = fopen("af_hrtf.log", "w");
|
||||
if(counter % 240 == 0)
|
||||
fprintf(fp_out, "%g %g %g %g %g ", counter * (1.0 / 48000),
|
||||
l_gain, r_gain, lpr_gain, lmr_gain);
|
||||
#endif
|
||||
|
||||
/*** AXIS NO. 1: (Lt, Rt) -> (C, Ls, Rs) ***/
|
||||
/* AGC adaption */
|
||||
d_gain = (fabs(l_gain - *adapt_l_gain) +
|
||||
fabs(r_gain - *adapt_r_gain)) * 0.5;
|
||||
f = d_gain * (1.0 / MATAGCTRIG);
|
||||
f = MATAGCDECAY - MATAGCDECAY / (1 + f * f);
|
||||
*adapt_l_gain = (1 - f) * *adapt_l_gain + f * l_gain;
|
||||
*adapt_r_gain = (1 - f) * *adapt_r_gain + f * r_gain;
|
||||
/* Matrix */
|
||||
l_agc = in[il] * passive_lock(*adapt_l_gain);
|
||||
r_agc = in[ir] * passive_lock(*adapt_r_gain);
|
||||
cf[k] = (l_agc + r_agc) * M_SQRT1_2;
|
||||
if(decode_rear) {
|
||||
lr[kr] = rr[kr] = (l_agc - r_agc) * M_SQRT1_2;
|
||||
/* Stereo rear channel is steered with the same AGC steering as
|
||||
the decoding matrix. Note this requires a fast updating AGC
|
||||
at the order of 20 ms (which is the case here). */
|
||||
lr[kr] *= (l_fwr + l_fwr) /
|
||||
(1 + l_fwr + r_fwr);
|
||||
rr[kr] *= (r_fwr + r_fwr) /
|
||||
(1 + l_fwr + r_fwr);
|
||||
}
|
||||
|
||||
/*** AXIS NO. 2: (Lt + Rt, Lt - Rt) -> (L, R) ***/
|
||||
lpr = (in[il] + in[ir]) * M_SQRT1_2;
|
||||
lmr = (in[il] - in[ir]) * M_SQRT1_2;
|
||||
/* AGC adaption */
|
||||
d_gain = fabs(lmr_unlim_gain - *adapt_lmr_gain);
|
||||
f = d_gain * (1.0 / MATAGCTRIG);
|
||||
f = MATAGCDECAY - MATAGCDECAY / (1 + f * f);
|
||||
*adapt_lpr_gain = (1 - f) * *adapt_lpr_gain + f * lpr_gain;
|
||||
*adapt_lmr_gain = (1 - f) * *adapt_lmr_gain + f * lmr_gain;
|
||||
/* Matrix */
|
||||
lpr_agc = lpr * passive_lock(*adapt_lpr_gain);
|
||||
lmr_agc = lmr * passive_lock(*adapt_lmr_gain);
|
||||
lf[k] = (lpr_agc + lmr_agc) * M_SQRT1_2;
|
||||
rf[k] = (lpr_agc - lmr_agc) * M_SQRT1_2;
|
||||
|
||||
/*** CENTER FRONT CANCELLATION ***/
|
||||
/* A heuristic approach exploits that Lt + Rt gain contains the
|
||||
information about Lt, Rt correlation. This effectively reshapes
|
||||
the front and rear "cones" to concentrate Lt + Rt to C and
|
||||
introduce Lt - Rt in L, R. */
|
||||
/* 0.67677 is the emprical lower bound for lpr_gain. */
|
||||
c_gain = 8 * (*adapt_lpr_gain - 0.67677);
|
||||
c_gain = c_gain > 0 ? c_gain : 0;
|
||||
/* c_gain should not be too high, not even reaching full
|
||||
cancellation (~ 0.50 - 0.55 at current AGC implementation), or
|
||||
the center will s0und too narrow. */
|
||||
c_gain = MATCOMPGAIN / (1 + c_gain * c_gain);
|
||||
c_agc_cfk = c_gain * cf[k];
|
||||
lf[k] -= c_agc_cfk;
|
||||
rf[k] -= c_agc_cfk;
|
||||
cf[k] += c_agc_cfk + c_agc_cfk;
|
||||
#if 0
|
||||
if(counter % 240 == 0)
|
||||
fprintf(fp_out, "%g %g %g %g %g\n",
|
||||
*adapt_l_gain, *adapt_r_gain,
|
||||
*adapt_lpr_gain, *adapt_lmr_gain,
|
||||
c_gain);
|
||||
counter++;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void update_ch(af_hrtf_t *s, short *in, const int k)
|
||||
{
|
||||
const int fwr_pos = (k + FWRDURATION) % s->dlbuflen;
|
||||
/* Update the full wave rectified total amplitude */
|
||||
/* Input matrix decoder */
|
||||
if(s->decode_mode == HRTF_MIX_MATRIX2CH) {
|
||||
s->l_fwr += abs(in[0]) - fabs(s->fwrbuf_l[fwr_pos]);
|
||||
s->r_fwr += abs(in[1]) - fabs(s->fwrbuf_r[fwr_pos]);
|
||||
s->lpr_fwr += abs(in[0] + in[1]) -
|
||||
fabs(s->fwrbuf_l[fwr_pos] + s->fwrbuf_r[fwr_pos]);
|
||||
s->lmr_fwr += abs(in[0] - in[1]) -
|
||||
fabs(s->fwrbuf_l[fwr_pos] - s->fwrbuf_r[fwr_pos]);
|
||||
}
|
||||
/* Rear matrix decoder */
|
||||
if(s->matrix_mode) {
|
||||
s->lr_fwr += abs(in[2]) - fabs(s->fwrbuf_lr[fwr_pos]);
|
||||
s->rr_fwr += abs(in[3]) - fabs(s->fwrbuf_rr[fwr_pos]);
|
||||
s->lrprr_fwr += abs(in[2] + in[3]) -
|
||||
fabs(s->fwrbuf_lr[fwr_pos] + s->fwrbuf_rr[fwr_pos]);
|
||||
s->lrmrr_fwr += abs(in[2] - in[3]) -
|
||||
fabs(s->fwrbuf_lr[fwr_pos] - s->fwrbuf_rr[fwr_pos]);
|
||||
}
|
||||
|
||||
switch (s->decode_mode) {
|
||||
case HRTF_MIX_51:
|
||||
/* 5/5+1 channel sources */
|
||||
s->lf[k] = in[0];
|
||||
s->cf[k] = in[4];
|
||||
s->rf[k] = in[1];
|
||||
s->fwrbuf_lr[k] = s->lr[k] = in[2];
|
||||
s->fwrbuf_rr[k] = s->rr[k] = in[3];
|
||||
break;
|
||||
case HRTF_MIX_MATRIX2CH:
|
||||
/* Matrix encoded 2 channel sources */
|
||||
s->fwrbuf_l[k] = in[0];
|
||||
s->fwrbuf_r[k] = in[1];
|
||||
matrix_decode(in, k, 0, 1, 1, s->dlbuflen,
|
||||
s->l_fwr, s->r_fwr,
|
||||
s->lpr_fwr, s->lmr_fwr,
|
||||
&(s->adapt_l_gain), &(s->adapt_r_gain),
|
||||
&(s->adapt_lpr_gain), &(s->adapt_lmr_gain),
|
||||
s->lf, s->rf, s->lr, s->rr, s->cf);
|
||||
break;
|
||||
case HRTF_MIX_STEREO:
|
||||
/* Stereo sources */
|
||||
s->lf[k] = in[0];
|
||||
s->rf[k] = in[1];
|
||||
s->cf[k] = s->lr[k] = s->rr[k] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* We need to update the bass compensation delay line, too. */
|
||||
// TODO: should this use lf/cf/rf etc. instead?
|
||||
s->ba_l[k] = in[0];
|
||||
s->ba_r[k] = in[1];
|
||||
if (s->decode_mode == HRTF_MIX_51) {
|
||||
s->ba_l[k] += in[4] + in[2];
|
||||
s->ba_r[k] += in[4] + in[3];
|
||||
}
|
||||
}
|
||||
|
||||
static void clear_coeff(af_hrtf_t *s, float *c)
|
||||
{
|
||||
memset(c, 0, s->dlbuflen * sizeof(float));
|
||||
}
|
||||
|
||||
static void reset(af_hrtf_t *s)
|
||||
{
|
||||
clear_coeff(s, s->lf);
|
||||
clear_coeff(s, s->rf);
|
||||
clear_coeff(s, s->lr);
|
||||
clear_coeff(s, s->rr);
|
||||
clear_coeff(s, s->cf);
|
||||
clear_coeff(s, s->cr);
|
||||
clear_coeff(s, s->ba_l);
|
||||
clear_coeff(s, s->ba_r);
|
||||
clear_coeff(s, s->fwrbuf_l);
|
||||
clear_coeff(s, s->fwrbuf_r);
|
||||
clear_coeff(s, s->fwrbuf_lr);
|
||||
clear_coeff(s, s->fwrbuf_rr);
|
||||
}
|
||||
|
||||
/* Initialization and runtime control */
|
||||
static int control(struct af_instance *af, int cmd, void* arg)
|
||||
{
|
||||
af_hrtf_t *s = af->priv;
|
||||
int test_output_res;
|
||||
|
||||
switch(cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
reset(s);
|
||||
af->data->rate = 48000;
|
||||
mp_audio_set_channels_old(af->data, ((struct mp_audio*)arg)->nch);
|
||||
if(af->data->nch == 2) {
|
||||
/* 2 channel input */
|
||||
if(s->decode_mode != HRTF_MIX_MATRIX2CH) {
|
||||
/* Default behavior is stereo mixing. */
|
||||
s->decode_mode = HRTF_MIX_STEREO;
|
||||
}
|
||||
} else if (af->data->nch < 5) {
|
||||
mp_audio_set_channels_old(af->data, 5);
|
||||
}
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
test_output_res = af_test_output(af, (struct mp_audio*)arg);
|
||||
// after testing input set the real output format
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
s->print_flag = 1;
|
||||
return test_output_res;
|
||||
case AF_CONTROL_RESET:
|
||||
reset(s);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
/* Deallocate memory */
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
af_hrtf_t *s = af->priv;
|
||||
|
||||
free(s->lf);
|
||||
free(s->rf);
|
||||
free(s->lr);
|
||||
free(s->rr);
|
||||
free(s->cf);
|
||||
free(s->cr);
|
||||
free(s->ba_l);
|
||||
free(s->ba_r);
|
||||
free(s->ba_ir);
|
||||
free(s->fwrbuf_l);
|
||||
free(s->fwrbuf_r);
|
||||
free(s->fwrbuf_lr);
|
||||
free(s->fwrbuf_rr);
|
||||
}
|
||||
|
||||
/* Filter data through filter
|
||||
|
||||
Two "tricks" are used to compensate the "color" of the KEMAR data:
|
||||
|
||||
1. The KEMAR data is refiltered to ensure that the front L, R channels
|
||||
on the same side of the ear are equalized (especially in the high
|
||||
frequencies).
|
||||
|
||||
2. A bass compensation is introduced to ensure that 0-200 Hz are not
|
||||
damped (without any real 3D acoustical image, however).
|
||||
*/
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
af_hrtf_t *s = af->priv;
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
struct mp_audio *outframe =
|
||||
mp_audio_pool_get(af->out_pool, &af->fmt_out, data->samples);
|
||||
if (!outframe) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(outframe, data);
|
||||
|
||||
short *in = data->planes[0]; // Input audio data
|
||||
short *out = outframe->planes[0]; // Output audio data
|
||||
short *end = in + data->samples * data->nch; // Loop end
|
||||
float common, left, right, diff, left_b, right_b;
|
||||
const int dblen = s->dlbuflen, hlen = s->hrflen, blen = s->basslen;
|
||||
|
||||
if(s->print_flag) {
|
||||
s->print_flag = 0;
|
||||
switch (s->decode_mode) {
|
||||
case HRTF_MIX_51:
|
||||
MP_INFO(af, "Using HRTF to mix %s discrete surround into "
|
||||
"L, R channels\n", s->matrix_mode ? "5+1" : "5");
|
||||
break;
|
||||
case HRTF_MIX_STEREO:
|
||||
MP_INFO(af, "Using HRTF to mix stereo into "
|
||||
"L, R channels\n");
|
||||
break;
|
||||
case HRTF_MIX_MATRIX2CH:
|
||||
MP_INFO(af, "Using active matrix to decode 2 channel "
|
||||
"input, HRTF to mix %s matrix surround into "
|
||||
"L, R channels\n", "3/2");
|
||||
break;
|
||||
default:
|
||||
MP_WARN(af, "bogus decode_mode: %d\n", s->decode_mode);
|
||||
break;
|
||||
}
|
||||
|
||||
if(s->matrix_mode)
|
||||
MP_INFO(af, "Using active matrix to decode rear center "
|
||||
"channel\n");
|
||||
}
|
||||
|
||||
/* MPlayer's 5 channel layout (notation for the variable):
|
||||
*
|
||||
* 0: L (LF), 1: R (RF), 2: Ls (LR), 3: Rs (RR), 4: C (CF), matrix
|
||||
* encoded: Cs (CR)
|
||||
*
|
||||
* or: L = left, C = center, R = right, F = front, R = rear
|
||||
*
|
||||
* Filter notation:
|
||||
*
|
||||
* CF
|
||||
* OF AF
|
||||
* Ear->
|
||||
* OR AR
|
||||
* CR
|
||||
*
|
||||
* or: C = center, A = same side, O = opposite, F = front, R = rear
|
||||
*/
|
||||
|
||||
while(in < end) {
|
||||
const int k = s->cyc_pos;
|
||||
|
||||
update_ch(s, in, k);
|
||||
|
||||
/* Simulate a 7.5 ms -20 dB echo of the center channel in the
|
||||
front channels (like reflection from a room wall) - a kind of
|
||||
psycho-acoustically "cheating" to focus the center front
|
||||
channel, which is normally hard to be perceived as front */
|
||||
s->lf[k] += CFECHOAMPL * s->cf[(k + CFECHODELAY) % s->dlbuflen];
|
||||
s->rf[k] += CFECHOAMPL * s->cf[(k + CFECHODELAY) % s->dlbuflen];
|
||||
|
||||
switch (s->decode_mode) {
|
||||
case HRTF_MIX_51:
|
||||
case HRTF_MIX_MATRIX2CH:
|
||||
/* Mixer filter matrix */
|
||||
common = conv(dblen, hlen, s->cf, s->cf_ir, k + s->cf_o);
|
||||
if(s->matrix_mode) {
|
||||
/* In matrix decoding mode, the rear channel gain must be
|
||||
renormalized, as there is an additional channel. */
|
||||
matrix_decode(in, k, 2, 3, 0, s->dlbuflen,
|
||||
s->lr_fwr, s->rr_fwr,
|
||||
s->lrprr_fwr, s->lrmrr_fwr,
|
||||
&(s->adapt_lr_gain), &(s->adapt_rr_gain),
|
||||
&(s->adapt_lrprr_gain), &(s->adapt_lrmrr_gain),
|
||||
s->lr, s->rr, NULL, NULL, s->cr);
|
||||
common +=
|
||||
conv(dblen, hlen, s->cr, s->cr_ir, k + s->cr_o) *
|
||||
M1_76DB;
|
||||
left =
|
||||
( conv(dblen, hlen, s->lf, s->af_ir, k + s->af_o) +
|
||||
conv(dblen, hlen, s->rf, s->of_ir, k + s->of_o) +
|
||||
(conv(dblen, hlen, s->lr, s->ar_ir, k + s->ar_o) +
|
||||
conv(dblen, hlen, s->rr, s->or_ir, k + s->or_o)) *
|
||||
M1_76DB + common);
|
||||
right =
|
||||
( conv(dblen, hlen, s->rf, s->af_ir, k + s->af_o) +
|
||||
conv(dblen, hlen, s->lf, s->of_ir, k + s->of_o) +
|
||||
(conv(dblen, hlen, s->rr, s->ar_ir, k + s->ar_o) +
|
||||
conv(dblen, hlen, s->lr, s->or_ir, k + s->or_o)) *
|
||||
M1_76DB + common);
|
||||
} else {
|
||||
left =
|
||||
( conv(dblen, hlen, s->lf, s->af_ir, k + s->af_o) +
|
||||
conv(dblen, hlen, s->rf, s->of_ir, k + s->of_o) +
|
||||
conv(dblen, hlen, s->lr, s->ar_ir, k + s->ar_o) +
|
||||
conv(dblen, hlen, s->rr, s->or_ir, k + s->or_o) +
|
||||
common);
|
||||
right =
|
||||
( conv(dblen, hlen, s->rf, s->af_ir, k + s->af_o) +
|
||||
conv(dblen, hlen, s->lf, s->of_ir, k + s->of_o) +
|
||||
conv(dblen, hlen, s->rr, s->ar_ir, k + s->ar_o) +
|
||||
conv(dblen, hlen, s->lr, s->or_ir, k + s->or_o) +
|
||||
common);
|
||||
}
|
||||
break;
|
||||
case HRTF_MIX_STEREO:
|
||||
left =
|
||||
( conv(dblen, hlen, s->lf, s->af_ir, k + s->af_o) +
|
||||
conv(dblen, hlen, s->rf, s->of_ir, k + s->of_o));
|
||||
right =
|
||||
( conv(dblen, hlen, s->rf, s->af_ir, k + s->af_o) +
|
||||
conv(dblen, hlen, s->lf, s->of_ir, k + s->of_o));
|
||||
break;
|
||||
default:
|
||||
/* make gcc happy */
|
||||
left = 0.0;
|
||||
right = 0.0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Bass compensation for the lower frequency cut of the HRTF. A
|
||||
cross talk of the left and right channel is introduced to
|
||||
match the directional characteristics of higher frequencies.
|
||||
The bass will not have any real 3D perception, but that is
|
||||
OK (note at 180 Hz, the wavelength is about 2 m, and any
|
||||
spatial perception is impossible). */
|
||||
left_b = conv(dblen, blen, s->ba_l, s->ba_ir, k);
|
||||
right_b = conv(dblen, blen, s->ba_r, s->ba_ir, k);
|
||||
left += (1 - BASSCROSS) * left_b + BASSCROSS * right_b;
|
||||
right += (1 - BASSCROSS) * right_b + BASSCROSS * left_b;
|
||||
/* Also mix the LFE channel (if available) */
|
||||
if(data->nch >= 6) {
|
||||
left += in[5] * M3_01DB;
|
||||
right += in[5] * M3_01DB;
|
||||
}
|
||||
|
||||
/* Amplitude renormalization. */
|
||||
left *= AMPLNORM;
|
||||
right *= AMPLNORM;
|
||||
|
||||
switch (s->decode_mode) {
|
||||
case HRTF_MIX_51:
|
||||
case HRTF_MIX_STEREO:
|
||||
/* "Cheating": linear stereo expansion to amplify the 3D
|
||||
perception. Note: Too much will destroy the acoustic space
|
||||
and may even result in headaches. */
|
||||
diff = STEXPAND2 * (left - right);
|
||||
out[0] = av_clip_int16(left + diff);
|
||||
out[1] = av_clip_int16(right - diff);
|
||||
break;
|
||||
case HRTF_MIX_MATRIX2CH:
|
||||
/* Do attempt any stereo expansion with matrix encoded
|
||||
sources. The L, R channels are already stereo expanded
|
||||
by the steering, any further stereo expansion will sound
|
||||
very unnatural. */
|
||||
out[0] = av_clip_int16(left);
|
||||
out[1] = av_clip_int16(right);
|
||||
break;
|
||||
}
|
||||
|
||||
/* Next sample... */
|
||||
in = &in[data->nch];
|
||||
out = &out[af->data->nch];
|
||||
(s->cyc_pos)--;
|
||||
if(s->cyc_pos < 0)
|
||||
s->cyc_pos += dblen;
|
||||
}
|
||||
|
||||
talloc_free(data);
|
||||
af_add_output_frame(af, outframe);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int allocate(af_hrtf_t *s)
|
||||
{
|
||||
if ((s->lf = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->rf = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->lr = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->rr = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->cf = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->cr = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->ba_l = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->ba_r = malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->fwrbuf_l =
|
||||
malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->fwrbuf_r =
|
||||
malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->fwrbuf_lr =
|
||||
malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
if ((s->fwrbuf_rr =
|
||||
malloc(s->dlbuflen * sizeof(float))) == NULL) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocate memory and set function pointers */
|
||||
static int af_open(struct af_instance* af)
|
||||
{
|
||||
int i;
|
||||
af_hrtf_t *s;
|
||||
float fc;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
|
||||
s = af->priv;
|
||||
|
||||
s->dlbuflen = DELAYBUFLEN;
|
||||
s->hrflen = HRTFFILTLEN;
|
||||
s->basslen = BASSFILTLEN;
|
||||
|
||||
s->cyc_pos = s->dlbuflen - 1;
|
||||
/* With a full (two axis) steering matrix decoder, s->matrix_mode
|
||||
should not be enabled lightly (it will also steer the Ls, Rs
|
||||
channels). */
|
||||
s->matrix_mode = 0;
|
||||
s->decode_mode = HRTF_MIX_51;
|
||||
|
||||
switch (s->mode) {
|
||||
case 0: /* Use matrix rear decoding. */
|
||||
s->matrix_mode = 1;
|
||||
break;
|
||||
case 1: /* Input needs matrix decoding. */
|
||||
s->decode_mode = HRTF_MIX_MATRIX2CH;
|
||||
break;
|
||||
case 2:
|
||||
s->matrix_mode = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
s->print_flag = 1;
|
||||
|
||||
if (allocate(s) != 0) {
|
||||
MP_ERR(af, "Memory allocation error.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
for(i = 0; i < s->dlbuflen; i++)
|
||||
s->lf[i] = s->rf[i] = s->lr[i] = s->rr[i] = s->cf[i] =
|
||||
s->cr[i] = 0;
|
||||
|
||||
s->lr_fwr =
|
||||
s->rr_fwr = 0;
|
||||
|
||||
s->cf_ir = cf_filt + (s->cf_o = pulse_detect(cf_filt));
|
||||
s->af_ir = af_filt + (s->af_o = pulse_detect(af_filt));
|
||||
s->of_ir = of_filt + (s->of_o = pulse_detect(of_filt));
|
||||
s->ar_ir = ar_filt + (s->ar_o = pulse_detect(ar_filt));
|
||||
s->or_ir = or_filt + (s->or_o = pulse_detect(or_filt));
|
||||
s->cr_ir = cr_filt + (s->cr_o = pulse_detect(cr_filt));
|
||||
|
||||
if((s->ba_ir = malloc(s->basslen * sizeof(float))) == NULL) {
|
||||
MP_ERR(af, "Memory allocation error.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
fc = 2.0 * BASSFILTFREQ / (float)af->data->rate;
|
||||
if(af_filter_design_fir(s->basslen, s->ba_ir, &fc, LP | KAISER, 4 * M_PI) ==
|
||||
-1) {
|
||||
MP_ERR(af, "Unable to design low-pass "
|
||||
"filter.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
for(i = 0; i < s->basslen; i++)
|
||||
s->ba_ir[i] *= BASSGAIN;
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_hrtf_t
|
||||
const struct af_info af_info_hrtf = {
|
||||
.info = "HRTF Headphone",
|
||||
.name = "hrtf",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_hrtf_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("mode", mode, 0, ({"m", 0}, {"s", 1}, {"0", 2})),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,510 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AF_HRTF_H
|
||||
#define MPLAYER_AF_HRTF_H
|
||||
|
||||
#define HRTF_MIX_51 0
|
||||
#define HRTF_MIX_STEREO 1
|
||||
#define HRTF_MIX_MATRIX2CH 2
|
||||
|
||||
/* Amplitude scaling factors */
|
||||
#define M17_0DB 0.1414213562
|
||||
#define M9_03DB 0.3535533906
|
||||
#define M6_99DB 0.4472135955
|
||||
#define M4_77DB 0.5773502692
|
||||
#define M3_01DB 0.7071067812
|
||||
#define M1_76DB 0.8164965809
|
||||
|
||||
#define DELAYBUFLEN 1024 /* Length of the delay buffer */
|
||||
#define HRTFFILTLEN 64 /* HRTF filter length */
|
||||
#define IRTHRESH 0.001 /* Impulse response pruning thresh. */
|
||||
|
||||
#define AMPLNORM M6_99DB /* Overall amplitude renormalization */
|
||||
|
||||
#define BASSFILTFREQ 180 /* Bass compensation filter cut (Hz) */
|
||||
#define BASSFILTLEN 193 /* Bass compensation filter length */
|
||||
#define BASSGAIN M_SQRT2 /* Bass compensation gain */
|
||||
#define BASSCROSS 0.35 /* Bass cross talk */
|
||||
|
||||
#define FWRDURATION 240 /* FWR average duration (samples) */
|
||||
#define MATREARDELAY 720 /* Matrix mode rear delay (samples) */
|
||||
|
||||
#define MATAGCTRIG 8.0 /* (Fuzzy) AGC trigger */
|
||||
#define MATAGCDECAY 1.0 /* AGC baseline decay rate (1/samp.) */
|
||||
#define MATAGCLOCK 0.2 /* AGC range (around 1) where the
|
||||
matrix behaves passively */
|
||||
#define MATCOMPGAIN 0.37 /* Cross talk compensation gain,
|
||||
0.50 - 0.55 is full cancellation. */
|
||||
|
||||
#define CFECHODELAY 360 /* Center front echo delay (samples) */
|
||||
#define CFECHOAMPL M17_0DB /* Center front echo amplitude */
|
||||
|
||||
#define STEXPAND2 0.07 /* Stereo expansion / 2 */
|
||||
|
||||
/* Head related impulse response (HRIR) derived from KEMAR measurement
|
||||
data by Bill Gardner <billg@media.mit.edu> and Keith Martin
|
||||
<kdm@media.mit.edu>
|
||||
|
||||
URL: http://sound.media.mit.edu/KEMAR.html
|
||||
|
||||
Distributed under GPL with authors' permission
|
||||
*/
|
||||
|
||||
/* EQUALIZED KEMAR HRIR
|
||||
|
||||
How to generate these data:
|
||||
|
||||
1. You need the MIT Media Lab's KEMAR data, read it into an software
|
||||
capable of signal/time series analysis (like Mathematica, Matlab,
|
||||
...)
|
||||
|
||||
2. Construct an equalizing FIR inverse filter by calculating the
|
||||
transfer function of the front, same side impulse response, then
|
||||
take 1 over the absolute magnitude.
|
||||
|
||||
3. Cut the poles in the inverse filter's transfer function
|
||||
specification by limiting the amplification to 2.5 (note, this
|
||||
number assumes that you have correct signal processing
|
||||
normalization of the Fourier transform).
|
||||
|
||||
4. Design the FIR inverse filter by calculating the inverse Fourier
|
||||
transform, then chopping the coefficients down to a reasonable
|
||||
number (N = 15 is used here), apply the Kaiser-Bessel window (alpha
|
||||
= 2 is used here). Note the objective is remove the color bias
|
||||
only (as if you are using an equalizer), _not_ to do a full inverse
|
||||
filtering. (Note: beta = pi*alpha in other notation.)
|
||||
|
||||
For N = 15, alpha = 2, you should get the following impulse
|
||||
response:
|
||||
|
||||
0.001001558668605168, 0.00698457265741865, 0.040453643039829436,
|
||||
0.012230541722147855, -0.11939760844854072, 0.16468099899755967,
|
||||
-0.30297563073747436, 1.3140211791355982, -0.30297563073747436,
|
||||
0.16468099899755967, -0.11939760844854072, 0.012230541722147855,
|
||||
0.040453643039829436, 0.00698457265741865, 0.001001558668605168
|
||||
|
||||
5. Linearly convolve all KEMAR HRIR with this inverse filter.
|
||||
|
||||
6. Resample from 44.1 kHz sampling frequency to 48 kHz.
|
||||
*/
|
||||
|
||||
/* Center front (-5 degree) - not 0 degree in order to create a clear
|
||||
front image from a finite distance */
|
||||
static const float cf_filt[128] = {
|
||||
-0.00008638082319075036, 0.0003198059946385229,
|
||||
-0.0005010631339162132, 0.0011424741331126876,
|
||||
-0.001584220794688753, 0.001742715363246275,
|
||||
-0.0011080796626780694, 0.0001651829990860167,
|
||||
0.005235028337314985, 0.0035223828473357776,
|
||||
0.010057681388431303, -0.033469432129545514,
|
||||
0.013391253316233523, 0.004858462839827063,
|
||||
0.08172161220103627, 0.26158596134500023,
|
||||
-0.12420314583323326, -0.14298458356097565,
|
||||
0.14421897280453896, -0.1635792507629016,
|
||||
-0.02187136722480014, 0.2426863044711817,
|
||||
0.07229814207917194, 0.0942742651913879,
|
||||
0.29856830878076834, 0.2944146162057754,
|
||||
-0.12122157003421209, -0.19640092165631157,
|
||||
0.11623836502034968, -0.05794027397995521,
|
||||
-0.34313138341973776, -0.19487516249168105,
|
||||
0.010118993953802401, -0.09460218797710966,
|
||||
-0.16761521117359582, 0.004154461610153861,
|
||||
0.052768641758969316, -0.00041823982226147407,
|
||||
0.021634960445143514, 0.07562793486871108,
|
||||
0.08895407129506479, 0.039857755093416214,
|
||||
0.044257936180422945, 0.061557584906101664,
|
||||
0.015547268541895703, -0.023908191934932484,
|
||||
0.009498030443468223, 0.03816269003221337,
|
||||
-0.009820500607303615, -0.042003975527908084,
|
||||
-0.03335447117311547, -0.029294510859746596,
|
||||
-0.05212623136198511, -0.073427547153205,
|
||||
-0.061190797824958836, -0.04620925059966413,
|
||||
-0.04204619420702159, -0.02331915902615157,
|
||||
0.00211481411477094, 0.00852563995740107,
|
||||
0.008766809731743399, 0.008666632180812078,
|
||||
0.018279202191625352, 0.02924332751289675,
|
||||
0.022293148257836494, 0.012362146008584188,
|
||||
0.008572582458807008, 0.006491370763597344,
|
||||
-0.0019366944997535774, -0.006318669309634434,
|
||||
-0.006457921690218669, -0.015050265524669974,
|
||||
-0.02110660282616213, -0.017027809096377904,
|
||||
-0.01651052305334348, -0.022770064150046673,
|
||||
-0.01999875754219472, -0.012294792027337775,
|
||||
-0.011506057031057188, -0.011448970577312903,
|
||||
-0.004823572302580925, 0.0022451134042777883,
|
||||
0.004145473526859826, 0.005629030064546135,
|
||||
0.008588029213398785, 0.010092048834844231,
|
||||
0.007182013245552008, 0.0014600979508720656,
|
||||
-0.0038314646272511756, -0.003443901997881347,
|
||||
-0.0029483418254804047, -0.007609357112679647,
|
||||
-0.006518368948030822, -0.004495803701497202,
|
||||
-0.007109113004849672, -0.008346237278084265,
|
||||
-0.005560847336252453, -0.002993453167553188,
|
||||
-0.005122897816824269, -0.004389782626604215,
|
||||
-0.0010912633695218108, -0.0019712029474458835,
|
||||
-0.005870162265802235, -0.005626159534954128,
|
||||
-0.00027254977910844407, 0.0013794425431633785,
|
||||
-0.0005919083190430062, -0.0007861203545416682,
|
||||
-0.0007049560240893946, -0.0032720188494468868,
|
||||
-0.004460645567968504, -0.0032018528193571696,
|
||||
-0.0030579229375062105, -0.003593998902656612,
|
||||
-0.0038032977997776445, -0.004613776010454773,
|
||||
-0.0059796549143736845, -0.00420126194319768,
|
||||
-0.0012374419948287222, 0.0008572699213050608,
|
||||
0.0021490971020081094, 0.00047295283198381995,
|
||||
-0.0009670277915884887, -0.001354440866080231,
|
||||
-0.002962902746547851, -0.00533935813338141,
|
||||
-0.005469203016468759, -0.004355784273189485
|
||||
};
|
||||
/* Front, same side (30 degree) */
|
||||
static const float af_filt[128] = {
|
||||
-0.004140580614755493, 0.005790934614385445,
|
||||
0.003318916682081112, 0.014257145544366063,
|
||||
0.007328442487127339, -0.06550381777876194,
|
||||
0.03502225818161845, -0.013090579770708259,
|
||||
0.2517776798694195, 0.420770489950659,
|
||||
-0.3418854608834852, -0.24371032493696737,
|
||||
0.04901356150030018, -0.1778083521632833,
|
||||
0.26448004245714163, 0.23245199964546834,
|
||||
0.033053145803936305, 0.46811222821062415,
|
||||
0.5359265986255611, -0.011912195468533787,
|
||||
-0.39763432601411647, -0.034482864386898314,
|
||||
0.029445398240649626, -0.3850940407863262,
|
||||
-0.3272328478175581, -0.14701421403616477,
|
||||
-0.08522137400169517, -0.14936851633336035,
|
||||
-0.09432605283433723, 0.0991200405937827,
|
||||
0.011075012089917331, -0.0051036489980274685,
|
||||
0.0933903289749412, 0.1344189369609565,
|
||||
0.10507466913017807, 0.04240159324684365,
|
||||
0.06559270110638656, 0.026984119875617524,
|
||||
-0.03359846103238096, -0.018000197099174275,
|
||||
0.042031818548178244, 0.03849039666888434,
|
||||
-0.02450829674011345, -0.03407882403088576,
|
||||
-0.029230189282961977, -0.046964865291761734,
|
||||
-0.09458258700116245, -0.1527349330901158,
|
||||
-0.15411577687826097, -0.08761679790956928,
|
||||
-0.033623549089171874, -0.007204768531481949,
|
||||
0.008273425020444852, 0.021368717994908505,
|
||||
0.04366608267875025, 0.05660907333076205,
|
||||
0.06775726495503939, 0.05621881735271431,
|
||||
0.03576231950669927, 0.02500825834889175,
|
||||
0.015423811076054601, 0.007903258334503761,
|
||||
-0.0053873014137761945, -0.006987955469434698,
|
||||
-0.012027972007598602, -0.025228281243816594,
|
||||
-0.026225091797257318, -0.023809293997344882,
|
||||
-0.03250172017712295, -0.03195696301067249,
|
||||
-0.01784813952189948, -0.01663267233760342,
|
||||
-0.016184530450468065, -0.011659883749357463,
|
||||
-0.0035378511240219163, -0.0005485800790443406,
|
||||
0.0018432660108168625, 0.011634844139907534,
|
||||
0.018333603402051105, 0.020447379185133056,
|
||||
0.00850783664147828, 0.0004694148911037838,
|
||||
-0.0017047130409786676, -0.0022409152834483997,
|
||||
-0.000860472174892845, -0.004111075059198666,
|
||||
-0.003527843382056666, -0.009640160874903018,
|
||||
-0.01750044574231376, -0.015613389403672443,
|
||||
-0.010790028120953001, -0.0095313499955768,
|
||||
-0.007469721416726809, -0.0019186578145117315,
|
||||
-0.00014977322572890802, -0.0029803838028179728,
|
||||
-0.006520567233727221, 0.000035015132033882596,
|
||||
0.009245098100543752, 0.009896930052308656,
|
||||
0.008316744929565786, 0.004575207140193997,
|
||||
-0.0000647420103997081, -0.004502916832871627,
|
||||
-0.004225962213251224, -0.002886014126381486,
|
||||
-0.006416834142585976, -0.007156609995423569,
|
||||
-0.008840274447579472, -0.01441763751386817,
|
||||
-0.015435817484659574, -0.00924487254924743,
|
||||
-0.0021571721940235205, 0.0028540722992305453,
|
||||
0.00273577475088536, -0.000788412365513805,
|
||||
-0.0032650029728365907, -0.003880217646231338,
|
||||
-0.0035302087299613778, -0.0038531436176586246,
|
||||
-0.0011921632190514074, -0.0020722967099011938,
|
||||
-0.004985351145629344, -0.0042375588844648735,
|
||||
-0.003030360463006021, -0.0014161075428041471,
|
||||
-0.0005083025643192044, 0.00035096963769606926
|
||||
};
|
||||
/* Front, opposite (-30 degree) */
|
||||
static const float of_filt[128] = {
|
||||
-0.000013472538374193126, -0.00008048061877079751,
|
||||
0.000043927265781258155, -0.000017931700794858892,
|
||||
-0.000034774602476112886, -0.00009576223008735474,
|
||||
0.0001557797638630691, -0.00018742885883751094,
|
||||
0.00026512448626705716, -0.0001451040203319678,
|
||||
-0.00008263233117758043, 0.0006486245853639179,
|
||||
-0.0010631408451846698, 0.0026571994100746143,
|
||||
0.0014179177997092787, 0.0062326502956616256,
|
||||
-0.008194149324545333, -0.006568029415878379,
|
||||
0.009538759710818582, 0.012309193558632693,
|
||||
0.12336638055838955, 0.046164307101829005,
|
||||
-0.10228706407884815, 0.04047687260345798,
|
||||
-0.00296595313977046, -0.07057949208414134,
|
||||
0.08172114840714612, 0.08736490764127891,
|
||||
0.05105250431333021, 0.11627179512747428,
|
||||
0.20717888490340705, 0.09375052213570291,
|
||||
-0.09784374168330194, -0.010493571845901443,
|
||||
0.053131894303891716, -0.10157443971694806,
|
||||
-0.16264032634244974, -0.05402369511361273,
|
||||
-0.0274403608654217, -0.09860277022495063,
|
||||
-0.06841875821090282, -0.004434574400066223,
|
||||
-0.0005222661652743502, -0.006231881259827263,
|
||||
0.014410397820340159, 0.04885649512730243,
|
||||
0.04361962569042684, 0.03399214029009391,
|
||||
0.04961073933475931, 0.04067325604132289,
|
||||
0.007850647519227257, 0.004564440466905299,
|
||||
0.02257107958021618, 0.008183791928884486,
|
||||
-0.014913479343180557, -0.018685938460856224,
|
||||
-0.01745737397226911, -0.02327177054233603,
|
||||
-0.03723048632685227, -0.044739390162299685,
|
||||
-0.042651220125613766, -0.03730017561004743,
|
||||
-0.029039465434276192, -0.01885087458914294,
|
||||
-0.01207127752277769, -0.006779800724164512,
|
||||
-0.001930416967444157, 0.000029454577995528385,
|
||||
0.0013822760965755472, 0.0014799128583230202,
|
||||
0.0002068200609199832, 0.0022254295286201083,
|
||||
0.005143858159434566, 0.0018580542060917013,
|
||||
-0.0019426046325146259, -0.0014464042108543495,
|
||||
-0.0034430083560735582, -0.009692758426099499,
|
||||
-0.011840035292593485, -0.010716508855893968,
|
||||
-0.012939889036853034, -0.0121846427935653,
|
||||
-0.006198503315630782, -0.0023186723099380305,
|
||||
-0.002679872498314837, -0.003086020446226295,
|
||||
-0.0015709623347698936, -0.0008147490468332398,
|
||||
-0.0012384575726770983, -0.0005212877089109362,
|
||||
0.0017707578744906142, 0.001324932723905786,
|
||||
-0.0017023653780617696, -0.0045108927752919425,
|
||||
-0.005422155613096912, -0.0039489323837623835,
|
||||
-0.005295995750506547, -0.00629706566356189,
|
||||
-0.004685732198036754, -0.0048076735568143,
|
||||
-0.005978864279217503, -0.005928999306332966,
|
||||
-0.004187703549017582, -0.003213999896976475,
|
||||
-0.0028068699816073414, -0.0010889703907593833,
|
||||
0.0003276714243495386, -0.0013015007040186994,
|
||||
-0.003208050402434782, -0.0025115319088208545,
|
||||
-0.0013787553006401076, -0.0018279087370218635,
|
||||
-0.0025904836507747754, -0.002071221947222004,
|
||||
-0.0026424212922485594, -0.0039837031817577,
|
||||
-0.0041635566057380965, -0.004355223489150822,
|
||||
-0.004350395332709937, -0.0036693292471930263,
|
||||
-0.003386384394185026, -0.003972568655001128,
|
||||
-0.004332336840023821, -0.002648767912111827,
|
||||
-0.001384410080218114, -0.0011353792711849466,
|
||||
-0.0013726264946164232, -0.0020075119315034313
|
||||
};
|
||||
/* Rear, same side (135 degree) */
|
||||
static const float ar_filt[128] = {
|
||||
0.004573315040810066, 0.0013592578059426913,
|
||||
0.01553271930902704, -0.0002356117224941317,
|
||||
-0.05746098219774702, 0.03430688963370445,
|
||||
0.00808371687447385, 0.21893535841158596,
|
||||
0.2984357591724814, -0.3302799746504719,
|
||||
-0.3194029149806245, 0.21633225051331056,
|
||||
0.24371260938097083, -0.08843705549751085,
|
||||
0.03939684701343366, 0.45386926431114494,
|
||||
0.07599118140753386, -0.18114706160474578,
|
||||
0.285640624686038, 0.4049515236666218,
|
||||
-0.05347890222071792, -0.31464359045319074,
|
||||
-0.1033502246468194, -0.04553593949283157,
|
||||
-0.1880747731157464, -0.13629090230626037,
|
||||
-0.10435789106123239, -0.19818232801888755,
|
||||
-0.16701805476330397, -0.022793111199284,
|
||||
0.058049696762683685, 0.007048321372693906,
|
||||
-0.002966419183225961, 0.10140569697797763,
|
||||
0.11648999956673124, 0.05218347182779882,
|
||||
0.028427001212735392, 0.04151900310166159,
|
||||
-0.0006960604221423734, -0.05898623212226975,
|
||||
-0.03801934531173312, -0.029306970535287986,
|
||||
-0.04549125782835908, -0.0599222718506552,
|
||||
-0.058299618975430116, -0.03765579129720727,
|
||||
-0.03559302657499581, -0.020647901025903054,
|
||||
-0.005720957338744348, -0.0041915732688915545,
|
||||
-0.0011470880098346143, 0.008737404798553,
|
||||
0.023444168098121512, 0.024204226042172663,
|
||||
0.01894897166475026, 0.020807655257479588,
|
||||
0.021570431128040954, 0.006800556178576289,
|
||||
-0.009000089216921362, -0.010969824547067934,
|
||||
-0.0033653428332822374, -0.012676936164668659,
|
||||
-0.026739938673413587, -0.023427869194287573,
|
||||
-0.023302007105117244, -0.023647155590533712,
|
||||
-0.021289317515613106, -0.009120487305069884,
|
||||
0.0009251551667728967, 0.00004285344125653763,
|
||||
-0.00009042365479456271, 0.00022573242339446494,
|
||||
0.00720168491586098, 0.007111875505402431,
|
||||
0.003186514817683482, 0.00810087718334745,
|
||||
0.012619557025922575, 0.007854726400013397,
|
||||
-0.0024013592881066267, -0.001452457473161119,
|
||||
-0.0025535188366093945, -0.012428911627809337,
|
||||
-0.013729251536694145, -0.0070099675146427344,
|
||||
-0.007165284278706593, -0.01639289295622301,
|
||||
-0.015831795079778305, -0.007305768485523729,
|
||||
-0.003608863157004021, -0.0032640528878698084,
|
||||
0.0030901263998481944, 0.00749497566124848,
|
||||
0.002515185532327241, 0.00004840875738621367,
|
||||
0.0017596043486043966, 0.0046229941553338144,
|
||||
0.0034259167322926096, 0.003707347634186093,
|
||||
0.0035584806528586328, -0.0019078936035275198,
|
||||
-0.006057343815214898, -0.0069262470468817,
|
||||
-0.004345020728618624, -0.004177623355574794,
|
||||
-0.005373506556122508, -0.006624933928893836,
|
||||
-0.008679541408588839, -0.010718719681595322,
|
||||
-0.011392246979594496, -0.007893917064389902,
|
||||
-0.0027572935365832536, -0.00006064707149834412,
|
||||
-0.0012512537319656323, -0.0024501501002409786,
|
||||
-0.0022106788572895998, -0.00234124933370301,
|
||||
-0.0008953445167066823, 0.0005393670625637734,
|
||||
-0.00033175600142209297, -0.004023994309351289,
|
||||
-0.008655472335784443, -0.009899957354849682,
|
||||
-0.008664952919996412, -0.00553483124503576,
|
||||
-0.003735336089277662, -0.002754824348643885,
|
||||
-0.0026884314856593368, -0.004084181815125924
|
||||
};
|
||||
/* Rear, opposite (-135 degree) */
|
||||
static const float or_filt[128] = {
|
||||
0.0001220944028243897, -0.000021785381808441314,
|
||||
5.823057988603169e-6, -0.00001217768176447613,
|
||||
-0.00006123604397345513, 5.574117262531134e-6,
|
||||
-0.00004935331914366778, 1.1771577934768211e-6,
|
||||
-0.000059236211621095756, 9.503536190497286e-6,
|
||||
-0.0001494445696103564, 0.00012248858284145305,
|
||||
-0.0000963975321456313, 6.017905197665205e-6,
|
||||
0.00003353395360402643, -0.0001931511015359506,
|
||||
0.0005113536523931485, -0.0005676652619386114,
|
||||
0.0012057159755477467, 0.0009370492250339692,
|
||||
0.004596472288877981, -0.0018831773384237068,
|
||||
-0.008208535225621212, 0.0038178646400751056,
|
||||
0.008726517739105965, 0.06664363898418262,
|
||||
0.06788684221502142, -0.04492315162807267,
|
||||
-0.04019906311255255, 0.026203059677375153,
|
||||
0.013678129114847544, -0.014334962223993527,
|
||||
0.010141709596167392, 0.11559131576945537,
|
||||
0.1586402064538425, 0.059975334707967023,
|
||||
0.004671725963777715, 0.031498678282775874,
|
||||
0.014338626006524587, -0.014749719448472231,
|
||||
-0.02055508237941379, -0.05407690143992048,
|
||||
-0.07767559836886143, -0.05029091786216801,
|
||||
-0.030808335706574427, -0.03401958135442541,
|
||||
-0.030520368430288967, -0.014168302104259355,
|
||||
0.011907621259989802, 0.014286081013069,
|
||||
0.006377467879613449, 0.018546823568277478,
|
||||
0.028297012771618273, 0.025222339408338186,
|
||||
0.021931611353415138, 0.019708894333646355,
|
||||
0.01729258494072014, 0.017468204169564034,
|
||||
0.009729094845051928, -0.002976992018531901,
|
||||
-0.00956986166277019, -0.016125733548332074,
|
||||
-0.02934094241442545, -0.04133767871051455,
|
||||
-0.043536981145416466, -0.0385966307108608,
|
||||
-0.02784453599342459, -0.018995135307247116,
|
||||
-0.012849534096536747, -0.004437491064613308,
|
||||
0.00028385411598204655, 0.003776874988516643,
|
||||
0.008069432041547833, 0.008764754183751848,
|
||||
0.008020908861878062, 0.006830351461360802,
|
||||
0.002218330884267235, -0.0020478725582339444,
|
||||
-0.003997428121462543, -0.007066287373515421,
|
||||
-0.00940847412544698, -0.010938998446237963,
|
||||
-0.011775483016151306, -0.011391103919484287,
|
||||
-0.010586061195163017, -0.009842793078929053,
|
||||
-0.007753202010139829, -0.00569213732353025,
|
||||
-0.006506783349722073, -0.005346134281903736,
|
||||
-0.003913089814898934, -0.0036091443854759727,
|
||||
-0.0020328564301266287, 0.00017932870773467683,
|
||||
0.0032779786679056357, 0.003969695813293966,
|
||||
0.0020339334412434987, -0.00011345940675415259,
|
||||
-0.0018344103399567666, -0.003556764701666365,
|
||||
-0.004263523639408391, -0.002940568582022133,
|
||||
-0.0034341188272627556, -0.006023399920020824,
|
||||
-0.0077456903203677865, -0.007912219312377842,
|
||||
-0.00625202770436523, -0.00530785086116117,
|
||||
-0.005569722659634311, -0.004664448462594344,
|
||||
-0.0037747773914077747, -0.004175649656985592,
|
||||
-0.004659601521384289, -0.005008602967819641,
|
||||
-0.004730625079902729, -0.0034039554356604146,
|
||||
-0.0017110333873406587, -0.0006091938771510242,
|
||||
-0.0016051679050678297, -0.003312864664007262,
|
||||
-0.004505512715977288, -0.004152222189861692,
|
||||
-0.003218596419678823, -0.0027277806209877343,
|
||||
-0.001715005444317267, -0.0012589960071233749,
|
||||
-0.001852908777923165, -0.002540339553144362
|
||||
};
|
||||
/* Center rear (180 degree) */
|
||||
static const float cr_filt[128] = {
|
||||
-0.00005989110716536726, -0.00022790291829128702,
|
||||
0.0002659166098971966, -0.0003774772716776257,
|
||||
0.0004540309551867803, -0.000420238187386368,
|
||||
0.00025518536450885686, 0.00028285526288953955,
|
||||
-0.001016391007574093, 0.0028634984299063795,
|
||||
0.0021574799687976045, 0.01035121276682072,
|
||||
-0.010481720917298163, -0.013197198495899292,
|
||||
0.0031928225328717195, 0.02903137843618603,
|
||||
0.1632429772511569, 0.1047487989875262,
|
||||
-0.10464685060623742, -0.09260196288035998,
|
||||
-0.007514241993554443, 0.013596249226741712,
|
||||
-0.019876166508450258, 0.1255626123599804,
|
||||
0.3648170359521724, 0.19458249753223478,
|
||||
-0.04434070930031298, 0.046582528121935265,
|
||||
0.09484427347230277, -0.03137795311969644,
|
||||
-0.10297437925363695, -0.09351091015917065,
|
||||
-0.1129521092162064, -0.14925322995658827,
|
||||
-0.1231466295584665, -0.06356719756705227,
|
||||
-0.05442277895126282, -0.07568433015661316,
|
||||
-0.023314932828602003, 0.04260950721318558,
|
||||
0.02249026315598923, 0.02048195669571197,
|
||||
0.05651342117268278, 0.05885038917623213,
|
||||
0.03797102097397795, 0.011767394419953451,
|
||||
0.00560502503429481, 0.005051125343961189,
|
||||
-0.012925933188033823, -0.023918884651306566,
|
||||
-0.013251659441678816, -0.010694772488866284,
|
||||
-0.03080486448617846, -0.03661278237783158,
|
||||
-0.0379227303416262, -0.042189005718490775,
|
||||
-0.026595666344596286, -0.009759025956801257,
|
||||
-0.002064986663513004, -0.002420117028098389,
|
||||
-0.006629991977552491, 0.004619970897631026,
|
||||
0.019450642967537877, 0.0173521119057514,
|
||||
0.017641425439988062, 0.02270029598048491,
|
||||
0.018976431925275348, 0.009299852902290885,
|
||||
-0.001695039371619912, -0.00675162574265618,
|
||||
-0.009380968871003034, -0.011208396125485165,
|
||||
-0.01308640049201482, -0.0165636375633249,
|
||||
-0.022004099870933345, -0.025173458684139286,
|
||||
-0.016918759559175375, -0.00865150653575917,
|
||||
-0.006999929082792643, -0.005454830010518988,
|
||||
-0.0021129521131095317, 0.00018717090054046307,
|
||||
-0.0002864344788569993, 0.0017615225381095569,
|
||||
0.006985907557802283, 0.010577308310476465,
|
||||
0.006466104789306027, -0.0014988738575948326,
|
||||
-0.0039669755229277195, -0.0065156971200080235,
|
||||
-0.009343206924192169, -0.0076430644693577495,
|
||||
-0.004395214976600924, -0.003052735340422483,
|
||||
-0.007019103043066595, -0.00974109267696527,
|
||||
-0.007968015032797376, -0.007801513845528344,
|
||||
-0.007535748903681969, -0.003543341967287925,
|
||||
0.0015083125553729722, 0.0023345972556147025,
|
||||
-0.0010043623069557037, -0.0025295765105203746,
|
||||
-0.0023701840891643634, -0.0005908186035024362,
|
||||
0.0029826252289082847, 0.004829048542117764,
|
||||
0.004488360022902081, 0.00002643748103005408,
|
||||
-0.0042100779212597295, -0.006170600558114495,
|
||||
-0.007267149164680168, -0.006825522903494639,
|
||||
-0.006899834372739123, -0.0073493916110062675,
|
||||
-0.009554351265163382, -0.011790297433830197,
|
||||
-0.010645796603734424, -0.0064661575394022106,
|
||||
-0.002026743466524137, -0.0004337034584909932,
|
||||
-0.0011172647031654614, -0.0017947816283674731,
|
||||
-0.00255615052036616, -0.0017721562881944813,
|
||||
-0.0002379619297227554, 0.0007130120121089036
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_AF_HRTF_H */
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* simple voice removal filter
|
||||
*
|
||||
* copyright (c) 2006 Reynaldo H. Verdejo Pinochet
|
||||
* Based on code by Alex Beregszaszi for his 'center' filter.
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
if (!c)
|
||||
return 0;
|
||||
if (af_make_writeable(af, c) < 0) {
|
||||
talloc_free(c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
float* a = c->planes[0]; // Audio data
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = c->samples*nch; // Number of samples in current audio block
|
||||
register int i;
|
||||
|
||||
/*
|
||||
FIXME1 add a low band pass filter to avoid suppressing
|
||||
centered bass/drums
|
||||
FIXME2 better calculated* attenuation factor
|
||||
*/
|
||||
|
||||
for(i=0;i<len;i+=nch)
|
||||
{
|
||||
a[i] = (a[i] - a[i+1]) * 0.7;
|
||||
a[i+1]=a[i];
|
||||
}
|
||||
|
||||
af_add_output_frame(af, c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
const struct af_info af_info_karaoke = {
|
||||
.info = "Simple karaoke/voice-removal audio filter",
|
||||
.name = "karaoke",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
};
|
||||
@@ -1,851 +0,0 @@
|
||||
/*
|
||||
* LADSPA plugin loader
|
||||
*
|
||||
* Written by Ivo van Poorten <ivop@euronet.nl>
|
||||
* Copyright (C) 2004, 2005
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Global Includes */
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <ladspa.h>
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Local Includes */
|
||||
|
||||
#include "af.h"
|
||||
|
||||
#define _(x) (x)
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Filter specific data */
|
||||
|
||||
typedef struct af_ladspa_s
|
||||
{
|
||||
int status; /**< Status of the filter.
|
||||
* Either AF_OK or AF_ERROR
|
||||
* Because MPlayer re-inits audio filters that
|
||||
* _clearly_ returned AF_ERROR anyway, I use this
|
||||
* in play() to skip the processing and return
|
||||
* the data unchanged.
|
||||
*/
|
||||
|
||||
int activated; /**< 0 or 1. Activate LADSPA filters only once, even
|
||||
* if the buffers get resized, to avoid a stuttering
|
||||
* filter.
|
||||
*/
|
||||
|
||||
char *file;
|
||||
char *label;
|
||||
char *controls;
|
||||
|
||||
char *myname; /**< It's easy to have a concatenation of file and label */
|
||||
|
||||
void *libhandle;
|
||||
const LADSPA_Descriptor *plugin_descriptor;
|
||||
|
||||
int nports;
|
||||
|
||||
int ninputs;
|
||||
int *inputs;
|
||||
|
||||
int noutputs;
|
||||
int *outputs;
|
||||
|
||||
int ninputcontrols;
|
||||
int *inputcontrolsmap; /**< Map input port number [0-] to actual port */
|
||||
float *inputcontrols;
|
||||
|
||||
int noutputcontrols;
|
||||
int *outputcontrolsmap;
|
||||
float *outputcontrols;
|
||||
|
||||
int nch; /**< number of channels */
|
||||
int bufsize;
|
||||
float **inbufs;
|
||||
float **outbufs;
|
||||
LADSPA_Handle *chhandles;
|
||||
|
||||
} af_ladspa_t;
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static int af_open(struct af_instance *af);
|
||||
static int af_ladspa_malloc_failed(char*);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
#define OPT_BASE_STRUCT af_ladspa_t
|
||||
const struct af_info af_info_ladspa = {
|
||||
.info = "LADSPA plugin loader",
|
||||
.name = "ladspa",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_ladspa_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("file", file, 0),
|
||||
OPT_STRING("label", label, 0),
|
||||
OPT_STRING("controls", controls, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* By lack of a better word (in my vocabulary) this is called 'parse'.
|
||||
* Feel free to suggest an alternative.
|
||||
*/
|
||||
|
||||
/** \brief Check for inputs, outputs and controls of a given filter.
|
||||
*
|
||||
* This function counts and checks all input, output and control ports
|
||||
* of the filter that was loaded. If it turns out to be a valid
|
||||
* filter for MPlayer use, it prints out a list of all controls and
|
||||
* the corresponding range of its value at message level MSGL_V.
|
||||
*
|
||||
* \param setup Current setup of the filter. Must have its
|
||||
* plugin_descriptor set!
|
||||
*
|
||||
* \return Returns AF_OK if it has a valid input/output/controls
|
||||
* configuration. Else, it returns AF_ERROR.
|
||||
*/
|
||||
|
||||
static int af_ladspa_parse_plugin(struct af_instance *af) {
|
||||
af_ladspa_t *setup = af->priv;
|
||||
int p, i;
|
||||
const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
|
||||
LADSPA_PortDescriptor d;
|
||||
LADSPA_PortRangeHint hint;
|
||||
|
||||
if (!setup->libhandle)
|
||||
return AF_ERROR; /* only call parse after a succesful load */
|
||||
if (!setup->plugin_descriptor)
|
||||
return AF_ERROR; /* same as above */
|
||||
|
||||
/* let's do it */
|
||||
|
||||
setup->nports = pdes->PortCount;
|
||||
|
||||
/* allocate memory for all inputs/outputs/controls */
|
||||
|
||||
setup->inputs = calloc(setup->nports, sizeof(int));
|
||||
if (!setup->inputs) return af_ladspa_malloc_failed(setup->myname);
|
||||
|
||||
setup->outputs = calloc(setup->nports, sizeof(int));
|
||||
if (!setup->outputs) return af_ladspa_malloc_failed(setup->myname);
|
||||
|
||||
setup->inputcontrolsmap = calloc(setup->nports, sizeof(int));
|
||||
if (!setup->inputcontrolsmap) return af_ladspa_malloc_failed(setup->myname);
|
||||
|
||||
setup->inputcontrols = calloc(setup->nports, sizeof(float));
|
||||
if (!setup->inputcontrols) return af_ladspa_malloc_failed(setup->myname);
|
||||
|
||||
setup->outputcontrolsmap = calloc(setup->nports, sizeof(int));
|
||||
if (!setup->outputcontrolsmap) return af_ladspa_malloc_failed(setup->myname);
|
||||
|
||||
setup->outputcontrols = calloc(setup->nports, sizeof(float));
|
||||
if (!setup->outputcontrols) return af_ladspa_malloc_failed(setup->myname);
|
||||
|
||||
/* set counts to zero */
|
||||
|
||||
setup->ninputs = 0;
|
||||
setup->noutputs = 0;
|
||||
setup->ninputcontrols = 0;
|
||||
setup->noutputcontrols = 0;
|
||||
|
||||
/* check all ports, see what type it is and set variables according to
|
||||
* what we have found
|
||||
*/
|
||||
|
||||
for (p=0; p<setup->nports; p++) {
|
||||
d = pdes->PortDescriptors[p];
|
||||
|
||||
if (LADSPA_IS_PORT_AUDIO(d)) {
|
||||
if (LADSPA_IS_PORT_INPUT(d)) {
|
||||
setup->inputs[setup->ninputs] = p;
|
||||
setup->ninputs++;
|
||||
} else if (LADSPA_IS_PORT_OUTPUT(d)) {
|
||||
setup->outputs[setup->noutputs] = p;
|
||||
setup->noutputs++;
|
||||
}
|
||||
}
|
||||
|
||||
if (LADSPA_IS_PORT_CONTROL(d)) {
|
||||
if (LADSPA_IS_PORT_INPUT(d)) {
|
||||
setup->inputcontrolsmap[setup->ninputcontrols] = p;
|
||||
setup->ninputcontrols++;
|
||||
/* set control to zero. set values after reading the rest
|
||||
* of the suboptions and check LADSPA_?_HINT's later.
|
||||
*/
|
||||
setup->inputcontrols[p] = 0.0f;
|
||||
} else if (LADSPA_IS_PORT_OUTPUT(d)) {
|
||||
/* read and handle these too, otherwise filters that have them
|
||||
* will sig11
|
||||
*/
|
||||
setup->outputcontrolsmap[setup->noutputcontrols]=p;
|
||||
setup->noutputcontrols++;
|
||||
setup->outputcontrols[p] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (setup->ninputs == 0) {
|
||||
MP_WARN(af, "%s: %s\n", setup->myname,
|
||||
_("WARNING! This LADSPA plugin has no audio inputs.\n The incoming audio signal will be lost."));
|
||||
} else if (setup->ninputs == 1) {
|
||||
MP_VERBOSE(af, "%s: this is a mono effect\n", setup->myname);
|
||||
} else if (setup->ninputs == 2) {
|
||||
MP_VERBOSE(af, "%s: this is a stereo effect\n", setup->myname);
|
||||
} else {
|
||||
MP_VERBOSE(af, "%s: this is a %i-channel effect, "
|
||||
"support is experimental\n", setup->myname, setup->ninputs);
|
||||
}
|
||||
|
||||
if (setup->noutputs == 0) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("This LADSPA plugin has no audio outputs."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (setup->noutputs != setup->ninputs ) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("The number of audio inputs and audio outputs of the LADSPA plugin differ."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
MP_VERBOSE(af, "%s: this plugin has %d input control(s)\n",
|
||||
setup->myname, setup->ninputcontrols);
|
||||
|
||||
/* Print list of controls and its range of values it accepts */
|
||||
|
||||
for (i=0; i<setup->ninputcontrols; i++) {
|
||||
p = setup->inputcontrolsmap[i];
|
||||
hint = pdes->PortRangeHints[p];
|
||||
MP_VERBOSE(af, " --- %d %s [", i, pdes->PortNames[p]);
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor)) {
|
||||
MP_VERBOSE(af, "%0.2f , ", hint.LowerBound);
|
||||
} else {
|
||||
MP_VERBOSE(af, "... , ");
|
||||
}
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor)) {
|
||||
MP_VERBOSE(af, "%0.2f]\n", hint.UpperBound);
|
||||
} else {
|
||||
MP_VERBOSE(af, "...]\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* This function might "slightly" look like dlopenLADSPA in the LADSPA SDK :-)
|
||||
* But, I changed a few things, because imho it was broken. It did not support
|
||||
* relative paths, only absolute paths that start with a /
|
||||
* I think ../../some/dir/foobar.so is just as valid. And if one wants to call
|
||||
* his library '...somename...so' he's crazy, but it should be allowed.
|
||||
* So, search the path first, try plain *filename later.
|
||||
* Also, try adding .so first! I like the recursion the SDK did, but it's
|
||||
* better the other way around. -af ladspa=cmt:amp_stereo:0.5 is easier to type
|
||||
* than -af ladspa=cmt.so:amp_stereo:0.5 :-))
|
||||
*/
|
||||
|
||||
/** \brief dlopen() wrapper
|
||||
*
|
||||
* This is a wrapper around dlopen(). It tries various variations of the
|
||||
* filename (with or without the addition of the .so extension) in various
|
||||
* directories specified by the LADSPA_PATH environment variable. If all fails
|
||||
* it tries the filename directly as an absolute path to the library.
|
||||
*
|
||||
* \param filename filename of the library to load.
|
||||
* \param flag see dlopen(3) for a description of the flags.
|
||||
*
|
||||
* \return returns a pointer to the loaded library on success, or
|
||||
* NULL if it fails to load.
|
||||
*/
|
||||
|
||||
static void* mydlopen(const char *filename, int flag) {
|
||||
char *buf;
|
||||
const char *end, *start, *ladspapath;
|
||||
int endsinso;
|
||||
size_t filenamelen;
|
||||
void *result = NULL;
|
||||
|
||||
#if defined(__MINGW32__) || defined(__CYGWIN__)
|
||||
/* For Windows there's only absolute path support.
|
||||
* If you have a Windows machine, feel free to fix this.
|
||||
* (path separator, shared objects extension, et cetera). */
|
||||
MP_VERBOSE(af, "\ton windows, only absolute pathnames "
|
||||
"are supported\n");
|
||||
MP_VERBOSE(af, "\ttrying %s\n", filename);
|
||||
return dlopen(filename, flag);
|
||||
#endif
|
||||
|
||||
filenamelen = strlen(filename);
|
||||
|
||||
endsinso = 0;
|
||||
if (filenamelen > 3)
|
||||
endsinso = (strcmp(filename+filenamelen-3, ".so") == 0);
|
||||
if (!endsinso) {
|
||||
buf=malloc(filenamelen+4);
|
||||
strcpy(buf, filename);
|
||||
strcat(buf, ".so");
|
||||
result=mydlopen(buf, flag);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
ladspapath=getenv("LADSPA_PATH");
|
||||
|
||||
if (ladspapath) {
|
||||
start=ladspapath;
|
||||
while (*start != '\0') {
|
||||
int needslash;
|
||||
end=start;
|
||||
while ( (*end != ':') && (*end != '\0') )
|
||||
end++;
|
||||
|
||||
buf=malloc(filenamelen + 2 + (end-start) );
|
||||
if (end > start)
|
||||
strncpy(buf, start, end-start);
|
||||
needslash=0;
|
||||
if (end > start)
|
||||
if (*(end-1) != '/') {
|
||||
needslash = 1;
|
||||
buf[end-start] = '/';
|
||||
}
|
||||
strcpy(buf+needslash+(end-start), filename);
|
||||
|
||||
result=dlopen(buf, flag);
|
||||
|
||||
free(buf);
|
||||
if (result)
|
||||
return result;
|
||||
|
||||
start = end;
|
||||
if (*start == ':')
|
||||
start++;
|
||||
} /* end while there's still more in the path */
|
||||
} /* end if there's a ladspapath */
|
||||
|
||||
/* last resort, just open it again, so the dlerror() message is correct */
|
||||
return dlopen(filename,flag);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/** \brief Load a LADSPA Plugin
|
||||
*
|
||||
* This function loads the LADSPA plugin specified by the file and label
|
||||
* that are present in the setup variable. First, it loads the library.
|
||||
* If it fails, it returns AF_ERROR. If not, it continues to look for the
|
||||
* specified label. If it finds it, it sets the plugin_descriptor inside
|
||||
* setup and returns AF_OK. If it doesn't, it returns AF_ERROR. Special case
|
||||
* is a label called 'help'. In that case, it prints a list of all available
|
||||
* labels (filters) in the library specified by file.
|
||||
*
|
||||
* \param setup Current setup of the filter. Contains filename and label.
|
||||
*
|
||||
* \return Either AF_ERROR or AF_OK, depending on the success of the operation.
|
||||
*/
|
||||
|
||||
static int af_ladspa_load_plugin(struct af_instance *af) {
|
||||
af_ladspa_t *setup = af->priv;
|
||||
const LADSPA_Descriptor *ladspa_descriptor;
|
||||
LADSPA_Descriptor_Function descriptor_function;
|
||||
int i;
|
||||
|
||||
/* load library */
|
||||
MP_VERBOSE(af, "%s: loading ladspa plugin library %s\n",
|
||||
setup->myname, setup->file);
|
||||
|
||||
setup->libhandle = mydlopen(setup->file, RTLD_NOW);
|
||||
|
||||
if (!setup->libhandle) {
|
||||
MP_ERR(af, "%s: %s %s\n\t%s\n", setup->myname,
|
||||
_("failed to load"), setup->file, dlerror() );
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
MP_VERBOSE(af, "%s: library found.\n", setup->myname);
|
||||
|
||||
/* find descriptor function */
|
||||
dlerror();
|
||||
descriptor_function = (LADSPA_Descriptor_Function) dlsym (setup->libhandle,
|
||||
"ladspa_descriptor");
|
||||
|
||||
if (!descriptor_function) {
|
||||
MP_ERR(af, "%s: %s\n\t%s\n", setup->myname,
|
||||
_("Couldn't find ladspa_descriptor() function in the specified library file."), dlerror());
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
/* if label == help, list all labels in library and exit */
|
||||
|
||||
if (strcmp(setup->label, "help") == 0) {
|
||||
MP_INFO(af, "%s: %s %s:\n", setup->myname,
|
||||
_("available labels in"), setup->file);
|
||||
for (i=0; ; i++) {
|
||||
ladspa_descriptor = descriptor_function(i);
|
||||
if (ladspa_descriptor == NULL) {
|
||||
return AF_ERROR;
|
||||
}
|
||||
MP_INFO(af, " %-16s - %s (%lu)\n",
|
||||
ladspa_descriptor->Label,
|
||||
ladspa_descriptor->Name,
|
||||
ladspa_descriptor->UniqueID);
|
||||
}
|
||||
}
|
||||
|
||||
MP_VERBOSE(af, "%s: looking for label\n", setup->myname);
|
||||
|
||||
/* find label in library */
|
||||
for (i=0; ; i++) {
|
||||
ladspa_descriptor = descriptor_function(i);
|
||||
if (ladspa_descriptor == NULL) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("Couldn't find label in plugin library."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (strcmp(ladspa_descriptor->Label, setup->label) == 0) {
|
||||
setup->plugin_descriptor = ladspa_descriptor;
|
||||
MP_VERBOSE(af, "%s: %s found\n", setup->myname,
|
||||
setup->label);
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/** \brief Print a malloc() failed error message.
|
||||
*
|
||||
* Generic function which can be called if a call to malloc(), calloc(),
|
||||
* strdup(), et cetera, failed. It prints a message to the console and
|
||||
* returns AF_ERROR.
|
||||
*
|
||||
* \return AF_ERROR
|
||||
*/
|
||||
|
||||
static int af_ladspa_malloc_failed(char *myname) {
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/** \brief Controls the filter.
|
||||
*
|
||||
* Control the behaviour of the filter.
|
||||
*
|
||||
* Commands:
|
||||
* CONTROL_REINIT Sets the af structure with proper values for number
|
||||
* of channels, rate, format, et cetera.
|
||||
* \param af Audio filter instance
|
||||
* \param cmd The command to execute
|
||||
* \param arg Arguments to the command
|
||||
*
|
||||
* \return Either AF_ERROR or AF_OK, depending on the succes of the
|
||||
* operation.
|
||||
*/
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg) {
|
||||
af_ladspa_t *setup = (af_ladspa_t*) af->priv;
|
||||
|
||||
switch(cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
MP_VERBOSE(af, "%s: (re)init\n", setup->myname);
|
||||
|
||||
if (!arg) return AF_ERROR;
|
||||
|
||||
/* accept FLOAT, let af_format do conversion */
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
return af_test_output(af, (struct mp_audio*)arg);
|
||||
}
|
||||
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/** \brief Uninitialise the LADSPA Plugin Loader filter.
|
||||
*
|
||||
* This function deactivates the plugin(s), cleans up, frees all allocated
|
||||
* memory and exits.
|
||||
*
|
||||
* \return No return value.
|
||||
*/
|
||||
|
||||
static void uninit(struct af_instance *af) {
|
||||
if (af->priv) {
|
||||
af_ladspa_t *setup = (af_ladspa_t*) af->priv;
|
||||
const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
|
||||
|
||||
if (setup->myname) {
|
||||
MP_VERBOSE(af, "%s: cleaning up\n", setup->myname);
|
||||
free(setup->myname);
|
||||
}
|
||||
|
||||
if (setup->chhandles) {
|
||||
for (int i = 0; i < setup->nch; i+=setup->ninputs) {
|
||||
if (pdes->deactivate) pdes->deactivate(setup->chhandles[i]);
|
||||
if (pdes->cleanup) pdes->cleanup(setup->chhandles[i]);
|
||||
}
|
||||
free(setup->chhandles);
|
||||
}
|
||||
|
||||
free(setup->inputcontrolsmap);
|
||||
free(setup->inputcontrols);
|
||||
free(setup->outputcontrolsmap);
|
||||
free(setup->outputcontrols);
|
||||
free(setup->inputs);
|
||||
free(setup->outputs);
|
||||
|
||||
if (setup->inbufs) {
|
||||
for(int i = 0; i < setup->nch; i++)
|
||||
free(setup->inbufs[i]);
|
||||
free(setup->inbufs);
|
||||
}
|
||||
|
||||
if (setup->outbufs) {
|
||||
for (int i = 0; i < setup->nch; i++)
|
||||
free(setup->outbufs[i]);
|
||||
free(setup->outbufs);
|
||||
}
|
||||
|
||||
if (setup->libhandle)
|
||||
dlclose(setup->libhandle);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/** \brief Process chunk of audio data through the selected LADSPA Plugin.
|
||||
*
|
||||
* \param af Pointer to audio filter instance
|
||||
* \param data Pointer to chunk of audio data
|
||||
*
|
||||
* \return Either AF_ERROR or AF_OK
|
||||
*/
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
af_ladspa_t *setup = af->priv;
|
||||
const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
|
||||
float *audio = (float*)data->planes[0];
|
||||
int nsamples = data->samples*data->nch;
|
||||
int nch = data->nch;
|
||||
int rate = data->rate;
|
||||
int i, p;
|
||||
|
||||
if (setup->status !=AF_OK) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* See if it's the first call. If so, setup inbufs/outbufs, instantiate
|
||||
* plugin, connect ports and activate plugin
|
||||
*/
|
||||
|
||||
if ( (setup->bufsize != nsamples/nch) || (setup->nch != nch) ) {
|
||||
|
||||
/* if setup->nch==0, it's the first call, if not, something has
|
||||
* changed and all previous mallocs have to be freed
|
||||
*/
|
||||
|
||||
if (setup->nch != 0) {
|
||||
MP_TRACE(af, "%s: bufsize change; free old buffer\n",
|
||||
setup->myname);
|
||||
|
||||
if(setup->inbufs) {
|
||||
for(i=0; i<setup->nch; i++)
|
||||
free(setup->inbufs[i]);
|
||||
free(setup->inbufs);
|
||||
}
|
||||
if(setup->outbufs) {
|
||||
for(i=0; i<setup->nch; i++)
|
||||
free(setup->outbufs[i]);
|
||||
free(setup->outbufs);
|
||||
}
|
||||
} /* everything is freed */
|
||||
|
||||
setup->bufsize = nsamples/nch;
|
||||
setup->nch = nch;
|
||||
|
||||
setup->inbufs = calloc(nch, sizeof(float*));
|
||||
setup->outbufs = calloc(nch, sizeof(float*));
|
||||
|
||||
MP_TRACE(af, "%s: bufsize = %d\n",
|
||||
setup->myname, setup->bufsize);
|
||||
|
||||
for(i=0; i<nch; i++) {
|
||||
setup->inbufs[i] = calloc(setup->bufsize, sizeof(float));
|
||||
setup->outbufs[i] = calloc(setup->bufsize, sizeof(float));
|
||||
}
|
||||
|
||||
/* only on the first call, there are no handles. */
|
||||
|
||||
if (!setup->chhandles) {
|
||||
setup->chhandles = calloc(nch, sizeof(LADSPA_Handle));
|
||||
|
||||
/* create handles
|
||||
* for stereo effects, create one handle for two channels
|
||||
*/
|
||||
|
||||
for(i=0; i<nch; i++) {
|
||||
|
||||
if (i % setup->ninputs) { /* stereo effect */
|
||||
/* copy the handle from previous channel */
|
||||
setup->chhandles[i] = setup->chhandles[i-1];
|
||||
continue;
|
||||
}
|
||||
|
||||
setup->chhandles[i] = pdes->instantiate(pdes, rate);
|
||||
}
|
||||
}
|
||||
|
||||
/* connect input/output ports for each channel/filter instance
|
||||
*
|
||||
* always (re)connect ports
|
||||
*/
|
||||
|
||||
for(i=0; i<nch; i++) {
|
||||
pdes->connect_port(setup->chhandles[i],
|
||||
setup->inputs[i % setup->ninputs],
|
||||
setup->inbufs[i]);
|
||||
pdes->connect_port(setup->chhandles[i],
|
||||
setup->outputs[i % setup->ninputs],
|
||||
setup->outbufs[i]);
|
||||
|
||||
/* connect (input) controls */
|
||||
|
||||
for (p=0; p<setup->nports; p++) {
|
||||
LADSPA_PortDescriptor d = pdes->PortDescriptors[p];
|
||||
if (LADSPA_IS_PORT_CONTROL(d)) {
|
||||
if (LADSPA_IS_PORT_INPUT(d)) {
|
||||
pdes->connect_port(setup->chhandles[i], p,
|
||||
&(setup->inputcontrols[p]) );
|
||||
} else {
|
||||
pdes->connect_port(setup->chhandles[i], p,
|
||||
&(setup->outputcontrols[p]) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Activate filter (if it isn't already :) ) */
|
||||
|
||||
if (pdes->activate && !setup->activated && i % setup->ninputs == 0)
|
||||
pdes->activate(setup->chhandles[i]);
|
||||
|
||||
} /* All channels/filters done! except for... */
|
||||
setup->activated = 1;
|
||||
|
||||
/* Stereo effect with one channel left. Use same buffer for left
|
||||
* and right. connect it to the second port.
|
||||
*/
|
||||
|
||||
for (p = i; p % setup->ninputs; p++) {
|
||||
pdes->connect_port(setup->chhandles[i-1],
|
||||
setup->inputs[p % setup->ninputs],
|
||||
setup->inbufs[i-1]);
|
||||
pdes->connect_port(setup->chhandles[i-1],
|
||||
setup->outputs[p % setup->ninputs],
|
||||
setup->outbufs[i-1]);
|
||||
} /* done! */
|
||||
|
||||
} /* setup for first call/change of bufsize is done.
|
||||
* normal playing routine follows...
|
||||
*/
|
||||
|
||||
/* Right now, I use a separate input and output buffer.
|
||||
* I could change this to in-place processing (inbuf==outbuf), but some
|
||||
* ladspa filters are broken and are not able to handle that. This seems
|
||||
* fast enough, so unless somebody complains, it stays this way :)
|
||||
*/
|
||||
|
||||
/* Fill inbufs */
|
||||
|
||||
for (p=0; p<setup->bufsize; p++) {
|
||||
for (i=0; i<nch; i++) {
|
||||
setup->inbufs[i][p] = audio[p*nch + i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Run filter(s) */
|
||||
|
||||
for (i=0; i<nch; i+=setup->ninputs) {
|
||||
pdes->run(setup->chhandles[i], setup->bufsize);
|
||||
}
|
||||
|
||||
/* Extract outbufs */
|
||||
|
||||
for (p=0; p<setup->bufsize; p++) {
|
||||
for (i=0; i<nch; i++) {
|
||||
audio[p*nch + i] = setup->outbufs[i][p];
|
||||
}
|
||||
}
|
||||
|
||||
/* done */
|
||||
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/** \brief Open LADSPA Plugin Loader Filter
|
||||
*
|
||||
* \param af Audio Filter instance
|
||||
*
|
||||
* \return Either AF_ERROR or AF_OK
|
||||
*/
|
||||
|
||||
static int af_open(struct af_instance *af) {
|
||||
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
|
||||
af_ladspa_t *setup = af->priv;
|
||||
|
||||
setup->status = AF_ERROR; /* will be set to AF_OK if
|
||||
* all went OK and filter()
|
||||
* should proceed.
|
||||
*/
|
||||
|
||||
setup->myname = strdup(af_info_ladspa.name);
|
||||
if (!setup->myname)
|
||||
return af_ladspa_malloc_failed((char*)af_info_ladspa.name);
|
||||
|
||||
if (!setup->file || !setup->file[0]) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("No library file specified."));
|
||||
uninit(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (!setup->label || !setup->label[0]) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("No filter label specified."));
|
||||
uninit(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
free(setup->myname);
|
||||
setup->myname = calloc(strlen(af_info_ladspa.name)+strlen(setup->file)+
|
||||
strlen(setup->label)+6, 1);
|
||||
snprintf(setup->myname, strlen(af_info_ladspa.name)+
|
||||
strlen(setup->file)+strlen(setup->label)+6, "%s: (%s:%s)",
|
||||
af_info_ladspa.name, setup->file, setup->label);
|
||||
|
||||
/* load plugin :) */
|
||||
|
||||
if ( af_ladspa_load_plugin(af) != AF_OK )
|
||||
return AF_ERROR;
|
||||
|
||||
/* see what inputs, outputs and controls this plugin has */
|
||||
if ( af_ladspa_parse_plugin(af) != AF_OK )
|
||||
return AF_ERROR;
|
||||
|
||||
/* ninputcontrols is set by now, read control values from arg */
|
||||
|
||||
float val;
|
||||
char *line = setup->controls;
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
if (!line || (i != 0 && *line != ',')) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("Not enough controls specified on the command line."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (i != 0)
|
||||
line++;
|
||||
if (sscanf(line, "%f", &val) != 1) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("Not enough controls specified on the command line."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
setup->inputcontrols[setup->inputcontrolsmap[i]] = val;
|
||||
line = strchr(line, ',');
|
||||
}
|
||||
|
||||
MP_VERBOSE(af, "%s: input controls: ", setup->myname);
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
MP_VERBOSE(af, "%0.4f ",
|
||||
setup->inputcontrols[setup->inputcontrolsmap[i]]);
|
||||
}
|
||||
MP_VERBOSE(af, "\n");
|
||||
|
||||
/* check boundaries of inputcontrols */
|
||||
|
||||
MP_VERBOSE(af, "%s: checking boundaries of input controls\n",
|
||||
setup->myname);
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
int p = setup->inputcontrolsmap[i];
|
||||
LADSPA_PortRangeHint hint =
|
||||
setup->plugin_descriptor->PortRangeHints[p];
|
||||
val = setup->inputcontrols[p];
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor) &&
|
||||
val < hint.LowerBound) {
|
||||
MP_ERR(af, "%s: Input control #%d is below lower boundary of %0.4f.\n",
|
||||
setup->myname, i, hint.LowerBound);
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor) &&
|
||||
val > hint.UpperBound) {
|
||||
MP_ERR(af, "%s: Input control #%d is above upper boundary of %0.4f.\n",
|
||||
setup->myname, i, hint.UpperBound);
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
MP_VERBOSE(af, "%s: all controls have sane values\n",
|
||||
setup->myname);
|
||||
|
||||
/* All is well! */
|
||||
setup->status = AF_OK;
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
@@ -26,7 +26,6 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/audioconvert.h>
|
||||
#include <libavutil/intreadwrite.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/bswap.h>
|
||||
@@ -75,7 +74,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (AF_FORMAT_IS_SPECIAL(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
mp_audio_set_format(in, s->in_sampleformat);
|
||||
@@ -156,6 +155,13 @@ static void uninit(struct af_instance* af)
|
||||
}
|
||||
}
|
||||
|
||||
static void update_delay(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples) /
|
||||
(double)s->input->rate;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
@@ -166,6 +172,7 @@ static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = audio;
|
||||
update_delay(af);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -177,22 +184,27 @@ static void swap_16(uint16_t *ptr, size_t size)
|
||||
|
||||
// Copy data from input frame to encode frame (because libavcodec wants a full
|
||||
// AC3 frame for encoding, while filter input frames can be smaller or larger).
|
||||
// Return true if the
|
||||
static bool fill_buffer(af_ac3enc_t *s)
|
||||
// Return true if the frame is complete.
|
||||
static bool fill_buffer(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
af->delay = 0;
|
||||
|
||||
if (s->pending) {
|
||||
int copy = MPMIN(s->in_samples - s->input->samples, s->pending->samples);
|
||||
s->input->samples += copy;
|
||||
mp_audio_copy(s->input, s->input->samples - copy, s->pending, 0, copy);
|
||||
mp_audio_skip_samples(s->pending, copy);
|
||||
}
|
||||
update_delay(af);
|
||||
return s->input->samples >= s->in_samples;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
if (!fill_buffer(s))
|
||||
if (!fill_buffer(af))
|
||||
return 0; // need more input
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
@@ -256,6 +268,7 @@ static int filter_out(struct af_instance *af)
|
||||
swap_16((uint16_t *)(buf + header_len), s->pkt.size / 2);
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
update_delay(af);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,14 @@
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "af.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/tags.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
|
||||
@@ -63,6 +66,8 @@ struct priv {
|
||||
|
||||
bool eof;
|
||||
|
||||
struct mp_tags *metadata;
|
||||
|
||||
// options
|
||||
char *cfg_graph;
|
||||
char **cfg_avopts;
|
||||
@@ -213,6 +218,12 @@ 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_GET_METADATA:
|
||||
if (p->metadata) {
|
||||
*(struct mp_tags *)arg = *p->metadata;
|
||||
return CONTROL_OK;
|
||||
}
|
||||
return CONTROL_NA;
|
||||
case AF_CONTROL_RESET:
|
||||
reset(af);
|
||||
return AF_OK;
|
||||
@@ -220,6 +231,17 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
#if HAVE_AVFRAME_METADATA
|
||||
struct priv *p = af->priv;
|
||||
if (!p->metadata)
|
||||
p->metadata = talloc_zero(p, struct mp_tags);
|
||||
|
||||
mp_tags_copy_from_av_dictionary(p->metadata, av_frame_get_metadata(frame));
|
||||
#endif
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
@@ -307,6 +329,7 @@ static int filter_out(struct af_instance *af)
|
||||
af->delay = in_time - out_time;
|
||||
}
|
||||
|
||||
get_metadata_from_av_frame(af, frame);
|
||||
af_add_output_frame(af, out);
|
||||
av_frame_free(&frame);
|
||||
return 0;
|
||||
|
||||
+235
-160
@@ -28,9 +28,9 @@
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/audioconvert.h>
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#include "common/common.h"
|
||||
@@ -58,12 +58,30 @@
|
||||
#include "options/m_option.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "osdep/endian.h"
|
||||
|
||||
struct af_resample_opts {
|
||||
int filter_size;
|
||||
int phase_shift;
|
||||
int linear;
|
||||
double cutoff;
|
||||
int normalize;
|
||||
};
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
char **avopts;
|
||||
double playback_speed;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct mp_audio avrctx_fmt; // output format of avrctx
|
||||
struct mp_audio pool_fmt; // format used to allocate frames for avrctx output
|
||||
struct mp_audio pre_out_fmt; // format before final conversion (S24)
|
||||
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
|
||||
struct af_resample_opts opts; // opts requested by the user
|
||||
// At least libswresample keeps a pointer around for this:
|
||||
int reorder_in[MP_NUM_CHANNELS];
|
||||
int reorder_out[MP_NUM_CHANNELS];
|
||||
struct mp_audio_pool *reorder_buffer;
|
||||
|
||||
int in_rate_af; // filter input sample rate
|
||||
int in_rate; // actual rate (used by lavr), adjusted for playback speed
|
||||
@@ -74,50 +92,53 @@ struct af_resample_opts {
|
||||
struct mp_chmap out_channels;
|
||||
};
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
char **avopts;
|
||||
double playback_speed;
|
||||
struct mp_audio *pending;
|
||||
bool avrctx_ok;
|
||||
struct AVAudioResampleContext *avrctx;
|
||||
struct AVAudioResampleContext *avrctx_out; // for output channel reordering
|
||||
struct af_resample_opts ctx; // opts in the context
|
||||
struct af_resample_opts opts; // opts requested by the user
|
||||
// At least libswresample keeps a pointer around for this:
|
||||
int reorder_in[MP_NUM_CHANNELS];
|
||||
int reorder_out[MP_NUM_CHANNELS];
|
||||
struct mp_audio_pool *reorder_buffer;
|
||||
};
|
||||
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
static int get_delay(struct af_resample *s)
|
||||
static double get_delay(struct af_resample *s)
|
||||
{
|
||||
return avresample_get_delay(s->avrctx);
|
||||
return avresample_get_delay(s->avrctx) / (double)s->in_rate +
|
||||
avresample_available(s->avrctx) / (double)s->out_rate;
|
||||
}
|
||||
static void drop_all_output(struct af_resample *s)
|
||||
{
|
||||
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
|
||||
}
|
||||
static int get_drain_samples(struct af_resample *s)
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
return avresample_get_out_samples(s->avrctx, 0);
|
||||
return avresample_get_out_samples(s->avrctx, in_samples);
|
||||
}
|
||||
#else
|
||||
static int get_delay(struct af_resample *s)
|
||||
static double get_delay(struct af_resample *s)
|
||||
{
|
||||
return swr_get_delay(s->avrctx, s->ctx.in_rate);
|
||||
int64_t base = s->in_rate * (int64_t)s->out_rate;
|
||||
return swr_get_delay(s->avrctx, base) / (double)base;
|
||||
}
|
||||
static void drop_all_output(struct af_resample *s)
|
||||
{
|
||||
while (swr_drop_output(s->avrctx, 1000) > 0) {}
|
||||
}
|
||||
static int get_drain_samples(struct af_resample *s)
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
return 4096; // libswscale does not have this
|
||||
#if LIBSWRESAMPLE_VERSION_MAJOR > 1 || LIBSWRESAMPLE_VERSION_MINOR >= 2
|
||||
return swr_get_out_samples(s->avrctx, in_samples);
|
||||
#else
|
||||
return av_rescale_rnd(in_samples, s->out_rate, s->in_rate, AV_ROUND_UP)
|
||||
+ swr_get_delay(s->avrctx, s->out_rate);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
static void close_lavrr(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
if (s->avrctx)
|
||||
avresample_close(s->avrctx);
|
||||
avresample_free(&s->avrctx);
|
||||
if (s->avrctx_out)
|
||||
avresample_close(s->avrctx_out);
|
||||
avresample_free(&s->avrctx_out);
|
||||
}
|
||||
|
||||
static int resample_frame(struct AVAudioResampleContext *r,
|
||||
struct mp_audio *out, struct mp_audio *in)
|
||||
{
|
||||
@@ -140,72 +161,82 @@ static int rate_from_speed(int rate, double speed)
|
||||
return lrint(rate * speed);
|
||||
}
|
||||
|
||||
static bool needs_lavrctx_reconfigure(struct af_resample *s,
|
||||
struct mp_audio *in,
|
||||
struct mp_audio *out)
|
||||
// Return the format libavresample should convert to, given the final output
|
||||
// format mp_format. In some cases (S24) we perform an extra conversion step,
|
||||
// and signal here what exactly libavresample should output. It will be the
|
||||
// input to the final conversion to mp_format.
|
||||
static int check_output_conversion(int mp_format)
|
||||
{
|
||||
return s->ctx.in_rate_af != in->rate ||
|
||||
s->ctx.in_format != in->format ||
|
||||
!mp_chmap_equals(&s->ctx.in_channels, &in->channels) ||
|
||||
s->ctx.out_rate != out->rate ||
|
||||
s->ctx.out_format != out->format ||
|
||||
!mp_chmap_equals(&s->ctx.out_channels, &out->channels) ||
|
||||
s->ctx.filter_size != s->opts.filter_size ||
|
||||
s->ctx.phase_shift != s->opts.phase_shift ||
|
||||
s->ctx.linear != s->opts.linear ||
|
||||
s->ctx.cutoff != s->opts.cutoff;
|
||||
|
||||
if (mp_format == AF_FORMAT_S24)
|
||||
return AV_SAMPLE_FMT_S32;
|
||||
return af_to_avformat(mp_format);
|
||||
}
|
||||
|
||||
static bool test_conversion(int src_format, int dst_format)
|
||||
bool af_lavrresample_test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
return af_to_avformat(src_format) != AV_SAMPLE_FMT_NONE &&
|
||||
af_to_avformat(dst_format) != AV_SAMPLE_FMT_NONE;
|
||||
check_output_conversion(dst_format) != AV_SAMPLE_FMT_NONE;
|
||||
}
|
||||
|
||||
// mp_chmap_get_reorder() performs:
|
||||
// to->speaker[n] = from->speaker[src[n]]
|
||||
// but libavresample does:
|
||||
// to->speaker[dst[n]] = from->speaker[n]
|
||||
static void transpose_order(int *map, int num)
|
||||
{
|
||||
int nmap[MP_NUM_CHANNELS] = {0};
|
||||
for (int n = 0; n < num; n++) {
|
||||
for (int i = 0; i < num; i++) {
|
||||
if (map[n] == i)
|
||||
nmap[i] = n;
|
||||
}
|
||||
}
|
||||
memcpy(map, nmap, sizeof(nmap));
|
||||
}
|
||||
|
||||
static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
struct mp_audio *out)
|
||||
struct mp_audio *out, bool verbose)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
s->avrctx_ok = false;
|
||||
close_lavrr(af);
|
||||
|
||||
s->avrctx = avresample_alloc_context();
|
||||
s->avrctx_out = avresample_alloc_context();
|
||||
if (!s->avrctx || !s->avrctx_out)
|
||||
goto error;
|
||||
|
||||
enum AVSampleFormat in_samplefmt = af_to_avformat(in->format);
|
||||
enum AVSampleFormat out_samplefmt = af_to_avformat(out->format);
|
||||
enum AVSampleFormat out_samplefmt = check_output_conversion(out->format);
|
||||
enum AVSampleFormat out_samplefmtp = av_get_planar_sample_fmt(out_samplefmt);
|
||||
|
||||
if (in_samplefmt == AV_SAMPLE_FMT_NONE || out_samplefmt == AV_SAMPLE_FMT_NONE)
|
||||
return AF_ERROR;
|
||||
if (in_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmt == AV_SAMPLE_FMT_NONE ||
|
||||
out_samplefmtp == AV_SAMPLE_FMT_NONE)
|
||||
goto error;
|
||||
|
||||
avresample_close(s->avrctx);
|
||||
avresample_close(s->avrctx_out);
|
||||
s->out_rate = out->rate;
|
||||
s->in_rate_af = in->rate;
|
||||
s->in_rate = rate_from_speed(in->rate, s->playback_speed);
|
||||
s->out_format = out->format;
|
||||
s->in_format = in->format;
|
||||
s->out_channels= out->channels;
|
||||
s->in_channels = in->channels;
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
av_opt_set_int(s->avrctx, "filter_size", s->opts.filter_size, 0);
|
||||
av_opt_set_int(s->avrctx, "phase_shift", s->opts.phase_shift, 0);
|
||||
av_opt_set_int(s->avrctx, "linear_interp", s->opts.linear, 0);
|
||||
|
||||
s->ctx.out_rate = out->rate;
|
||||
s->ctx.in_rate_af = in->rate;
|
||||
s->ctx.in_rate = rate_from_speed(in->rate, s->playback_speed);
|
||||
s->ctx.out_format = out->format;
|
||||
s->ctx.in_format = in->format;
|
||||
s->ctx.out_channels= out->channels;
|
||||
s->ctx.in_channels = in->channels;
|
||||
s->ctx.filter_size = s->opts.filter_size;
|
||||
s->ctx.phase_shift = s->opts.phase_shift;
|
||||
s->ctx.linear = s->opts.linear;
|
||||
s->ctx.cutoff = s->opts.cutoff;
|
||||
|
||||
av_opt_set_int(s->avrctx, "filter_size", s->ctx.filter_size, 0);
|
||||
av_opt_set_int(s->avrctx, "phase_shift", s->ctx.phase_shift, 0);
|
||||
av_opt_set_int(s->avrctx, "linear_interp", s->ctx.linear, 0);
|
||||
|
||||
av_opt_set_double(s->avrctx, "cutoff", s->ctx.cutoff, 0);
|
||||
av_opt_set_double(s->avrctx, "cutoff", s->opts.cutoff, 0);
|
||||
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
av_opt_set_double(s->avrctx, "rematrix_maxval", 1.0, 0);
|
||||
av_opt_set_double(s->avrctx, "rematrix_maxval", s->opts.normalize ? 1 : 1000, 0);
|
||||
#else
|
||||
av_opt_set_int(s->avrctx, "normalize_mix_level", s->opts.normalize, 0);
|
||||
#endif
|
||||
|
||||
if (mp_set_avopts(af->log, s->avrctx, s->avopts) < 0)
|
||||
return AF_ERROR;
|
||||
goto error;
|
||||
|
||||
struct mp_chmap map_in = in->channels;
|
||||
struct mp_chmap map_out = out->channels;
|
||||
@@ -220,45 +251,86 @@ static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
|
||||
uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_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);
|
||||
|
||||
av_opt_set_int(s->avrctx, "in_sample_rate", s->ctx.in_rate, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_rate", s->ctx.out_rate, 0);
|
||||
|
||||
av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmt, 0);
|
||||
|
||||
struct mp_chmap in_lavc;
|
||||
struct mp_chmap in_lavc, out_lavc;
|
||||
mp_chmap_from_lavc(&in_lavc, in_ch_layout);
|
||||
mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
|
||||
|
||||
struct mp_chmap out_lavc;
|
||||
mp_chmap_from_lavc(&out_lavc, out_ch_layout);
|
||||
|
||||
if (verbose && !mp_chmap_equals(&in_lavc, &out_lavc)) {
|
||||
MP_VERBOSE(af, "Remix: %s -> %s\n", mp_chmap_to_str(&in_lavc),
|
||||
mp_chmap_to_str(&out_lavc));
|
||||
}
|
||||
|
||||
if (in_lavc.num != map_in.num) {
|
||||
// For handling NA channels, we would have to add a planarization step.
|
||||
MP_FATAL(af, "Unsupported channel remapping.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
|
||||
transpose_order(s->reorder_in, map_in.num);
|
||||
|
||||
if (mp_chmap_equals(&out_lavc, &map_out)) {
|
||||
// No intermediate step required - output new format directly.
|
||||
out_samplefmtp = out_samplefmt;
|
||||
} else {
|
||||
// Verify that we really just reorder and/or insert NA channels.
|
||||
struct mp_chmap withna = out_lavc;
|
||||
mp_chmap_fill_na(&withna, map_out.num);
|
||||
if (withna.num != map_out.num)
|
||||
goto error;
|
||||
}
|
||||
mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
|
||||
|
||||
// Same configuration; we just reorder.
|
||||
av_opt_set_int(s->avrctx_out, "in_channel_layout", out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_channel_layout", out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmt, 0);
|
||||
s->avrctx_fmt = *out;
|
||||
mp_audio_set_channels(&s->avrctx_fmt, &out_lavc);
|
||||
mp_audio_set_format(&s->avrctx_fmt, af_from_avformat(out_samplefmtp));
|
||||
|
||||
s->pre_out_fmt = *out;
|
||||
mp_audio_set_format(&s->pre_out_fmt, af_from_avformat(out_samplefmt));
|
||||
|
||||
// If there are NA channels, the final output will have more channels than
|
||||
// the avrctx output. Also, avrctx will output planar (out_samplefmtp was
|
||||
// not overwritten). Allocate the output frame with more channels, so the
|
||||
// NA channels can be trivially added.
|
||||
s->pool_fmt = s->avrctx_fmt;
|
||||
if (map_out.num > out_lavc.num)
|
||||
mp_audio_set_channels(&s->pool_fmt, &map_out);
|
||||
|
||||
// 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);
|
||||
av_opt_set_int(s->avrctx, "in_sample_rate", s->in_rate, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_rate", s->out_rate, 0);
|
||||
av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmtp, 0);
|
||||
|
||||
// Just needs the correct number of channels.
|
||||
int fake_out_ch_layout = av_get_default_channel_layout(map_out.num);
|
||||
if (!fake_out_ch_layout)
|
||||
goto error;
|
||||
|
||||
// Deplanarize if needed.
|
||||
av_opt_set_int(s->avrctx_out, "in_channel_layout", fake_out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_channel_layout", fake_out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmtp, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->ctx.out_rate, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_rate", s->ctx.out_rate, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->out_rate, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_rate", s->out_rate, 0);
|
||||
|
||||
// API has weird requirements, quoting avresample.h:
|
||||
// * This function can only be called when the allocated context is not open.
|
||||
// * Also, the input channel layout must have already been set.
|
||||
avresample_set_channel_mapping(s->avrctx, s->reorder_in);
|
||||
avresample_set_channel_mapping(s->avrctx_out, s->reorder_out);
|
||||
|
||||
if (avresample_open(s->avrctx) < 0 ||
|
||||
avresample_open(s->avrctx_out) < 0)
|
||||
{
|
||||
if (avresample_open(s->avrctx) < 0 || avresample_open(s->avrctx_out) < 0) {
|
||||
MP_ERR(af, "Cannot open Libavresample Context. \n");
|
||||
return AF_ERROR;
|
||||
goto error;
|
||||
}
|
||||
s->avrctx_ok = true;
|
||||
return AF_OK;
|
||||
|
||||
error:
|
||||
close_lavrr(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
|
||||
@@ -286,20 +358,20 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
if (af_to_avformat(out->format) == AV_SAMPLE_FMT_NONE)
|
||||
if (check_output_conversion(out->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(out, in->format);
|
||||
|
||||
int r = ((in->format == orig_in.format) &&
|
||||
mp_chmap_equals(&in->channels, &orig_in.channels))
|
||||
? AF_OK : AF_FALSE;
|
||||
|
||||
if (r == AF_OK && needs_lavrctx_reconfigure(s, in, out))
|
||||
r = configure_lavrr(af, in, out);
|
||||
if (r == AF_OK)
|
||||
r = configure_lavrr(af, in, out, true);
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_SET_FORMAT: {
|
||||
int format = *(int *)arg;
|
||||
if (format && af_to_avformat(format) == AV_SAMPLE_FMT_NONE)
|
||||
if (format && check_output_conversion(format) == AV_SAMPLE_FMT_NONE)
|
||||
return AF_FALSE;
|
||||
|
||||
mp_audio_set_format(af->data, format);
|
||||
@@ -314,61 +386,72 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
int new_rate = rate_from_speed(s->ctx.in_rate_af, s->playback_speed);
|
||||
if (new_rate != s->ctx.in_rate && s->avrctx_ok && af->fmt_out.format) {
|
||||
int new_rate = rate_from_speed(s->in_rate_af, s->playback_speed);
|
||||
if (new_rate != s->in_rate && s->avrctx && af->fmt_out.format) {
|
||||
// Before reconfiguring, drain the audio that is still buffered
|
||||
// in the resampler.
|
||||
struct mp_audio *pending = talloc_zero(NULL, struct mp_audio);
|
||||
mp_audio_copy_config(pending, &af->fmt_out);
|
||||
pending->samples = get_drain_samples(s);
|
||||
if (pending->samples > 0) {
|
||||
mp_audio_realloc_min(pending, pending->samples);
|
||||
int r = resample_frame(s->avrctx, pending, NULL);
|
||||
pending->samples = MPMAX(r, 0);
|
||||
}
|
||||
af->filter_frame(af, NULL);
|
||||
// Reinitialize resampler.
|
||||
configure_lavrr(af, &af->fmt_in, &af->fmt_out);
|
||||
s->pending = pending;
|
||||
configure_lavrr(af, &af->fmt_in, &af->fmt_out, false);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
drop_all_output(s);
|
||||
if (s->avrctx)
|
||||
drop_all_output(s);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
#undef ctx_opt_set_int
|
||||
#undef ctx_opt_set_dbl
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
if (s->avrctx)
|
||||
avresample_close(s->avrctx);
|
||||
avresample_free(&s->avrctx);
|
||||
if (s->avrctx_out)
|
||||
avresample_close(s->avrctx_out);
|
||||
avresample_free(&s->avrctx_out);
|
||||
talloc_free(s->pending);
|
||||
close_lavrr(af);
|
||||
}
|
||||
|
||||
static bool needs_reorder(int *reorder, int num_ch)
|
||||
// The LSB is always ignored.
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
#define SHIFT24(x) ((3-(x))*8)
|
||||
#else
|
||||
#define SHIFT24(x) (((x)+1)*8)
|
||||
#endif
|
||||
|
||||
static void extra_output_conversion(struct af_instance *af, struct mp_audio *mpa)
|
||||
{
|
||||
for (int n = 0; n < num_ch; n++) {
|
||||
if (reorder[n] != n)
|
||||
return true;
|
||||
if (mpa->format == AF_FORMAT_S32 && af->data->format == AF_FORMAT_S24) {
|
||||
size_t len = mp_audio_psize(mpa) / mpa->bps;
|
||||
for (int s = 0; s < len; s++) {
|
||||
uint32_t val = *((uint32_t *)mpa->planes[0] + s);
|
||||
uint8_t *ptr = (uint8_t *)mpa->planes[0] + s * 3;
|
||||
ptr[0] = val >> SHIFT24(0);
|
||||
ptr[1] = val >> SHIFT24(1);
|
||||
ptr[2] = val >> SHIFT24(2);
|
||||
}
|
||||
mp_audio_set_format(mpa, AF_FORMAT_S24);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reorder_planes(struct mp_audio *mpa, int *reorder)
|
||||
// This relies on the tricky way mpa was allocated.
|
||||
static void reorder_planes(struct mp_audio *mpa, int *reorder,
|
||||
struct mp_chmap *newmap)
|
||||
{
|
||||
struct mp_audio prev = *mpa;
|
||||
mp_audio_set_channels(mpa, newmap);
|
||||
|
||||
// The trailing planes were never written by avrctx, they're the NA channels.
|
||||
int next_na = prev.num_planes;
|
||||
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
assert(reorder[n] >= 0 && reorder[n] < mpa->num_planes);
|
||||
mpa->planes[n] = prev.planes[reorder[n]];
|
||||
int src = reorder[n];
|
||||
assert(src >= -1 && src < prev.num_planes);
|
||||
if (src >= 0) {
|
||||
mpa->planes[n] = prev.planes[src];
|
||||
} else {
|
||||
assert(next_na < mpa->num_planes);
|
||||
mpa->planes[n] = prev.planes[next_na++];
|
||||
af_fill_silence(mpa->planes[n], mpa->sstride * mpa->samples,
|
||||
mpa->format);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,27 +459,17 @@ static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
if (s->pending) {
|
||||
if (s->pending->samples) {
|
||||
af_add_output_frame(af, s->pending);
|
||||
} else {
|
||||
talloc_free(s->pending);
|
||||
}
|
||||
s->pending = NULL;
|
||||
}
|
||||
int samples = get_out_samples(s, in ? in->samples : 0);
|
||||
|
||||
int samples = avresample_available(s->avrctx) +
|
||||
av_rescale_rnd(get_delay(s) + (in ? in->samples : 0),
|
||||
s->ctx.out_rate, s->ctx.in_rate, AV_ROUND_UP);
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data, samples);
|
||||
struct mp_audio out_format = s->pool_fmt;
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, &out_format, samples);
|
||||
if (!out)
|
||||
goto error;
|
||||
if (in)
|
||||
mp_audio_copy_attributes(out, in);
|
||||
|
||||
mp_audio_realloc_min(out, out->samples);
|
||||
|
||||
af->delay = get_delay(s) / (double)s->ctx.in_rate;
|
||||
if (!s->avrctx)
|
||||
goto error;
|
||||
|
||||
if (out->samples) {
|
||||
out->samples = resample_frame(s->avrctx, out, in);
|
||||
@@ -404,11 +477,15 @@ static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (needs_reorder(s->reorder_out, out->nch)) {
|
||||
if (AF_FORMAT_IS_PLANAR(out->format)) {
|
||||
reorder_planes(out, s->reorder_out);
|
||||
} else if (out->samples) {
|
||||
struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer, out,
|
||||
struct mp_audio real_out = *out;
|
||||
mp_audio_copy_config(out, &s->avrctx_fmt);
|
||||
|
||||
if (out->samples && !mp_audio_config_equals(out, &s->pre_out_fmt)) {
|
||||
assert(af_fmt_is_planar(out->format) && out->format == real_out.format);
|
||||
reorder_planes(out, s->reorder_out, &s->pool_fmt.channels);
|
||||
if (!mp_audio_config_equals(out, &s->pre_out_fmt)) {
|
||||
struct mp_audio *new = mp_audio_pool_get(s->reorder_buffer,
|
||||
&s->pre_out_fmt,
|
||||
out->samples);
|
||||
if (!new)
|
||||
goto error;
|
||||
@@ -421,12 +498,17 @@ static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
}
|
||||
}
|
||||
|
||||
extra_output_conversion(af, out);
|
||||
|
||||
talloc_free(in);
|
||||
if (out->samples) {
|
||||
af_add_output_frame(af, out);
|
||||
} else {
|
||||
talloc_free(out);
|
||||
}
|
||||
|
||||
af->delay = get_delay(s);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
talloc_free(in);
|
||||
@@ -445,17 +527,9 @@ static int af_open(struct af_instance *af)
|
||||
if (s->opts.cutoff <= 0.0)
|
||||
s->opts.cutoff = af_resample_default_cutoff(s->opts.filter_size);
|
||||
|
||||
s->avrctx = avresample_alloc_context();
|
||||
s->avrctx_out = avresample_alloc_context();
|
||||
s->reorder_buffer = mp_audio_pool_create(s);
|
||||
|
||||
if (s->avrctx && s->avrctx_out) {
|
||||
return AF_OK;
|
||||
} else {
|
||||
MP_ERR(af, "Cannot initialize Libavresample Context. \n");
|
||||
uninit(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct af_resample
|
||||
@@ -464,13 +538,13 @@ const struct af_info af_info_lavrresample = {
|
||||
.info = "Sample frequency conversion using libavresample",
|
||||
.name = "lavrresample",
|
||||
.open = af_open,
|
||||
.test_conversion = test_conversion,
|
||||
.priv_size = sizeof(struct af_resample),
|
||||
.priv_defaults = &(const struct af_resample) {
|
||||
.opts = {
|
||||
.filter_size = 16,
|
||||
.cutoff = 0.0,
|
||||
.phase_shift = 10,
|
||||
.normalize = 1,
|
||||
},
|
||||
.playback_speed = 1.0,
|
||||
.allow_detach = 1,
|
||||
@@ -481,6 +555,7 @@ 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_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2006 Michael Niedermayer
|
||||
* Copyright (C) 2004 Alex Beregszaszi
|
||||
* based upon af_extrastereo.c by Pierre Lombard
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_sinesuppress_s
|
||||
{
|
||||
double freq;
|
||||
double decay;
|
||||
double real;
|
||||
double imag;
|
||||
double ref;
|
||||
double pos;
|
||||
}af_sinesuppress_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_num_channels(af->data, 1);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int play_s16(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
af_sinesuppress_t *s = af->priv;
|
||||
register int i = 0;
|
||||
int16_t *a = (int16_t*)data->planes[0]; // Audio data
|
||||
int len = data->samples*data->nch; // Number of samples
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
double co= cos(s->pos);
|
||||
double si= sin(s->pos);
|
||||
|
||||
s->real += co * a[i];
|
||||
s->imag += si * a[i];
|
||||
s->ref += co * co;
|
||||
|
||||
a[i] -= (s->real * co + s->imag * si) / s->ref;
|
||||
|
||||
s->real -= s->real * s->decay;
|
||||
s->imag -= s->imag * s->decay;
|
||||
s->ref -= s->ref * s->decay;
|
||||
|
||||
s->pos += 2 * M_PI * s->freq / data->rate;
|
||||
}
|
||||
|
||||
MP_VERBOSE(af, "f:%8.2f: amp:%8.2f\n", s->freq, sqrt(s->real*s->real + s->imag*s->imag) / s->ref);
|
||||
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = play_s16;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_sinesuppress_t
|
||||
const struct af_info af_info_sinesuppress = {
|
||||
.info = "Sine Suppress",
|
||||
.name = "sinesuppress",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_sinesuppress_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_DOUBLE("freq", freq, 0, OPTDEF_DOUBLE(50.0)),
|
||||
OPT_DOUBLE("decay", decay, 0, OPTDEF_DOUBLE(0.0001)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2002 Anders Johansson ajh@watri.uwa.edu.au
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* This filter adds a sub-woofer channels to the audio stream by
|
||||
averaging the left and right channel and low-pass filter them. The
|
||||
low-pass filter is implemented as a 4th order IIR Butterworth
|
||||
filter, with a variable cutoff frequency between 10 and 300 Hz. The
|
||||
filter gives 24dB/octave attenuation. There are two runtime
|
||||
controls one for setting which channel to insert the sub-audio into
|
||||
called AF_CONTROL_SUB_CH and one for setting the cutoff frequency
|
||||
called AF_CONTROL_SUB_FC.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "dsp.h"
|
||||
|
||||
// Q value for low-pass filter
|
||||
#define Q 1.0
|
||||
|
||||
// Analog domain biquad section
|
||||
typedef struct{
|
||||
float a[3]; // Numerator coefficients
|
||||
float b[3]; // Denominator coefficients
|
||||
} biquad_t;
|
||||
|
||||
// S-parameters for designing 4th order Butterworth filter
|
||||
static const biquad_t sp[2] = {{{1.0,0.0,0.0},{1.0,0.765367,1.0}},
|
||||
{{1.0,0.0,0.0},{1.0,1.847759,1.0}}};
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_sub_s
|
||||
{
|
||||
float w[2][4]; // Filter taps for low-pass filter
|
||||
float q[2][2]; // Circular queues
|
||||
float fc; // Cutoff frequency [Hz] for low-pass filter
|
||||
float k; // Filter gain;
|
||||
int ch; // Channel number which to insert the filtered data
|
||||
|
||||
}af_sub_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_sub_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_channels_old(af->data, MPMAX(s->ch+1,((struct mp_audio*)arg)->nch));
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
// Design low-pass filter
|
||||
s->k = 1.0;
|
||||
if((-1 == af_filter_szxform(sp[0].a, sp[0].b, Q, s->fc,
|
||||
(float)af->data->rate, &s->k, s->w[0])) ||
|
||||
(-1 == af_filter_szxform(sp[1].a, sp[1].b, Q, s->fc,
|
||||
(float)af->data->rate, &s->k, s->w[1])))
|
||||
return AF_ERROR;
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
#ifndef IIR
|
||||
#define IIR(in,w,q,out) { \
|
||||
float h0 = (q)[0]; \
|
||||
float h1 = (q)[1]; \
|
||||
float hn = (in) - h0 * (w)[0] - h1 * (w)[1]; \
|
||||
out = hn + h0 * (w)[2] + h1 * (w)[3]; \
|
||||
(q)[1] = h0; \
|
||||
(q)[0] = hn; \
|
||||
}
|
||||
#endif
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_sub_t* s = af->priv; // Setup for this instance
|
||||
float* a = c->planes[0]; // Audio data
|
||||
int len = c->samples*c->nch; // Number of samples in current audio block
|
||||
int nch = c->nch; // Number of channels
|
||||
int ch = s->ch; // Channel in which to insert the sub audio
|
||||
register int i;
|
||||
|
||||
// Run filter
|
||||
for(i=0;i<len;i+=nch){
|
||||
// Average left and right
|
||||
register float x = 0.5 * (a[i] + a[i+1]);
|
||||
IIR(x * s->k, s->w[0], s->q[0], x);
|
||||
IIR(x , s->w[1], s->q[1], a[i+ch]);
|
||||
}
|
||||
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = filter_frame;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_sub_t
|
||||
const struct af_info af_info_sub = {
|
||||
.info = "Audio filter for adding a sub-base channel",
|
||||
.name = "sub",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_sub_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOATRANGE("fc", fc, 0, 20, 300, OPTDEF_FLOAT(60.0)),
|
||||
OPT_INTRANGE("ch", ch, 0, 0, AF_NCH - 1, OPTDEF_INT(5)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,246 +0,0 @@
|
||||
/*
|
||||
* Filter to do simple decoding of matrixed surround sound.
|
||||
* This will provide a (basic) surround-sound effect from
|
||||
* audio encoded for Dolby Surround, Pro Logic etc.
|
||||
*
|
||||
* Original author: Steve Davies <steve@daviesfam.org>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* The principle: Make rear channels by extracting anti-phase data
|
||||
from the front channels, delay by 20ms and feed to rear in anti-phase
|
||||
*/
|
||||
|
||||
|
||||
/* SPLITREAR: Define to decode two distinct rear channels - this
|
||||
doesn't work so well in practice because separation in a passive
|
||||
matrix is not high. C (dialogue) to Ls and Rs 14dB or so - so
|
||||
dialogue leaks to the rear. Still - give it a try and send
|
||||
feedback. Comment this define for old behavior of a single
|
||||
surround sent to rear in anti-phase */
|
||||
#define SPLITREAR 1
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "af.h"
|
||||
#include "dsp.h"
|
||||
|
||||
#define L 32 // Length of fir filter
|
||||
#define LD 65536 // Length of delay buffer
|
||||
|
||||
// 32 Tap fir filter loop unrolled
|
||||
#define FIR(x,w,y) \
|
||||
y = ( w[0] *x[0] +w[1] *x[1] +w[2] *x[2] +w[3] *x[3] \
|
||||
+ w[4] *x[4] +w[5] *x[5] +w[6] *x[6] +w[7] *x[7] \
|
||||
+ w[8] *x[8] +w[9] *x[9] +w[10]*x[10]+w[11]*x[11] \
|
||||
+ w[12]*x[12]+w[13]*x[13]+w[14]*x[14]+w[15]*x[15] \
|
||||
+ w[16]*x[16]+w[17]*x[17]+w[18]*x[18]+w[19]*x[19] \
|
||||
+ w[20]*x[20]+w[21]*x[21]+w[22]*x[22]+w[23]*x[23] \
|
||||
+ w[24]*x[24]+w[25]*x[25]+w[26]*x[26]+w[27]*x[27] \
|
||||
+ w[28]*x[28]+w[29]*x[29]+w[30]*x[30]+w[31]*x[31])
|
||||
|
||||
// Add to circular queue macro + update index
|
||||
#ifdef SPLITREAR
|
||||
#define ADDQUE(qi,rq,lq,r,l)\
|
||||
lq[qi]=lq[qi+L]=(l);\
|
||||
rq[qi]=rq[qi+L]=(r);\
|
||||
qi=(qi-1)&(L-1);
|
||||
#else
|
||||
#define ADDQUE(qi,lq,l)\
|
||||
lq[qi]=lq[qi+L]=(l);\
|
||||
qi=(qi-1)&(L-1);
|
||||
#endif
|
||||
|
||||
// Macro for updating queue index in delay queues
|
||||
#define UPDATEQI(qi) qi=(qi+1)&(LD-1)
|
||||
|
||||
// instance data
|
||||
typedef struct af_surround_s
|
||||
{
|
||||
float lq[2*L]; // Circular queue for filtering left rear channel
|
||||
float rq[2*L]; // Circular queue for filtering right rear channel
|
||||
float w[L]; // FIR filter coefficients for surround sound 7kHz low-pass
|
||||
float* dr; // Delay queue right rear channel
|
||||
float* dl; // Delay queue left rear channel
|
||||
float d; // Delay time
|
||||
int i; // Position in circular buffer
|
||||
int wi; // Write index for delay queue
|
||||
int ri; // Read index for delay queue
|
||||
}af_surround_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_surround_t *s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
struct mp_audio *in = arg;
|
||||
float fc;
|
||||
if (!mp_chmap_is_stereo(&in->channels)) {
|
||||
MP_ERR(af, "Only stereo input is supported.\n");
|
||||
return AF_DETACH;
|
||||
}
|
||||
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
mp_audio_copy_config(af->data, in);
|
||||
mp_audio_set_channels_old(af->data, in->nch * 2);
|
||||
|
||||
// Surround filer coefficients
|
||||
fc = 2.0 * 7000.0/(float)af->data->rate;
|
||||
if (-1 == af_filter_design_fir(L, s->w, &fc, LP|HAMMING, 0)){
|
||||
MP_ERR(af, "Unable to design low-pass filter.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
// Free previous delay queues
|
||||
free(s->dl);
|
||||
free(s->dr);
|
||||
// Allocate new delay queues
|
||||
s->dl = calloc(LD,af->data->bps);
|
||||
s->dr = calloc(LD,af->data->bps);
|
||||
if((NULL == s->dl) || (NULL == s->dr))
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
|
||||
// Initialize delay queue index
|
||||
if(AF_OK != af_from_ms(1, &s->d, &s->wi, af->data->rate, 0.0, 1000.0))
|
||||
return AF_ERROR;
|
||||
// printf("%i\n",s->wi);
|
||||
s->ri = 0;
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// The beginnings of an active matrix...
|
||||
static const float steering_matrix[][12] = {
|
||||
// LL RL LR RR LS RS
|
||||
// LLs RLs LRs RRs LC RC
|
||||
{.707, .0, .0, .707, .5, -.5,
|
||||
.5878, -.3928, .3928, -.5878, .5, .5},
|
||||
};
|
||||
|
||||
// Experimental moving average dominance
|
||||
//static int amp_L = 0, amp_R = 0, amp_C = 0, amp_S = 0;
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
struct mp_audio *outframe =
|
||||
mp_audio_pool_get(af->out_pool, &af->fmt_out, data->samples);
|
||||
if (!outframe) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(outframe, data);
|
||||
|
||||
af_surround_t* s = (af_surround_t*)af->priv;
|
||||
const float* m = steering_matrix[0];
|
||||
float* in = data->planes[0]; // Input audio data
|
||||
float* out = outframe->planes[0]; // Output audio data
|
||||
float* end = in + data->samples * data->nch;
|
||||
int i = s->i; // Filter queue index
|
||||
int ri = s->ri; // Read index for delay queue
|
||||
int wi = s->wi; // Write index for delay queue
|
||||
|
||||
while(in < end){
|
||||
/* Dominance:
|
||||
abs(in[0]) abs(in[1]);
|
||||
abs(in[0]+in[1]) abs(in[0]-in[1]);
|
||||
10 * log( abs(in[0]) / (abs(in[1])|1) );
|
||||
10 * log( abs(in[0]+in[1]) / (abs(in[0]-in[1])|1) ); */
|
||||
|
||||
/* About volume balancing...
|
||||
Surround encoding does the following:
|
||||
Lt=L+.707*C+.707*S, Rt=R+.707*C-.707*S
|
||||
So S should be extracted as:
|
||||
(Lt-Rt)
|
||||
But we are splitting the S to two output channels, so we
|
||||
must take 3dB off as we split it:
|
||||
Ls=Rs=.707*(Lt-Rt)
|
||||
Trouble is, Lt could be +1, Rt -1, so possibility that S will
|
||||
overflow. So to avoid that, we cut L/R by 3dB (*.707), and S by
|
||||
6dB (/2). This keeps the overall balance, but guarantees no
|
||||
overflow. */
|
||||
|
||||
// Output front left and right
|
||||
out[0] = m[0]*in[0] + m[1]*in[1];
|
||||
out[1] = m[2]*in[0] + m[3]*in[1];
|
||||
|
||||
// Low-pass output @ 7kHz
|
||||
FIR((&s->lq[i]), s->w, s->dl[wi]);
|
||||
|
||||
// Delay output by d ms
|
||||
out[2] = s->dl[ri];
|
||||
|
||||
#ifdef SPLITREAR
|
||||
// Low-pass output @ 7kHz
|
||||
FIR((&s->rq[i]), s->w, s->dr[wi]);
|
||||
|
||||
// Delay output by d ms
|
||||
out[3] = s->dr[ri];
|
||||
#else
|
||||
out[3] = -out[2];
|
||||
#endif
|
||||
|
||||
// Update delay queues indexes
|
||||
UPDATEQI(ri);
|
||||
UPDATEQI(wi);
|
||||
|
||||
// Calculate and save surround in circular queue
|
||||
#ifdef SPLITREAR
|
||||
ADDQUE(i, s->rq, s->lq, m[6]*in[0]+m[7]*in[1], m[8]*in[0]+m[9]*in[1]);
|
||||
#else
|
||||
ADDQUE(i, s->lq, m[4]*in[0]+m[5]*in[1]);
|
||||
#endif
|
||||
|
||||
// Next sample...
|
||||
in = &in[data->nch];
|
||||
out = &out[af->data->nch];
|
||||
}
|
||||
|
||||
// Save indexes
|
||||
s->i = i; s->ri = ri; s->wi = wi;
|
||||
|
||||
talloc_free(data);
|
||||
af_add_output_frame(af, outframe);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = filter_frame;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_surround_t
|
||||
const struct af_info af_info_surround =
|
||||
{
|
||||
.info = "Surround decoder filter",
|
||||
.name = "surround",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_surround_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOATRANGE("d", d, 0, 0, 1000, OPTDEF_FLOAT(20.0)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "af.h"
|
||||
|
||||
typedef struct af_sweep_s{
|
||||
double x;
|
||||
double delta;
|
||||
}af_sweept;
|
||||
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
struct mp_audio *data= (struct mp_audio*)arg;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
mp_audio_copy_config(af->data, data);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
|
||||
return af_test_output(af, data);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (!data)
|
||||
return 0;
|
||||
if (af_make_writeable(af, data) < 0) {
|
||||
talloc_free(data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
af_sweept *s = af->priv;
|
||||
int i, j;
|
||||
int16_t *in = (int16_t*)data->planes[0];
|
||||
int chans = data->nch;
|
||||
int in_len = data->samples;
|
||||
|
||||
for(i=0; i<in_len; i++){
|
||||
for(j=0; j<chans; j++)
|
||||
in[i*chans+j]= 32000*sin(s->x*s->x);
|
||||
s->x += s->delta;
|
||||
if(2*s->x*s->delta >= 3.141592) s->x=0;
|
||||
}
|
||||
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = filter_frame;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_sweept
|
||||
const struct af_info af_info_sweep = {
|
||||
.info = "sine sweep",
|
||||
.name = "sweep",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_sweept),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_DOUBLE("delta", delta, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -30,18 +30,28 @@
|
||||
#include "demux/demux.h"
|
||||
|
||||
struct priv {
|
||||
float vol; // User-specified non-linear volume
|
||||
float level; // User-specified gain level for each channel
|
||||
float rgain; // Replaygain level
|
||||
int rgain_track; // Enable/disable track based replaygain
|
||||
int rgain_album; // Enable/disable album based replaygain
|
||||
float rgain_preamp; // Set replaygain pre-amplification
|
||||
int rgain_clip; // Enable/disable clipping prevention
|
||||
float replaygain_fallback;
|
||||
int soft; // Enable/disable soft clipping
|
||||
int fast; // Use fix-point volume control
|
||||
int detach; // Detach if gain volume is neutral
|
||||
float cfg_volume;
|
||||
};
|
||||
|
||||
// Convert to gain value from dB. input <= -200dB will become 0 gain.
|
||||
static float from_dB(float in, float k, float mi, float ma)
|
||||
{
|
||||
if (in <= -200)
|
||||
return 0.0;
|
||||
return pow(10.0, MPCLAMP(in, mi, ma) / k);
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
@@ -58,7 +68,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
if (AF_FORMAT_IS_PLANAR(in->format))
|
||||
if (af_fmt_is_planar(in->format))
|
||||
mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
|
||||
s->rgain = 1.0;
|
||||
if ((s->rgain_track || s->rgain_album) && af->replaygain_data) {
|
||||
@@ -73,7 +83,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
}
|
||||
|
||||
gain += s->rgain_preamp;
|
||||
af_from_dB(1, &gain, &s->rgain, 20.0, -200.0, 60.0);
|
||||
s->rgain = from_dB(gain, 20.0, -200.0, 60.0);
|
||||
|
||||
MP_VERBOSE(af, "Applying replay-gain: %f\n", s->rgain);
|
||||
|
||||
@@ -81,16 +91,21 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
s->rgain = MPMIN(s->rgain, 1.0 / peak);
|
||||
MP_VERBOSE(af, "...with clipping prevention: %f\n", s->rgain);
|
||||
}
|
||||
} else if (s->replaygain_fallback) {
|
||||
s->rgain = from_dB(s->replaygain_fallback, 20.0, -200.0, 60.0);
|
||||
MP_VERBOSE(af, "Applying fallback gain: %f\n", s->rgain);
|
||||
}
|
||||
if (s->detach && fabs(s->level * s->rgain - 1.0) < 0.00001)
|
||||
return AF_DETACH;
|
||||
return af_test_output(af, in);
|
||||
}
|
||||
case AF_CONTROL_SET_VOLUME:
|
||||
s->level = *(float *)arg;
|
||||
s->vol = *(float *)arg;
|
||||
s->level = pow(s->vol, 3);
|
||||
MP_VERBOSE(af, "volume gain: %f\n", s->level);
|
||||
return AF_OK;
|
||||
case AF_CONTROL_GET_VOLUME:
|
||||
*(float *)arg = s->level;
|
||||
*(float *)arg = s->vol;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
@@ -143,7 +158,7 @@ static int af_open(struct af_instance *af)
|
||||
struct priv *s = af->priv;
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
af_from_dB(1, &s->cfg_volume, &s->level, 20.0, -200.0, 60.0);
|
||||
s->level = from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
@@ -153,7 +168,6 @@ static int af_open(struct af_instance *af)
|
||||
const struct af_info af_info_volume = {
|
||||
.info = "Volume control audio filter",
|
||||
.name = "volume",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
@@ -162,6 +176,7 @@ const struct af_info af_info_volume = {
|
||||
OPT_FLAG("replaygain-album", rgain_album, 0),
|
||||
OPT_FLOATRANGE("replaygain-preamp", rgain_preamp, 0, -15, 15),
|
||||
OPT_FLAG("replaygain-clip", rgain_clip, 0),
|
||||
OPT_FLOATRANGE("replaygain-fallback", replaygain_fallback, 0, -200, 60),
|
||||
OPT_FLAG("softclip", soft, 0),
|
||||
OPT_FLAG("s16", fast, 0),
|
||||
OPT_FLAG("detach", detach, 0),
|
||||
|
||||
@@ -1,359 +0,0 @@
|
||||
/*
|
||||
* design and implementation of different types of digital filters
|
||||
*
|
||||
* Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "dsp.h"
|
||||
|
||||
/******************************************************************************
|
||||
* FIR filter implementations
|
||||
******************************************************************************/
|
||||
|
||||
/* C implementation of FIR filter y=w*x
|
||||
|
||||
n number of filter taps, where mod(n,4)==0
|
||||
w filter taps
|
||||
x input signal must be a circular buffer which is indexed backwards
|
||||
*/
|
||||
inline FLOAT_TYPE af_filter_fir(register unsigned int n, const FLOAT_TYPE* w,
|
||||
const FLOAT_TYPE* x)
|
||||
{
|
||||
register FLOAT_TYPE y; // Output
|
||||
y = 0.0;
|
||||
do{
|
||||
n--;
|
||||
y+=w[n]*x[n];
|
||||
}while(n != 0);
|
||||
return y;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* FIR filter design
|
||||
******************************************************************************/
|
||||
|
||||
/* Design FIR filter using the Window method
|
||||
|
||||
n filter length must be odd for HP and BS filters
|
||||
w buffer for the filter taps (must be n long)
|
||||
fc cutoff frequencies (1 for LP and HP, 2 for BP and BS)
|
||||
0 < fc < 1 where 1 <=> Fs/2
|
||||
flags window and filter type as defined in filter.h
|
||||
variables are ored together: i.e. LP|HAMMING will give a
|
||||
low pass filter designed using a hamming window
|
||||
opt beta constant used only when designing using kaiser windows
|
||||
|
||||
returns 0 if OK, -1 if fail
|
||||
*/
|
||||
int af_filter_design_fir(unsigned int n, FLOAT_TYPE* w, const FLOAT_TYPE* fc,
|
||||
unsigned int flags, FLOAT_TYPE opt)
|
||||
{
|
||||
unsigned int o = n & 1; // Indicator for odd filter length
|
||||
unsigned int end = ((n + 1) >> 1) - o; // Loop end
|
||||
unsigned int i; // Loop index
|
||||
|
||||
FLOAT_TYPE k1 = 2 * M_PI; // 2*pi*fc1
|
||||
FLOAT_TYPE k2 = 0.5 * (FLOAT_TYPE)(1 - o);// Constant used if the filter has even length
|
||||
FLOAT_TYPE k3; // 2*pi*fc2 Constant used in BP and BS design
|
||||
FLOAT_TYPE g = 0.0; // Gain
|
||||
FLOAT_TYPE t1,t2,t3; // Temporary variables
|
||||
FLOAT_TYPE fc1,fc2; // Cutoff frequencies
|
||||
|
||||
// Sanity check
|
||||
if(!w || (n == 0)) return -1;
|
||||
|
||||
// Get window coefficients
|
||||
switch(flags & WINDOW_MASK){
|
||||
case(BOXCAR):
|
||||
af_window_boxcar(n,w); break;
|
||||
case(TRIANG):
|
||||
af_window_triang(n,w); break;
|
||||
case(HAMMING):
|
||||
af_window_hamming(n,w); break;
|
||||
case(HANNING):
|
||||
af_window_hanning(n,w); break;
|
||||
case(BLACKMAN):
|
||||
af_window_blackman(n,w); break;
|
||||
case(FLATTOP):
|
||||
af_window_flattop(n,w); break;
|
||||
case(KAISER):
|
||||
af_window_kaiser(n,w,opt); break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(flags & (LP | HP)){
|
||||
fc1=*fc;
|
||||
// Cutoff frequency must be < 0.5 where 0.5 <=> Fs/2
|
||||
fc1 = ((fc1 <= 1.0) && (fc1 > 0.0)) ? fc1/2 : 0.25;
|
||||
k1 *= fc1;
|
||||
|
||||
if(flags & LP){ // Low pass filter
|
||||
|
||||
// If the filter length is odd, there is one point which is exactly
|
||||
// in the middle. The value at this point is 2*fCutoff*sin(x)/x,
|
||||
// where x is zero. To make sure nothing strange happens, we set this
|
||||
// value separately.
|
||||
if (o){
|
||||
w[end] = fc1 * w[end] * 2.0;
|
||||
g=w[end];
|
||||
}
|
||||
|
||||
// Create filter
|
||||
for (i=0 ; i<end ; i++){
|
||||
t1 = (FLOAT_TYPE)(i+1) - k2;
|
||||
w[end-i-1] = w[n-end+i] = w[end-i-1] * sin(k1 * t1)/(M_PI * t1); // Sinc
|
||||
g += 2*w[end-i-1]; // Total gain in filter
|
||||
}
|
||||
}
|
||||
else{ // High pass filter
|
||||
if (!o) // High pass filters must have odd length
|
||||
return -1;
|
||||
w[end] = 1.0 - (fc1 * w[end] * 2.0);
|
||||
g= w[end];
|
||||
|
||||
// Create filter
|
||||
for (i=0 ; i<end ; i++){
|
||||
t1 = (FLOAT_TYPE)(i+1);
|
||||
w[end-i-1] = w[n-end+i] = -1 * w[end-i-1] * sin(k1 * t1)/(M_PI * t1); // Sinc
|
||||
g += ((i&1) ? (2*w[end-i-1]) : (-2*w[end-i-1])); // Total gain in filter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(flags & (BP | BS)){
|
||||
fc1=fc[0];
|
||||
fc2=fc[1];
|
||||
// Cutoff frequencies must be < 1.0 where 1.0 <=> Fs/2
|
||||
fc1 = ((fc1 <= 1.0) && (fc1 > 0.0)) ? fc1/2 : 0.25;
|
||||
fc2 = ((fc2 <= 1.0) && (fc2 > 0.0)) ? fc2/2 : 0.25;
|
||||
k3 = k1 * fc2; // 2*pi*fc2
|
||||
k1 *= fc1; // 2*pi*fc1
|
||||
|
||||
if(flags & BP){ // Band pass
|
||||
// Calculate center tap
|
||||
if (o){
|
||||
g=w[end]*(fc1+fc2);
|
||||
w[end] = (fc2 - fc1) * w[end] * 2.0;
|
||||
}
|
||||
|
||||
// Create filter
|
||||
for (i=0 ; i<end ; i++){
|
||||
t1 = (FLOAT_TYPE)(i+1) - k2;
|
||||
t2 = sin(k3 * t1)/(M_PI * t1); // Sinc fc2
|
||||
t3 = sin(k1 * t1)/(M_PI * t1); // Sinc fc1
|
||||
g += w[end-i-1] * (t3 + t2); // Total gain in filter
|
||||
w[end-i-1] = w[n-end+i] = w[end-i-1] * (t2 - t3);
|
||||
}
|
||||
}
|
||||
else{ // Band stop
|
||||
if (!o) // Band stop filters must have odd length
|
||||
return -1;
|
||||
w[end] = 1.0 - (fc2 - fc1) * w[end] * 2.0;
|
||||
g= w[end];
|
||||
|
||||
// Create filter
|
||||
for (i=0 ; i<end ; i++){
|
||||
t1 = (FLOAT_TYPE)(i+1);
|
||||
t2 = sin(k1 * t1)/(M_PI * t1); // Sinc fc1
|
||||
t3 = sin(k3 * t1)/(M_PI * t1); // Sinc fc2
|
||||
w[end-i-1] = w[n-end+i] = w[end-i-1] * (t2 - t3);
|
||||
g += 2*w[end-i-1]; // Total gain in filter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize gain
|
||||
g=1/g;
|
||||
for (i=0; i<n; i++)
|
||||
w[i] *= g;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/******************************************************************************
|
||||
* IIR filter design
|
||||
******************************************************************************/
|
||||
|
||||
/* Helper functions for the bilinear transform */
|
||||
|
||||
/* Pre-warp the coefficients of a numerator or denominator.
|
||||
Note that a0 is assumed to be 1, so there is no wrapping
|
||||
of it.
|
||||
*/
|
||||
static void af_filter_prewarp(FLOAT_TYPE* a, FLOAT_TYPE fc, FLOAT_TYPE fs)
|
||||
{
|
||||
FLOAT_TYPE wp;
|
||||
wp = 2.0 * fs * tan(M_PI * fc / fs);
|
||||
a[2] = a[2]/(wp * wp);
|
||||
a[1] = a[1]/wp;
|
||||
}
|
||||
|
||||
/* Transform the numerator and denominator coefficients of s-domain
|
||||
biquad section into corresponding z-domain coefficients.
|
||||
|
||||
The transfer function for z-domain is:
|
||||
|
||||
1 + alpha1 * z^(-1) + alpha2 * z^(-2)
|
||||
H(z) = -------------------------------------
|
||||
1 + beta1 * z^(-1) + beta2 * z^(-2)
|
||||
|
||||
Store the 4 IIR coefficients in array pointed by coef in following
|
||||
order:
|
||||
beta1, beta2 (denominator)
|
||||
alpha1, alpha2 (numerator)
|
||||
|
||||
Arguments:
|
||||
a - s-domain numerator coefficients
|
||||
b - s-domain denominator coefficients
|
||||
k - filter gain factor. Initially set to 1 and modified by each
|
||||
biquad section in such a way, as to make it the
|
||||
coefficient by which to multiply the overall filter gain
|
||||
in order to achieve a desired overall filter gain,
|
||||
specified in initial value of k.
|
||||
fs - sampling rate (Hz)
|
||||
coef - array of z-domain coefficients to be filled in.
|
||||
|
||||
Return: On return, set coef z-domain coefficients and k to the gain
|
||||
required to maintain overall gain = 1.0;
|
||||
*/
|
||||
static void af_filter_bilinear(const FLOAT_TYPE* a, const FLOAT_TYPE* b, FLOAT_TYPE* k,
|
||||
FLOAT_TYPE fs, FLOAT_TYPE *coef)
|
||||
{
|
||||
FLOAT_TYPE ad, bd;
|
||||
|
||||
/* alpha (Numerator in s-domain) */
|
||||
ad = 4. * a[2] * fs * fs + 2. * a[1] * fs + a[0];
|
||||
/* beta (Denominator in s-domain) */
|
||||
bd = 4. * b[2] * fs * fs + 2. * b[1] * fs + b[0];
|
||||
|
||||
/* Update gain constant for this section */
|
||||
*k *= ad/bd;
|
||||
|
||||
/* Denominator */
|
||||
*coef++ = (2. * b[0] - 8. * b[2] * fs * fs)/bd; /* beta1 */
|
||||
*coef++ = (4. * b[2] * fs * fs - 2. * b[1] * fs + b[0])/bd; /* beta2 */
|
||||
|
||||
/* Numerator */
|
||||
*coef++ = (2. * a[0] - 8. * a[2] * fs * fs)/ad; /* alpha1 */
|
||||
*coef = (4. * a[2] * fs * fs - 2. * a[1] * fs + a[0])/ad; /* alpha2 */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* IIR filter design using bilinear transform and prewarp. Transforms
|
||||
2nd order s domain analog filter into a digital IIR biquad link. To
|
||||
create a filter fill in a, b, Q and fs and make space for coef and k.
|
||||
|
||||
|
||||
Example Butterworth design:
|
||||
|
||||
Below are Butterworth polynomials, arranged as a series of 2nd
|
||||
order sections:
|
||||
|
||||
Note: n is filter order.
|
||||
|
||||
n Polynomials
|
||||
-------------------------------------------------------------------
|
||||
2 s^2 + 1.4142s + 1
|
||||
4 (s^2 + 0.765367s + 1) * (s^2 + 1.847759s + 1)
|
||||
6 (s^2 + 0.5176387s + 1) * (s^2 + 1.414214 + 1) * (s^2 + 1.931852s + 1)
|
||||
|
||||
For n=4 we have following equation for the filter transfer function:
|
||||
1 1
|
||||
T(s) = --------------------------- * ----------------------------
|
||||
s^2 + (1/Q) * 0.765367s + 1 s^2 + (1/Q) * 1.847759s + 1
|
||||
|
||||
The filter consists of two 2nd order sections since highest s power
|
||||
is 2. Now we can take the coefficients, or the numbers by which s
|
||||
is multiplied and plug them into a standard formula to be used by
|
||||
bilinear transform.
|
||||
|
||||
Our standard form for each 2nd order section is:
|
||||
|
||||
a2 * s^2 + a1 * s + a0
|
||||
H(s) = ----------------------
|
||||
b2 * s^2 + b1 * s + b0
|
||||
|
||||
Note that Butterworth numerator is 1 for all filter sections, which
|
||||
means s^2 = 0 and s^1 = 0
|
||||
|
||||
Let's convert standard Butterworth polynomials into this form:
|
||||
|
||||
0 + 0 + 1 0 + 0 + 1
|
||||
--------------------------- * --------------------------
|
||||
1 + ((1/Q) * 0.765367) + 1 1 + ((1/Q) * 1.847759) + 1
|
||||
|
||||
Section 1:
|
||||
a2 = 0; a1 = 0; a0 = 1;
|
||||
b2 = 1; b1 = 0.765367; b0 = 1;
|
||||
|
||||
Section 2:
|
||||
a2 = 0; a1 = 0; a0 = 1;
|
||||
b2 = 1; b1 = 1.847759; b0 = 1;
|
||||
|
||||
Q is filter quality factor or resonance, in the range of 1 to
|
||||
1000. The overall filter Q is a product of all 2nd order stages.
|
||||
For example, the 6th order filter (3 stages, or biquads) with
|
||||
individual Q of 2 will have filter Q = 2 * 2 * 2 = 8.
|
||||
|
||||
|
||||
Arguments:
|
||||
a - s-domain numerator coefficients, a[1] is always assumed to be 1.0
|
||||
b - s-domain denominator coefficients
|
||||
Q - Q value for the filter
|
||||
k - filter gain factor. Initially set to 1 and modified by each
|
||||
biquad section in such a way, as to make it the
|
||||
coefficient by which to multiply the overall filter gain
|
||||
in order to achieve a desired overall filter gain,
|
||||
specified in initial value of k.
|
||||
fs - sampling rate (Hz)
|
||||
coef - array of z-domain coefficients to be filled in.
|
||||
|
||||
Note: Upon return from each call, the k argument will be set to a
|
||||
value, by which to multiply our actual signal in order for the gain
|
||||
to be one. On second call to szxform() we provide k that was
|
||||
changed by the previous section. During actual audio filtering
|
||||
k can be used for gain compensation.
|
||||
|
||||
return -1 if fail 0 if success.
|
||||
*/
|
||||
int af_filter_szxform(const FLOAT_TYPE* a, const FLOAT_TYPE* b, FLOAT_TYPE Q, FLOAT_TYPE fc,
|
||||
FLOAT_TYPE fs, FLOAT_TYPE *k, FLOAT_TYPE *coef)
|
||||
{
|
||||
FLOAT_TYPE at[3];
|
||||
FLOAT_TYPE bt[3];
|
||||
|
||||
if(!a || !b || !k || !coef || (Q>1000.0 || Q< 1.0))
|
||||
return -1;
|
||||
|
||||
memcpy(at,a,3*sizeof(FLOAT_TYPE));
|
||||
memcpy(bt,b,3*sizeof(FLOAT_TYPE));
|
||||
|
||||
bt[1]/=Q;
|
||||
|
||||
/* Calculate a and b and overwrite the original values */
|
||||
af_filter_prewarp(at, fc, fs);
|
||||
af_filter_prewarp(bt, fc, fs);
|
||||
/* Execute bilinear transform */
|
||||
af_filter_bilinear(at, bt, k, fs, coef);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#if !defined MPLAYER_DSP_H
|
||||
# error Never use filter.h directly; include dsp.h instead.
|
||||
#endif
|
||||
|
||||
#ifndef MPLAYER_FILTER_H
|
||||
#define MPLAYER_FILTER_H
|
||||
|
||||
|
||||
// Design and implementation of different types of digital filters
|
||||
|
||||
|
||||
// Flags used for filter design
|
||||
|
||||
// Filter characteristics
|
||||
#define LP 0x00010000 // Low pass
|
||||
#define HP 0x00020000 // High pass
|
||||
#define BP 0x00040000 // Band pass
|
||||
#define BS 0x00080000 // Band stop
|
||||
#define TYPE_MASK 0x000F0000
|
||||
|
||||
// Window types
|
||||
#define BOXCAR 0x00000001
|
||||
#define TRIANG 0x00000002
|
||||
#define HAMMING 0x00000004
|
||||
#define HANNING 0x00000008
|
||||
#define BLACKMAN 0x00000010
|
||||
#define FLATTOP 0x00000011
|
||||
#define KAISER 0x00000012
|
||||
#define WINDOW_MASK 0x0000001F
|
||||
|
||||
// Parallel filter design
|
||||
#define FWD 0x00000001 // Forward indexing of polyphase filter
|
||||
#define REW 0x00000002 // Reverse indexing of polyphase filter
|
||||
#define ODD 0x00000010 // Make filter HP
|
||||
|
||||
// Exported functions
|
||||
FLOAT_TYPE af_filter_fir(unsigned int n, const FLOAT_TYPE* w, const FLOAT_TYPE* x);
|
||||
|
||||
int af_filter_design_fir(unsigned int n, FLOAT_TYPE* w, const FLOAT_TYPE* fc,
|
||||
unsigned int flags, FLOAT_TYPE opt);
|
||||
|
||||
int af_filter_szxform(const FLOAT_TYPE* a, const FLOAT_TYPE* b, FLOAT_TYPE Q,
|
||||
FLOAT_TYPE fc, FLOAT_TYPE fs, FLOAT_TYPE *k,
|
||||
FLOAT_TYPE *coef);
|
||||
|
||||
/* Add new data to circular queue designed to be used with a FIR
|
||||
filter. xq is the circular queue, in pointing at the new sample, xi
|
||||
current index for xq and n the length of the filter. xq must be n*2
|
||||
long.
|
||||
*/
|
||||
#define af_filter_updateq(n,xi,xq,in)\
|
||||
xq[xi]=(xq)[(xi)+(n)]=*(in);\
|
||||
xi=(++(xi))&((n)-1);
|
||||
|
||||
#endif /* MPLAYER_FILTER_H */
|
||||
@@ -21,24 +21,6 @@
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
/* Convert to gain value from dB. Returns AF_OK if of and AF_ERROR if
|
||||
* fail. input <= -200dB will become 0 gain. */
|
||||
int af_from_dB(int n, float* in, float* out, float k, float mi, float ma)
|
||||
{
|
||||
int i = 0;
|
||||
// Sanity check
|
||||
if(!in || !out)
|
||||
return AF_ERROR;
|
||||
|
||||
for(i=0;i<n;i++){
|
||||
if(in[i]<=-200)
|
||||
out[i]=0.0;
|
||||
else
|
||||
out[i]=pow(10.0,MPCLAMP(in[i],mi,ma)/k);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* Convert from ms to sample time */
|
||||
int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
|
||||
{
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* Calculates a number of window functions. The following window
|
||||
functions are currently implemented: Boxcar, Triang, Hanning,
|
||||
Hamming, Blackman, Flattop and Kaiser. In the function call n is
|
||||
the number of filter taps and w the buffer in which the filter
|
||||
coefficients will be stored.
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include "dsp.h"
|
||||
|
||||
/*
|
||||
// Boxcar
|
||||
//
|
||||
// n window length
|
||||
// w buffer for the window parameters
|
||||
*/
|
||||
void af_window_boxcar(int n, FLOAT_TYPE* w)
|
||||
{
|
||||
int i;
|
||||
// Calculate window coefficients
|
||||
for (i=0 ; i<n ; i++)
|
||||
w[i] = 1.0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
// Triang a.k.a Bartlett
|
||||
//
|
||||
// | (N-1)|
|
||||
// 2 * |k - -----|
|
||||
// | 2 |
|
||||
// w = 1.0 - ---------------
|
||||
// N+1
|
||||
// n window length
|
||||
// w buffer for the window parameters
|
||||
*/
|
||||
void af_window_triang(int n, FLOAT_TYPE* w)
|
||||
{
|
||||
FLOAT_TYPE k1 = (FLOAT_TYPE)(n & 1);
|
||||
FLOAT_TYPE k2 = 1/((FLOAT_TYPE)n + k1);
|
||||
int end = (n + 1) >> 1;
|
||||
int i;
|
||||
|
||||
// Calculate window coefficients
|
||||
for (i=0 ; i<end ; i++)
|
||||
w[i] = w[n-i-1] = (2.0*((FLOAT_TYPE)(i+1))-(1.0-k1))*k2;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
// Hanning
|
||||
// 2*pi*k
|
||||
// w = 0.5 - 0.5*cos(------), where 0 < k <= N
|
||||
// N+1
|
||||
// n window length
|
||||
// w buffer for the window parameters
|
||||
*/
|
||||
void af_window_hanning(int n, FLOAT_TYPE* w)
|
||||
{
|
||||
int i;
|
||||
FLOAT_TYPE k = 2*M_PI/((FLOAT_TYPE)(n+1)); // 2*pi/(N+1)
|
||||
|
||||
// Calculate window coefficients
|
||||
for (i=0; i<n; i++)
|
||||
*w++ = 0.5*(1.0 - cos(k*(FLOAT_TYPE)(i+1)));
|
||||
}
|
||||
|
||||
/*
|
||||
// Hamming
|
||||
// 2*pi*k
|
||||
// w(k) = 0.54 - 0.46*cos(------), where 0 <= k < N
|
||||
// N-1
|
||||
//
|
||||
// n window length
|
||||
// w buffer for the window parameters
|
||||
*/
|
||||
void af_window_hamming(int n,FLOAT_TYPE* w)
|
||||
{
|
||||
int i;
|
||||
FLOAT_TYPE k = 2*M_PI/((FLOAT_TYPE)(n-1)); // 2*pi/(N-1)
|
||||
|
||||
// Calculate window coefficients
|
||||
for (i=0; i<n; i++)
|
||||
*w++ = 0.54 - 0.46*cos(k*(FLOAT_TYPE)i);
|
||||
}
|
||||
|
||||
/*
|
||||
// Blackman
|
||||
// 2*pi*k 4*pi*k
|
||||
// w(k) = 0.42 - 0.5*cos(------) + 0.08*cos(------), where 0 <= k < N
|
||||
// N-1 N-1
|
||||
//
|
||||
// n window length
|
||||
// w buffer for the window parameters
|
||||
*/
|
||||
void af_window_blackman(int n,FLOAT_TYPE* w)
|
||||
{
|
||||
int i;
|
||||
FLOAT_TYPE k1 = 2*M_PI/((FLOAT_TYPE)(n-1)); // 2*pi/(N-1)
|
||||
FLOAT_TYPE k2 = 2*k1; // 4*pi/(N-1)
|
||||
|
||||
// Calculate window coefficients
|
||||
for (i=0; i<n; i++)
|
||||
*w++ = 0.42 - 0.50*cos(k1*(FLOAT_TYPE)i) + 0.08*cos(k2*(FLOAT_TYPE)i);
|
||||
}
|
||||
|
||||
/*
|
||||
// Flattop
|
||||
// 2*pi*k 4*pi*k
|
||||
// w(k) = 0.2810638602 - 0.5208971735*cos(------) + 0.1980389663*cos(------), where 0 <= k < N
|
||||
// N-1 N-1
|
||||
//
|
||||
// n window length
|
||||
// w buffer for the window parameters
|
||||
*/
|
||||
void af_window_flattop(int n,FLOAT_TYPE* w)
|
||||
{
|
||||
int i;
|
||||
FLOAT_TYPE k1 = 2*M_PI/((FLOAT_TYPE)(n-1)); // 2*pi/(N-1)
|
||||
FLOAT_TYPE k2 = 2*k1; // 4*pi/(N-1)
|
||||
|
||||
// Calculate window coefficients
|
||||
for (i=0; i<n; i++)
|
||||
*w++ = 0.2810638602 - 0.5208971735*cos(k1*(FLOAT_TYPE)i)
|
||||
+ 0.1980389663*cos(k2*(FLOAT_TYPE)i);
|
||||
}
|
||||
|
||||
/* Computes the 0th order modified Bessel function of the first kind.
|
||||
// (Needed to compute Kaiser window)
|
||||
//
|
||||
// y = sum( (x/(2*n))^2 )
|
||||
// n
|
||||
*/
|
||||
#define BIZ_EPSILON 1E-21 // Max error acceptable
|
||||
|
||||
static FLOAT_TYPE besselizero(FLOAT_TYPE x)
|
||||
{
|
||||
FLOAT_TYPE temp;
|
||||
FLOAT_TYPE sum = 1.0;
|
||||
FLOAT_TYPE u = 1.0;
|
||||
FLOAT_TYPE halfx = x/2.0;
|
||||
int n = 1;
|
||||
|
||||
do {
|
||||
temp = halfx/(FLOAT_TYPE)n;
|
||||
u *=temp * temp;
|
||||
sum += u;
|
||||
n++;
|
||||
} while (u >= BIZ_EPSILON * sum);
|
||||
return sum;
|
||||
}
|
||||
|
||||
/*
|
||||
// Kaiser
|
||||
//
|
||||
// n window length
|
||||
// w buffer for the window parameters
|
||||
// b beta parameter of Kaiser window, Beta >= 1
|
||||
//
|
||||
// Beta trades the rejection of the low pass filter against the
|
||||
// transition width from passband to stop band. Larger Beta means a
|
||||
// slower transition and greater stop band rejection. See Rabiner and
|
||||
// Gold (Theory and Application of DSP) under Kaiser windows for more
|
||||
// about Beta. The following table from Rabiner and Gold gives some
|
||||
// feel for the effect of Beta:
|
||||
//
|
||||
// All ripples in dB, width of transition band = D*N where N = window
|
||||
// length
|
||||
//
|
||||
// BETA D PB RIP SB RIP
|
||||
// 2.120 1.50 +-0.27 -30
|
||||
// 3.384 2.23 0.0864 -40
|
||||
// 4.538 2.93 0.0274 -50
|
||||
// 5.658 3.62 0.00868 -60
|
||||
// 6.764 4.32 0.00275 -70
|
||||
// 7.865 5.0 0.000868 -80
|
||||
// 8.960 5.7 0.000275 -90
|
||||
// 10.056 6.4 0.000087 -100
|
||||
*/
|
||||
void af_window_kaiser(int n, FLOAT_TYPE* w, FLOAT_TYPE b)
|
||||
{
|
||||
FLOAT_TYPE tmp;
|
||||
FLOAT_TYPE k1 = 1.0/besselizero(b);
|
||||
int k2 = 1 - (n & 1);
|
||||
int end = (n + 1) >> 1;
|
||||
int i;
|
||||
|
||||
// Calculate window coefficients
|
||||
for (i=0 ; i<end ; i++){
|
||||
tmp = (FLOAT_TYPE)(2*i + k2) / ((FLOAT_TYPE)n - 1.0);
|
||||
w[end-(1&(!k2))+i] = w[end-1-i] = k1 * besselizero(b*sqrt(1.0 - tmp*tmp));
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2001 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* Calculates a number of window functions. The following window
|
||||
functions are currently implemented: Boxcar, Triang, Hanning,
|
||||
Hamming, Blackman, Flattop and Kaiser. In the function call n is
|
||||
the number of filter taps and w the buffer in which the filter
|
||||
coefficients will be stored.
|
||||
*/
|
||||
|
||||
#if !defined MPLAYER_DSP_H
|
||||
# error Never use window.h directly; include dsp.h instead.
|
||||
#endif
|
||||
|
||||
#ifndef MPLAYER_WINDOW_H
|
||||
#define MPLAYER_WINDOW_H
|
||||
|
||||
void af_window_boxcar(int n, FLOAT_TYPE* w);
|
||||
void af_window_triang(int n, FLOAT_TYPE* w);
|
||||
void af_window_hanning(int n, FLOAT_TYPE* w);
|
||||
void af_window_hamming(int n, FLOAT_TYPE* w);
|
||||
void af_window_blackman(int n, FLOAT_TYPE* w);
|
||||
void af_window_flattop(int n, FLOAT_TYPE* w);
|
||||
void af_window_kaiser(int n, FLOAT_TYPE* w, FLOAT_TYPE b);
|
||||
|
||||
#endif /* MPLAYER_WINDOW_H */
|
||||
+131
-102
@@ -27,42 +27,68 @@
|
||||
#include "common/common.h"
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
int af_fmt2bps(int format)
|
||||
// number of bytes per sample, 0 if invalid/unknown
|
||||
int af_fmt_to_bytes(int format)
|
||||
{
|
||||
switch (format & AF_FORMAT_BITS_MASK) {
|
||||
case AF_FORMAT_8BIT: return 1;
|
||||
case AF_FORMAT_16BIT: return 2;
|
||||
case AF_FORMAT_24BIT: return 3;
|
||||
case AF_FORMAT_32BIT: return 4;
|
||||
case AF_FORMAT_64BIT: return 8;
|
||||
switch (af_fmt_from_planar(format)) {
|
||||
case AF_FORMAT_U8: return 1;
|
||||
case AF_FORMAT_S16: return 2;
|
||||
case AF_FORMAT_S24: return 3;
|
||||
case AF_FORMAT_S32: return 4;
|
||||
case AF_FORMAT_FLOAT: return 4;
|
||||
case AF_FORMAT_DOUBLE: return 8;
|
||||
}
|
||||
if (af_fmt_is_spdif(format))
|
||||
return 2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int af_fmt2bits(int format)
|
||||
int af_fmt_change_bytes(int format, int bytes)
|
||||
{
|
||||
return af_fmt2bps(format) * 8;
|
||||
}
|
||||
|
||||
static int bits_to_mask(int bits)
|
||||
{
|
||||
switch (bits) {
|
||||
case 8: return AF_FORMAT_8BIT;
|
||||
case 16: return AF_FORMAT_16BIT;
|
||||
case 24: return AF_FORMAT_24BIT;
|
||||
case 32: return AF_FORMAT_32BIT;
|
||||
case 64: return AF_FORMAT_64BIT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int af_fmt_change_bits(int format, int bits)
|
||||
{
|
||||
if (!af_fmt_is_valid(format))
|
||||
if (!af_fmt_is_valid(format) || !bytes)
|
||||
return 0;
|
||||
int mask = bits_to_mask(bits);
|
||||
format = (format & ~AF_FORMAT_BITS_MASK) | mask;
|
||||
return af_fmt_is_valid(format) ? format : 0;
|
||||
for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
|
||||
if (af_fmt_to_bytes(fmt) == bytes &&
|
||||
af_fmt_is_float(fmt) == af_fmt_is_float(format) &&
|
||||
af_fmt_is_planar(fmt) == af_fmt_is_planar(format) &&
|
||||
af_fmt_is_spdif(fmt) == af_fmt_is_spdif(format))
|
||||
return fmt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// All formats are considered signed, except explicitly unsigned int formats.
|
||||
bool af_fmt_is_unsigned(int format)
|
||||
{
|
||||
return format == AF_FORMAT_U8 || format == AF_FORMAT_U8P;
|
||||
}
|
||||
|
||||
bool af_fmt_is_float(int format)
|
||||
{
|
||||
format = af_fmt_from_planar(format);
|
||||
return format == AF_FORMAT_FLOAT || format == AF_FORMAT_DOUBLE;
|
||||
}
|
||||
|
||||
// true for both unsigned and signed ints
|
||||
bool af_fmt_is_int(int format)
|
||||
{
|
||||
return format && !af_fmt_is_spdif(format) && !af_fmt_is_float(format);
|
||||
}
|
||||
|
||||
// false for interleaved and AF_FORMAT_UNKNOWN
|
||||
bool af_fmt_is_planar(int format)
|
||||
{
|
||||
return format && af_fmt_to_planar(format) == format;
|
||||
}
|
||||
|
||||
bool af_fmt_is_spdif(int format)
|
||||
{
|
||||
return af_format_sample_alignment(format) > 1;
|
||||
}
|
||||
|
||||
bool af_fmt_is_pcm(int format)
|
||||
{
|
||||
return af_fmt_is_valid(format) && !af_fmt_is_spdif(format);
|
||||
}
|
||||
|
||||
static const int planar_formats[][2] = {
|
||||
@@ -99,58 +125,40 @@ int af_fmt_from_planar(int format)
|
||||
return format;
|
||||
}
|
||||
|
||||
const struct af_fmt_entry af_fmtstr_table[] = {
|
||||
{"u8", AF_FORMAT_U8},
|
||||
{"s8", AF_FORMAT_S8},
|
||||
{"u16", AF_FORMAT_U16},
|
||||
{"s16", AF_FORMAT_S16},
|
||||
{"u24", AF_FORMAT_U24},
|
||||
{"s24", AF_FORMAT_S24},
|
||||
{"u32", AF_FORMAT_U32},
|
||||
{"s32", AF_FORMAT_S32},
|
||||
{"float", AF_FORMAT_FLOAT},
|
||||
{"double", AF_FORMAT_DOUBLE},
|
||||
|
||||
{"u8p", AF_FORMAT_U8P},
|
||||
{"s16p", AF_FORMAT_S16P},
|
||||
{"s32p", AF_FORMAT_S32P},
|
||||
{"floatp", AF_FORMAT_FLOATP},
|
||||
{"doublep", AF_FORMAT_DOUBLEP},
|
||||
|
||||
{"spdif-aac", AF_FORMAT_S_AAC},
|
||||
{"spdif-ac3", AF_FORMAT_S_AC3},
|
||||
{"spdif-dts", AF_FORMAT_S_DTS},
|
||||
{"spdif-dtshd", AF_FORMAT_S_DTSHD},
|
||||
{"spdif-eac3", AF_FORMAT_S_EAC3},
|
||||
{"spdif-mp3", AF_FORMAT_S_MP3},
|
||||
{"spdif-truehd",AF_FORMAT_S_TRUEHD},
|
||||
|
||||
{0}
|
||||
};
|
||||
|
||||
bool af_fmt_is_valid(int format)
|
||||
{
|
||||
for (int i = 0; af_fmtstr_table[i].name; i++) {
|
||||
if (af_fmtstr_table[i].format == format)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return format > 0 && format < AF_FORMAT_COUNT;
|
||||
}
|
||||
|
||||
const char *af_fmt_to_str(int format)
|
||||
{
|
||||
for (int i = 0; af_fmtstr_table[i].name; i++) {
|
||||
if (af_fmtstr_table[i].format == format)
|
||||
return af_fmtstr_table[i].name;
|
||||
switch (format) {
|
||||
case AF_FORMAT_U8: return "u8";
|
||||
case AF_FORMAT_S16: return "s16";
|
||||
case AF_FORMAT_S24: return "s24";
|
||||
case AF_FORMAT_S32: return "s32";
|
||||
case AF_FORMAT_FLOAT: return "float";
|
||||
case AF_FORMAT_DOUBLE: return "double";
|
||||
case AF_FORMAT_U8P: return "u8p";
|
||||
case AF_FORMAT_S16P: return "s16p";
|
||||
case AF_FORMAT_S32P: return "s32p";
|
||||
case AF_FORMAT_FLOATP: return "floatp";
|
||||
case AF_FORMAT_DOUBLEP: return "doublep";
|
||||
case AF_FORMAT_S_AAC: return "spdif-aac";
|
||||
case AF_FORMAT_S_AC3: return "spdif-ac3";
|
||||
case AF_FORMAT_S_DTS: return "spdif-dts";
|
||||
case AF_FORMAT_S_DTSHD: return "spdif-dtshd";
|
||||
case AF_FORMAT_S_EAC3: return "spdif-eac3";
|
||||
case AF_FORMAT_S_MP3: return "spdif-mp3";
|
||||
case AF_FORMAT_S_TRUEHD: return "spdif-truehd";
|
||||
}
|
||||
|
||||
return "??";
|
||||
}
|
||||
|
||||
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate)
|
||||
{
|
||||
assert(!AF_FORMAT_IS_PLANAR(format));
|
||||
int bps = af_fmt2bps(format);
|
||||
assert(!af_fmt_is_planar(format));
|
||||
int bps = af_fmt_to_bytes(format);
|
||||
int framelen = channels * bps;
|
||||
int bytes = seconds * bps * samplerate;
|
||||
if (bytes % framelen)
|
||||
@@ -158,64 +166,85 @@ int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int sampler
|
||||
return bytes;
|
||||
}
|
||||
|
||||
int af_str2fmt_short(bstr str)
|
||||
{
|
||||
for (int i = 0; af_fmtstr_table[i].name; i++) {
|
||||
if (!bstrcasecmp0(str, af_fmtstr_table[i].name))
|
||||
return af_fmtstr_table[i].format;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format)
|
||||
{
|
||||
bool us = (format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_US;
|
||||
memset(dst, us ? 0x80 : 0, bytes);
|
||||
memset(dst, af_fmt_is_unsigned(format) ? 0x80 : 0, bytes);
|
||||
}
|
||||
|
||||
#define FMT_DIFF(type, a, b) (((a) & type) - ((b) & type))
|
||||
|
||||
// Returns a "score" that serves as heuristic how lossy or hard a conversion is.
|
||||
// If the formats are equal, 1024 is returned. If they are gravely incompatible
|
||||
// (like s16<->ac3), INT_MIN is returned. If there is implied loss of precision
|
||||
// (like s16->s8), a value <0 is returned.
|
||||
int af_format_conversion_score(int dst_format, int src_format)
|
||||
static int af_format_conversion_score(int dst_format, int src_format)
|
||||
{
|
||||
if (dst_format == AF_FORMAT_UNKNOWN || src_format == AF_FORMAT_UNKNOWN)
|
||||
return INT_MIN;
|
||||
if (dst_format == src_format)
|
||||
return 1024;
|
||||
// Can't be normally converted
|
||||
if (AF_FORMAT_IS_SPECIAL(dst_format) || AF_FORMAT_IS_SPECIAL(src_format))
|
||||
if (!af_fmt_is_pcm(dst_format) || !af_fmt_is_pcm(src_format))
|
||||
return INT_MIN;
|
||||
int score = 1024;
|
||||
if (FMT_DIFF(AF_FORMAT_INTERLEAVING_MASK, dst_format, src_format))
|
||||
if (af_fmt_is_planar(dst_format) != af_fmt_is_planar(src_format))
|
||||
score -= 1; // has to (de-)planarize
|
||||
if (FMT_DIFF(AF_FORMAT_SIGN_MASK, dst_format, src_format))
|
||||
score -= 4; // has to swap sign
|
||||
if (FMT_DIFF(AF_FORMAT_TYPE_MASK, dst_format, src_format)) {
|
||||
int dst_bits = dst_format & AF_FORMAT_BITS_MASK;
|
||||
if ((dst_format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_F) {
|
||||
// For int->float, always prefer 32 bit float.
|
||||
score -= dst_bits == AF_FORMAT_32BIT ? 8 : 0;
|
||||
if (af_fmt_is_float(dst_format) != af_fmt_is_float(src_format)) {
|
||||
int dst_bytes = af_fmt_to_bytes(dst_format);
|
||||
if (af_fmt_is_float(dst_format)) {
|
||||
// For int->float, consider a lower bound on the precision difference.
|
||||
int bytes = (dst_bytes == 4 ? 3 : 6) - af_fmt_to_bytes(src_format);
|
||||
if (bytes >= 0) {
|
||||
score -= 8 * bytes; // excess precision
|
||||
} else {
|
||||
score += 1024 * (bytes - 1); // precision is lost (i.e. s32 -> float)
|
||||
}
|
||||
} else {
|
||||
// For float->int, always prefer highest bit depth int
|
||||
score -= 8 * (AF_FORMAT_64BIT - dst_bits);
|
||||
// float->int is the worst case. Penalize heavily and
|
||||
// prefer highest bit depth int.
|
||||
score -= 1048576 * (8 - dst_bytes);
|
||||
}
|
||||
score -= 512; // penalty for any float <-> int conversion
|
||||
} else {
|
||||
int bits = FMT_DIFF(AF_FORMAT_BITS_MASK, dst_format, src_format);
|
||||
if (bits > 0) {
|
||||
score -= 8 * bits; // has to add padding
|
||||
} else if (bits < 0) {
|
||||
score -= 1024 - 8 * bits; // has to reduce bit depth
|
||||
int bytes = af_fmt_to_bytes(dst_format) - af_fmt_to_bytes(src_format);
|
||||
if (bytes > 0) {
|
||||
score -= 8 * bytes; // has to add padding
|
||||
} else if (bytes < 0) {
|
||||
score += 1024 * (bytes - 1); // has to reduce bit depth
|
||||
}
|
||||
}
|
||||
// Consider this the worst case.
|
||||
if (FMT_DIFF(AF_FORMAT_TYPE_MASK, dst_format, src_format))
|
||||
score -= 2048; // has to convert float<->int
|
||||
return score;
|
||||
}
|
||||
|
||||
struct entry {
|
||||
int fmt;
|
||||
int score;
|
||||
};
|
||||
|
||||
static int cmp_entry(const void *a, const void *b)
|
||||
{
|
||||
#define CMP_INT(a, b) (a > b ? 1 : (a < b ? -1 : 0))
|
||||
return -CMP_INT(((struct entry *)a)->score, ((struct entry *)b)->score);
|
||||
}
|
||||
|
||||
// Return a list of sample format compatible to src_format, sorted by order
|
||||
// of preference. out_formats[0] will be src_format (as long as it's valid),
|
||||
// and the list is terminated with 0 (AF_FORMAT_UNKNOWN).
|
||||
// Keep in mind that this also returns formats with flipped interleaving
|
||||
// (e.g. for s16, it returns [s16, s16p, ...]).
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT])
|
||||
{
|
||||
int num = 0;
|
||||
struct entry e[AF_FORMAT_COUNT];
|
||||
for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
|
||||
int score = af_format_conversion_score(fmt, src_format);
|
||||
if (score > INT_MIN)
|
||||
e[num++] = (struct entry){fmt, score};
|
||||
}
|
||||
qsort(e, num, sizeof(e[0]), cmp_entry);
|
||||
for (int n = 0; n < num; n++)
|
||||
out_formats[n] = e[n].fmt;
|
||||
out_formats[num] = 0;
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
+33
-81
@@ -22,96 +22,50 @@
|
||||
#ifndef MPLAYER_AF_FORMAT_H
|
||||
#define MPLAYER_AF_FORMAT_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "misc/bstr.h"
|
||||
|
||||
// Signed/unsigned
|
||||
#define AF_FORMAT_SI (0<<0) // Signed
|
||||
#define AF_FORMAT_US (1<<0) // Unsigned
|
||||
#define AF_FORMAT_SIGN_MASK (1<<0)
|
||||
|
||||
// Bits used
|
||||
// Some code assumes they're sorted by size.
|
||||
#define AF_FORMAT_8BIT (0<<1)
|
||||
#define AF_FORMAT_16BIT (1<<1)
|
||||
#define AF_FORMAT_24BIT (2<<1)
|
||||
#define AF_FORMAT_32BIT (3<<1)
|
||||
#define AF_FORMAT_64BIT (4<<1)
|
||||
#define AF_FORMAT_BITS_MASK (7<<1)
|
||||
|
||||
// Fixed/floating point/special
|
||||
#define AF_FORMAT_I (1<<4) // Int
|
||||
#define AF_FORMAT_F (2<<4) // Foating point
|
||||
#define AF_FORMAT_S (4<<4) // special (IEC61937)
|
||||
#define AF_FORMAT_TYPE_MASK (7<<4)
|
||||
|
||||
// Interleaving (planar formats have data for each channel in separate planes)
|
||||
#define AF_FORMAT_INTERLEAVED (0<<7) // must be 0
|
||||
#define AF_FORMAT_PLANAR (1<<7)
|
||||
#define AF_FORMAT_INTERLEAVING_MASK (1<<7)
|
||||
|
||||
#define AF_FORMAT_S_CODEC(n) ((n)<<8)
|
||||
#define AF_FORMAT_S_CODEC_MASK (15 <<8) // 16 codecs max.
|
||||
|
||||
#define AF_FORMAT_MASK ((1<<12)-1)
|
||||
|
||||
#define AF_INTP (AF_FORMAT_I|AF_FORMAT_PLANAR)
|
||||
#define AF_FLTP (AF_FORMAT_F|AF_FORMAT_PLANAR)
|
||||
#define AF_FORMAT_S_(n) (AF_FORMAT_S_CODEC(n)|AF_FORMAT_S|AF_FORMAT_16BIT)
|
||||
|
||||
// actual sample formats
|
||||
enum af_format {
|
||||
AF_FORMAT_UNKNOWN = 0,
|
||||
AF_FORMAT_UNKNOWN = 0,
|
||||
|
||||
AF_FORMAT_U8 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_8BIT),
|
||||
AF_FORMAT_S8 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_8BIT),
|
||||
AF_FORMAT_U16 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT),
|
||||
AF_FORMAT_S16 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT),
|
||||
AF_FORMAT_U24 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT),
|
||||
AF_FORMAT_S24 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT),
|
||||
AF_FORMAT_U32 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_S32 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT),
|
||||
|
||||
AF_FORMAT_FLOAT = (AF_FORMAT_F|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_DOUBLE = (AF_FORMAT_F|AF_FORMAT_64BIT),
|
||||
AF_FORMAT_U8,
|
||||
AF_FORMAT_S16,
|
||||
AF_FORMAT_S24,
|
||||
AF_FORMAT_S32,
|
||||
AF_FORMAT_FLOAT,
|
||||
AF_FORMAT_DOUBLE,
|
||||
|
||||
// Planar variants
|
||||
AF_FORMAT_U8P = (AF_INTP|AF_FORMAT_US|AF_FORMAT_8BIT),
|
||||
AF_FORMAT_S16P = (AF_INTP|AF_FORMAT_SI|AF_FORMAT_16BIT),
|
||||
AF_FORMAT_S32P = (AF_INTP|AF_FORMAT_SI|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_FLOATP = (AF_FLTP|AF_FORMAT_32BIT),
|
||||
AF_FORMAT_DOUBLEP = (AF_FLTP|AF_FORMAT_64BIT),
|
||||
AF_FORMAT_U8P,
|
||||
AF_FORMAT_S16P,
|
||||
AF_FORMAT_S32P,
|
||||
AF_FORMAT_FLOATP,
|
||||
AF_FORMAT_DOUBLEP,
|
||||
|
||||
// All of these use IEC61937 framing, and otherwise pretend to be like PCM.
|
||||
AF_FORMAT_S_AAC = AF_FORMAT_S_(0),
|
||||
AF_FORMAT_S_AC3 = AF_FORMAT_S_(1),
|
||||
AF_FORMAT_S_DTS = AF_FORMAT_S_(2),
|
||||
AF_FORMAT_S_DTSHD = AF_FORMAT_S_(3),
|
||||
AF_FORMAT_S_EAC3 = AF_FORMAT_S_(4),
|
||||
AF_FORMAT_S_MP3 = AF_FORMAT_S_(5),
|
||||
AF_FORMAT_S_TRUEHD = AF_FORMAT_S_(6),
|
||||
AF_FORMAT_S_AAC,
|
||||
AF_FORMAT_S_AC3,
|
||||
AF_FORMAT_S_DTS,
|
||||
AF_FORMAT_S_DTSHD,
|
||||
AF_FORMAT_S_EAC3,
|
||||
AF_FORMAT_S_MP3,
|
||||
AF_FORMAT_S_TRUEHD,
|
||||
|
||||
AF_FORMAT_COUNT
|
||||
};
|
||||
|
||||
#define AF_FORMAT_IS_IEC61937(f) (((f) & AF_FORMAT_TYPE_MASK) == AF_FORMAT_S)
|
||||
#define AF_FORMAT_IS_SPECIAL(f) AF_FORMAT_IS_IEC61937(f)
|
||||
#define AF_FORMAT_IS_FLOAT(f) (!!((f) & AF_FORMAT_F))
|
||||
// false for interleaved and AF_FORMAT_UNKNOWN
|
||||
#define AF_FORMAT_IS_PLANAR(f) (!!((f) & AF_FORMAT_PLANAR))
|
||||
|
||||
struct af_fmt_entry {
|
||||
const char *name;
|
||||
int format;
|
||||
};
|
||||
|
||||
extern const struct af_fmt_entry af_fmtstr_table[];
|
||||
|
||||
int af_str2fmt_short(bstr str);
|
||||
const char *af_fmt_to_str(int format);
|
||||
|
||||
int af_fmt2bps(int format);
|
||||
int af_fmt2bits(int format);
|
||||
int af_fmt_change_bits(int format, int bits);
|
||||
int af_fmt_to_bytes(int format);
|
||||
int af_fmt_change_bytes(int format, int bytes);
|
||||
|
||||
bool af_fmt_is_valid(int format);
|
||||
bool af_fmt_is_unsigned(int format);
|
||||
bool af_fmt_is_float(int format);
|
||||
bool af_fmt_is_int(int format);
|
||||
bool af_fmt_is_planar(int format);
|
||||
bool af_fmt_is_spdif(int format);
|
||||
bool af_fmt_is_pcm(int format);
|
||||
|
||||
int af_fmt_to_planar(int format);
|
||||
int af_fmt_from_planar(int format);
|
||||
@@ -119,11 +73,9 @@ int af_fmt_from_planar(int format);
|
||||
// Amount of bytes that contain audio of the given duration, aligned to frames.
|
||||
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate);
|
||||
|
||||
bool af_fmt_is_valid(int format);
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format);
|
||||
|
||||
int af_format_conversion_score(int dst_format, int src_format);
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]);
|
||||
|
||||
int af_format_sample_alignment(int format);
|
||||
|
||||
|
||||
+15
-16
@@ -69,10 +69,10 @@ bool mixer_audio_initialized(struct mixer *mixer)
|
||||
return !!mixer->ao;
|
||||
}
|
||||
|
||||
float mixer_getneutralvolume(struct mixer *mixer)
|
||||
float mixer_getmaxvolume(struct mixer *mixer)
|
||||
{
|
||||
// gain == 1
|
||||
return mixer->softvol ? 1.0 / mixer->opts->softvol_max * 100.0 * 100.0 : 100;
|
||||
return mixer->softvol ? mixer->opts->softvol_max : 100;
|
||||
}
|
||||
|
||||
static void checkvolume(struct mixer *mixer)
|
||||
@@ -85,8 +85,8 @@ static void checkvolume(struct mixer *mixer)
|
||||
float gain;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_GET_VOLUME, &gain))
|
||||
gain = 1.0;
|
||||
vol.left = (gain / (mixer->opts->softvol_max / 100.0)) * 100.0;
|
||||
vol.right = (gain / (mixer->opts->softvol_max / 100.0)) * 100.0;
|
||||
vol.left = gain * 100.0;
|
||||
vol.right = gain * 100.0;
|
||||
} else {
|
||||
MP_DBG(mixer, "Reading volume from AO.\n");
|
||||
// Rely on the values not changing if the query is not supported
|
||||
@@ -122,14 +122,14 @@ void mixer_getvolume(struct mixer *mixer, float *l, float *r)
|
||||
|
||||
static void setvolume_internal(struct mixer *mixer, float l, float r)
|
||||
{
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
if (!mixer->softvol) {
|
||||
MP_DBG(mixer, "Setting volume on AO.\n");
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
MP_ERR(mixer, "Failed to change audio output volume.\n");
|
||||
return;
|
||||
}
|
||||
float gain = (l + r) / 2.0 / 100.0 * mixer->opts->softvol_max / 100.0;
|
||||
float gain = (l + r) / 2.0 / 100.0;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)) {
|
||||
if (gain == 1.0)
|
||||
return;
|
||||
@@ -144,8 +144,9 @@ void mixer_setvolume(struct mixer *mixer, float l, float r)
|
||||
{
|
||||
checkvolume(mixer); // to check mute status
|
||||
|
||||
mixer->vol_l = av_clipf(l, 0, 100);
|
||||
mixer->vol_r = av_clipf(r, 0, 100);
|
||||
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);
|
||||
}
|
||||
@@ -226,7 +227,7 @@ void mixer_setbalance(struct mixer *mixer, float val)
|
||||
return;
|
||||
}
|
||||
|
||||
/* make all other channels pass thru since by default pan blocks all */
|
||||
/* make all other channels pass through since by default pan blocks all */
|
||||
for (int i = 2; i < AF_NCH; i++) {
|
||||
float level[AF_NCH] = {0};
|
||||
level[i] = 1.f;
|
||||
@@ -242,9 +243,8 @@ char *mixer_get_volume_restore_data(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->driver[0])
|
||||
return NULL;
|
||||
return talloc_asprintf(NULL, "%s:%f:%f:%d:%f", mixer->driver, mixer->vol_l,
|
||||
mixer->vol_r, mixer->muted_by_us,
|
||||
mixer->opts->softvol_max);
|
||||
return talloc_asprintf(NULL, "%s:%f:%f:%d", mixer->driver, mixer->vol_l,
|
||||
mixer->vol_r, mixer->muted_by_us);
|
||||
}
|
||||
|
||||
static void probe_softvol(struct mixer *mixer)
|
||||
@@ -315,11 +315,10 @@ static void restore_volume(struct mixer *mixer)
|
||||
char *data = mixer->opts->mixer_restore_volume_data;
|
||||
if (!mixer->persistent_volume && data && data[0]) {
|
||||
char drv[40];
|
||||
float v_l, v_r, s;
|
||||
float v_l, v_r;
|
||||
int m;
|
||||
if (sscanf(data, "%39[^:]:%f:%f:%d:%f", drv, &v_l, &v_r, &m, &s) == 5) {
|
||||
float diff = fabs(mixer->opts->softvol_max - s);
|
||||
if (strcmp(mixer->driver, drv) == 0 && diff < 0.01) {
|
||||
if (sscanf(data, "%39[^:]:%f:%f:%d", drv, &v_l, &v_r, &m) == 4) {
|
||||
if (strcmp(mixer->driver, drv) == 0) {
|
||||
force_vol_l = v_l;
|
||||
force_vol_r = v_r;
|
||||
force_mute = !!m;
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ void mixer_setmute(struct mixer *mixer, bool mute);
|
||||
bool mixer_getmute(struct mixer *mixer);
|
||||
void mixer_getbalance(struct mixer *mixer, float *bal);
|
||||
void mixer_setbalance(struct mixer *mixer, float bal);
|
||||
float mixer_getneutralvolume(struct mixer *mixer);
|
||||
float mixer_getmaxvolume(struct mixer *mixer);
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer);
|
||||
|
||||
#endif /* MPLAYER_MIXER_H */
|
||||
+21
-20
@@ -191,14 +191,14 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
|
||||
talloc_free(ao);
|
||||
return ao_init(probing, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels, rdevice, redirect, args);
|
||||
samplerate, format, channels, rdevice, redirect, NULL);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ao->sstride = af_fmt2bps(ao->format);
|
||||
ao->sstride = af_fmt_to_bytes(ao->format);
|
||||
ao->num_planes = 1;
|
||||
if (AF_FORMAT_IS_PLANAR(ao->format)) {
|
||||
if (af_fmt_is_planar(ao->format)) {
|
||||
ao->num_planes = ao->channels.num;
|
||||
} else {
|
||||
ao->sstride *= ao->channels.num;
|
||||
@@ -307,7 +307,8 @@ done:
|
||||
// Uninitialize and destroy the AO. Remaining audio must be dropped.
|
||||
void ao_uninit(struct ao *ao)
|
||||
{
|
||||
ao->api->uninit(ao);
|
||||
if (ao)
|
||||
ao->api->uninit(ao);
|
||||
talloc_free(ao);
|
||||
}
|
||||
|
||||
@@ -385,28 +386,26 @@ bool ao_eof_reached(struct ao *ao)
|
||||
// Query the AO_EVENT_*s as requested by the events parameter, and return them.
|
||||
int ao_query_and_reset_events(struct ao *ao, int events)
|
||||
{
|
||||
int actual_events = 0;
|
||||
if (atomic_load(&ao->request_reload)) // don't need to reset it
|
||||
actual_events |= AO_EVENT_RELOAD;
|
||||
if (atomic_load(&ao->request_hotplug))
|
||||
actual_events |= AO_EVENT_HOTPLUG;
|
||||
return actual_events & events;
|
||||
return atomic_fetch_and(&ao->events_, ~(unsigned)events) & events;
|
||||
}
|
||||
|
||||
static void ao_add_events(struct ao *ao, int events)
|
||||
{
|
||||
atomic_fetch_or(&ao->events_, events);
|
||||
if (ao->input_ctx)
|
||||
mp_input_wakeup(ao->input_ctx);
|
||||
}
|
||||
|
||||
// Request that the player core destroys and recreates the AO. Fully thread-safe.
|
||||
void ao_request_reload(struct ao *ao)
|
||||
{
|
||||
atomic_store(&ao->request_reload, true);
|
||||
if (ao->input_ctx)
|
||||
mp_input_wakeup(ao->input_ctx);
|
||||
ao_add_events(ao, AO_EVENT_RELOAD);
|
||||
}
|
||||
|
||||
// Notify the player that the device list changed. Fully thread-safe.
|
||||
void ao_hotplug_event(struct ao *ao)
|
||||
{
|
||||
atomic_store(&ao->request_hotplug, true);
|
||||
if (ao->input_ctx)
|
||||
mp_input_wakeup(ao->input_ctx);
|
||||
ao_add_events(ao, AO_EVENT_HOTPLUG);
|
||||
}
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
@@ -415,12 +414,15 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
if (mp_msg_test(ao->log, MSGL_DEBUG)) {
|
||||
for (int i = 0; i < s->num_chmaps; i++) {
|
||||
struct mp_chmap c = s->chmaps[i];
|
||||
MP_DBG(ao, "chmap_sel #%d: %s\n", i, mp_chmap_to_str(&c));
|
||||
MP_DBG(ao, "chmap_sel #%d: %s (%s)\n", i, mp_chmap_to_str(&c),
|
||||
mp_chmap_to_str_hr(&c));
|
||||
}
|
||||
}
|
||||
bool r = mp_chmap_sel_adjust(s, map);
|
||||
if (r)
|
||||
MP_DBG(ao, "result: %s\n", mp_chmap_to_str(map));
|
||||
if (r) {
|
||||
MP_DBG(ao, "result: %s (%s)\n", mp_chmap_to_str(map),
|
||||
mp_chmap_to_str_hr(map));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -496,7 +498,6 @@ bool ao_hotplug_check_update(struct ao_hotplug *hp)
|
||||
{
|
||||
if (hp->ao && ao_query_and_reset_events(hp->ao, AO_EVENT_HOTPLUG)) {
|
||||
hp->needs_update = true;
|
||||
atomic_store(&hp->ao->request_hotplug, false);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
+126
-97
@@ -101,7 +101,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
long get_vol, set_vol;
|
||||
float f_multi;
|
||||
|
||||
if (AF_FORMAT_IS_SPECIAL(ao->format))
|
||||
if (!af_fmt_is_pcm(ao->format))
|
||||
return CONTROL_FALSE;
|
||||
|
||||
snd_mixer_selem_id_alloca(&sid);
|
||||
@@ -204,22 +204,26 @@ alsa_error:
|
||||
}
|
||||
|
||||
static const int mp_to_alsa_format[][2] = {
|
||||
{AF_FORMAT_S8, SND_PCM_FORMAT_S8},
|
||||
{AF_FORMAT_U8, SND_PCM_FORMAT_U8},
|
||||
{AF_FORMAT_U16, SND_PCM_FORMAT_U16},
|
||||
{AF_FORMAT_S16, SND_PCM_FORMAT_S16},
|
||||
{AF_FORMAT_U32, SND_PCM_FORMAT_U32},
|
||||
{AF_FORMAT_S32, SND_PCM_FORMAT_S32},
|
||||
{AF_FORMAT_U24,
|
||||
MP_SELECT_LE_BE(SND_PCM_FORMAT_U24_3LE, SND_PCM_FORMAT_U24_3BE)},
|
||||
{AF_FORMAT_S24,
|
||||
MP_SELECT_LE_BE(SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE)},
|
||||
{AF_FORMAT_FLOAT, SND_PCM_FORMAT_FLOAT},
|
||||
{AF_FORMAT_DOUBLE, SND_PCM_FORMAT_FLOAT64},
|
||||
{AF_FORMAT_UNKNOWN, SND_PCM_FORMAT_UNKNOWN},
|
||||
};
|
||||
|
||||
static int find_alsa_format(int af_format)
|
||||
{
|
||||
if (af_fmt_is_spdif(af_format)) {
|
||||
if (af_format == AF_FORMAT_S_MP3) {
|
||||
return SND_PCM_FORMAT_MPEG;
|
||||
} else {
|
||||
return SND_PCM_FORMAT_S16;
|
||||
}
|
||||
}
|
||||
|
||||
af_format = af_fmt_from_planar(af_format);
|
||||
for (int n = 0; mp_to_alsa_format[n][0] != AF_FORMAT_UNKNOWN; n++) {
|
||||
if (mp_to_alsa_format[n][0] == af_format)
|
||||
@@ -251,7 +255,10 @@ static const int alsa_to_mp_channels[][2] = {
|
||||
{SND_CHMAP_TRL, MP_SP(TBL)},
|
||||
{SND_CHMAP_TRR, MP_SP(TBR)},
|
||||
{SND_CHMAP_TRC, MP_SP(TBC)},
|
||||
{SND_CHMAP_RRC, MP_SP(SDR)},
|
||||
{SND_CHMAP_RLC, MP_SP(SDL)},
|
||||
{SND_CHMAP_MONO, MP_SP(FC)},
|
||||
{SND_CHMAP_NA, MP_SPEAKER_ID_NA},
|
||||
{SND_CHMAP_LAST, MP_SPEAKER_ID_COUNT}
|
||||
};
|
||||
|
||||
@@ -275,6 +282,20 @@ static int find_alsa_channel(int mp_channel)
|
||||
return SND_CHMAP_UNKNOWN;
|
||||
}
|
||||
|
||||
static int mp_chmap_from_alsa(struct mp_chmap *dst, snd_pcm_chmap_t *src)
|
||||
{
|
||||
*dst = (struct mp_chmap) {0};
|
||||
|
||||
if (src->channels > MP_NUM_CHANNELS)
|
||||
return -1;
|
||||
|
||||
dst->num = src->channels;
|
||||
for (int c = 0; c < dst->num; c++)
|
||||
dst->speaker[c] = find_mp_channel(src->pos[c]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -285,16 +306,12 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
return false;
|
||||
|
||||
for (int i = 0; maps[i] != NULL; i++) {
|
||||
if (maps[i]->map.channels > MP_NUM_CHANNELS) {
|
||||
MP_VERBOSE(ao, "skipping ALSA channel map with too many channels.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
struct mp_chmap entry = {.num = maps[i]->map.channels};
|
||||
for (int c = 0; c < entry.num; c++)
|
||||
entry.speaker[c] = find_mp_channel(maps[i]->map.pos[c]);
|
||||
struct mp_chmap entry;
|
||||
mp_chmap_from_alsa(&entry, &maps[i]->map);
|
||||
|
||||
if (mp_chmap_is_valid(&entry)) {
|
||||
if (maps[i]->type == SND_CHMAP_TYPE_VAR)
|
||||
mp_chmap_reorder_norm(&entry);
|
||||
MP_VERBOSE(ao, "Got supported channel map: %s (type %s)\n",
|
||||
mp_chmap_to_str(&entry),
|
||||
snd_pcm_chmap_type_name(maps[i]->type));
|
||||
@@ -366,8 +383,9 @@ static char *append_params(void *ta_parent, const char *device, const char *p)
|
||||
static int try_open_device(struct ao *ao, const char *device)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
if (af_fmt_is_spdif(ao->format)) {
|
||||
void *tmp = talloc_new(NULL);
|
||||
char *params = talloc_asprintf(tmp,
|
||||
"AES0=%d,AES1=%d,AES2=0,AES3=%d",
|
||||
@@ -376,15 +394,29 @@ static int try_open_device(struct ao *ao, const char *device)
|
||||
map_iec958_srate(ao->samplerate));
|
||||
const char *ac3_device = append_params(tmp, device, params);
|
||||
MP_VERBOSE(ao, "opening device '%s' => '%s'\n", device, ac3_device);
|
||||
int err = snd_pcm_open
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
err = snd_pcm_open(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
if (err < 0) {
|
||||
// Some spdif-capable devices do not accept the AES0 parameter,
|
||||
// and instead require the iec958 pseudo-device (they will play
|
||||
// noise otherwise). Unfortunately, ALSA gives us no way to map
|
||||
// these devices, so try it for the default device only.
|
||||
bstr dev;
|
||||
bstr_split_tok(bstr0(device), ":", &dev, &(bstr){0});
|
||||
if (bstr_equals0(dev, "default")) {
|
||||
ac3_device = append_params(tmp, "iec958", params);
|
||||
MP_VERBOSE(ao, "got error %d; opening iec fallback device '%s'\n",
|
||||
err, ac3_device);
|
||||
err = snd_pcm_open
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
}
|
||||
talloc_free(tmp);
|
||||
if (!err)
|
||||
return 0;
|
||||
} else {
|
||||
MP_VERBOSE(ao, "opening device '%s'\n", device);
|
||||
err = snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "opening device '%s'\n", device);
|
||||
return snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
@@ -404,14 +436,12 @@ alsa_error: ;
|
||||
#define INIT_OK 0
|
||||
#define INIT_ERROR -1
|
||||
#define INIT_BRAINDEATH -2
|
||||
static int init_device(struct ao *ao)
|
||||
static int init_device(struct ao *ao, bool second_try)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
const char *device = "default";
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format))
|
||||
device = "iec958";
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->cfg_device && p->cfg_device[0])
|
||||
@@ -432,38 +462,28 @@ static int init_device(struct ao *ao)
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
if (ao->format == AF_FORMAT_S_MP3) {
|
||||
p->alsa_fmt = SND_PCM_FORMAT_MPEG;
|
||||
} else {
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16;
|
||||
}
|
||||
} else {
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
ao->format = try_formats[n];
|
||||
p->alsa_fmt = find_alsa_format(ao->format);
|
||||
}
|
||||
if (p->alsa_fmt == SND_PCM_FORMAT_UNKNOWN) {
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16;
|
||||
ao->format = AF_FORMAT_S16;
|
||||
if (snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt) >= 0)
|
||||
break;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
if (err < 0) {
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format))
|
||||
CHECK_ALSA_ERROR("Unable to set IEC61937 format");
|
||||
MP_INFO(ao, "Format %s is not supported by hardware, trying default.\n",
|
||||
af_fmt_to_str(ao->format));
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16;
|
||||
ao->format = AF_FORMAT_S16;
|
||||
if (!ao->format) {
|
||||
MP_ERR(ao, "Can't find appropriate sample format.\n");
|
||||
goto alsa_error;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
CHECK_ALSA_ERROR("Unable to set format");
|
||||
|
||||
snd_pcm_access_t access = AF_FORMAT_IS_PLANAR(ao->format)
|
||||
snd_pcm_access_t access = af_fmt_is_planar(ao->format)
|
||||
? SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
: SND_PCM_ACCESS_RW_INTERLEAVED;
|
||||
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
|
||||
if (err < 0 && AF_FORMAT_IS_PLANAR(ao->format)) {
|
||||
if (err < 0 && af_fmt_is_planar(ao->format)) {
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
access = SND_PCM_ACCESS_RW_INTERLEAVED;
|
||||
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
|
||||
@@ -471,8 +491,11 @@ static int init_device(struct ao *ao)
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
|
||||
struct mp_chmap dev_chmap = ao->channels;
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format) || p->cfg_ignore_chmap) {
|
||||
if (af_fmt_is_spdif(ao->format) || p->cfg_ignore_chmap) {
|
||||
dev_chmap.num = 0; // disable chmap API
|
||||
} else if (dev_chmap.num == 1 && dev_chmap.speaker[0] == MP_SPEAKER_ID_FC) {
|
||||
// As yet another ALSA API inconsistency, mono is not reported correctly.
|
||||
dev_chmap.num = 0;
|
||||
} else if (query_chmaps(ao, &dev_chmap)) {
|
||||
ao->channels = dev_chmap;
|
||||
} else {
|
||||
@@ -492,11 +515,12 @@ static int init_device(struct ao *ao)
|
||||
}
|
||||
|
||||
if (num_channels != ao->channels.num) {
|
||||
int req = ao->channels.num;
|
||||
mp_chmap_from_channels_alsa(&ao->channels, num_channels);
|
||||
if (!mp_chmap_is_valid(&ao->channels))
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
MP_ERR(ao, "Couldn't get requested number of channels, fallback to %s.\n",
|
||||
mp_chmap_to_str(&ao->channels));
|
||||
MP_ERR(ao, "Asked for %d channels, got %d - fallback to %s.\n", req,
|
||||
num_channels, mp_chmap_to_str(&ao->channels));
|
||||
}
|
||||
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
@@ -546,10 +570,11 @@ static int init_device(struct ao *ao)
|
||||
|
||||
err = snd_pcm_set_chmap(p->alsa, alsa_chmap);
|
||||
if (err == -ENXIO) {
|
||||
// I consider this an ALSA bug: the channel map was reported as
|
||||
// supported, but we still can't set it. It happens virtually
|
||||
// always with dmix, though.
|
||||
MP_VERBOSE(ao, "Device does not support requested channel map (%s)\n",
|
||||
// A device my not be able to set any channel map, even channel maps
|
||||
// that were reported as supported. This is either because the ALSA
|
||||
// device is broken (dmix), or because the driver has only 1
|
||||
// channel map per channel count, and setting the map is not needed.
|
||||
MP_VERBOSE(ao, "device returned ENXIO when setting channel map %s\n",
|
||||
mp_chmap_to_str(&dev_chmap));
|
||||
} else {
|
||||
CHECK_ALSA_WARN("Channel map setup failed");
|
||||
@@ -564,17 +589,45 @@ static int init_device(struct ao *ao)
|
||||
if (snd_pcm_chmap_print(alsa_chmap, sizeof(tmp), tmp) > 0)
|
||||
MP_VERBOSE(ao, "channel map reported by ALSA: %s\n", tmp);
|
||||
|
||||
struct mp_chmap chmap = {.num = alsa_chmap->channels};
|
||||
for (int c = 0; c < chmap.num; c++)
|
||||
chmap.speaker[c] = find_mp_channel(alsa_chmap->pos[c]);
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_alsa(&chmap, alsa_chmap);
|
||||
|
||||
MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap));
|
||||
|
||||
if (p->cfg_ignore_chmap) {
|
||||
MP_VERBOSE(ao, "user set ignore-chmap; ignoring the channel map.\n");
|
||||
} else if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
} else if (af_fmt_is_spdif(ao->format)) {
|
||||
MP_VERBOSE(ao, "using spdif passthrough; ignoring the channel map.\n");
|
||||
} else if (mp_chmap_is_valid(&chmap)) {
|
||||
// Is it one that contains NA channels?
|
||||
struct mp_chmap without_na = chmap;
|
||||
mp_chmap_remove_na(&without_na);
|
||||
|
||||
if (mp_chmap_is_valid(&without_na) &&
|
||||
!mp_chmap_equals(&without_na, &chmap) &&
|
||||
!mp_chmap_equals(&chmap, &ao->channels) &&
|
||||
!second_try)
|
||||
{
|
||||
// Sometimes, ALSA will advertise certain chmaps, but it's not
|
||||
// possible to set them. This can happen with dmix: as of
|
||||
// alsa 1.0.28, dmix can do stereo only, but advertises the
|
||||
// surround chmaps of the underlying device. In this case,
|
||||
// e.g. setting 6 channels will succeed, but requesting 5.1
|
||||
// afterwards will fail. Then it will return something like
|
||||
// "FL FR NA NA NA NA" as channel map. This means we would
|
||||
// have to pad stereo output to 6 channels with silence, which
|
||||
// would require lots of extra processing. You can't change
|
||||
// the number of channels to 2 either, because the hw params
|
||||
// are already set! So just fuck it and reopen the device with
|
||||
// the chmap "cleaned out" of NA entries.
|
||||
MP_VERBOSE(ao, "Working around braindead ALSA behavior.\n");
|
||||
err = snd_pcm_close(p->alsa);
|
||||
p->alsa = NULL;
|
||||
CHECK_ALSA_ERROR("pcm close error");
|
||||
ao->channels = without_na;
|
||||
return INIT_BRAINDEATH;
|
||||
}
|
||||
|
||||
if (mp_chmap_equals(&chmap, &ao->channels)) {
|
||||
MP_VERBOSE(ao, "which is what we requested.\n");
|
||||
} else if (chmap.num == ao->channels.num) {
|
||||
@@ -584,41 +637,14 @@ static int init_device(struct ao *ao)
|
||||
MP_WARN(ao, "ALSA channel map conflicts with channel count!\n");
|
||||
}
|
||||
} else {
|
||||
// Is it one that contains NA channels?
|
||||
struct mp_chmap chmap2 = {0};
|
||||
for (int c = 0; c < alsa_chmap->channels; c++) {
|
||||
int alsa_ch = alsa_chmap->pos[c];
|
||||
if (alsa_ch != SND_CHMAP_NA)
|
||||
chmap2.speaker[chmap2.num++] = find_mp_channel(alsa_ch);
|
||||
}
|
||||
|
||||
if (mp_chmap_is_valid(&chmap2)) {
|
||||
// Sometimes, ALSA will advertise certain chmaps, but it's not
|
||||
// possible to set them. This can happen with dmix: as of
|
||||
// alsa 1.0.28, dmix can do stereo only, but advertises the
|
||||
// surround chmaps of the underlying device. In this case,
|
||||
// requesting e.g. 5.1 will fail, but it will still allow
|
||||
// setting 6 channels. Then it will return something like
|
||||
// "FL FR NA NA NA NA" as channel map. This means we would
|
||||
// have to pad stereo output to 6 channels with silence, which
|
||||
// is way too complicated in the general case. You can't change
|
||||
// the number of channels to 2 either, because the hw params
|
||||
// are already set! So just fuck it and reopen the device with
|
||||
// the chmap "cleaned out" of NA entries.
|
||||
MP_VERBOSE(ao, "Working around braindead ALSA behavior.\n");
|
||||
err = snd_pcm_close(p->alsa);
|
||||
p->alsa = NULL;
|
||||
CHECK_ALSA_ERROR("pcm close error");
|
||||
ao->channels = chmap2;
|
||||
return INIT_BRAINDEATH;
|
||||
}
|
||||
|
||||
MP_WARN(ao, "Got unknown channel map from ALSA.\n");
|
||||
}
|
||||
|
||||
// mpv and ALSA use different conventions for mono
|
||||
if (ao->channels.num == 1)
|
||||
if (ao->channels.num == 1) {
|
||||
MP_VERBOSE(ao, "assuming we actually got MONO from ALSA.\n");
|
||||
ao->channels.speaker[0] = MP_SP(FC);
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
}
|
||||
@@ -683,9 +709,9 @@ static int init(struct ao *ao)
|
||||
|
||||
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
|
||||
|
||||
int r = init_device(ao);
|
||||
int r = init_device(ao, false);
|
||||
if (r == INIT_BRAINDEATH)
|
||||
r = init_device(ao); // retry with normalized channel layout
|
||||
r = init_device(ao, true); // retry with normalized channel layout
|
||||
return r == INIT_OK ? 0 : -1;
|
||||
}
|
||||
|
||||
@@ -822,7 +848,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
if (AF_FORMAT_IS_PLANAR(ao->format)) {
|
||||
if (af_fmt_is_planar(ao->format)) {
|
||||
res = snd_pcm_writen(p->alsa, data, samples);
|
||||
} else {
|
||||
res = snd_pcm_writei(p->alsa, data[0], samples);
|
||||
@@ -896,15 +922,18 @@ static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
char *name = snd_device_name_get_hint(hints[n], "NAME");
|
||||
char *desc = snd_device_name_get_hint(hints[n], "DESC");
|
||||
char *io = snd_device_name_get_hint(hints[n], "IOID");
|
||||
if (io && strcmp(io, "Output") != 0)
|
||||
continue;
|
||||
char desc2[1024];
|
||||
snprintf(desc2, sizeof(desc2), "%s", desc ? desc : "");
|
||||
for (int i = 0; desc2[i]; i++) {
|
||||
if (desc2[i] == '\n')
|
||||
desc2[i] = '/';
|
||||
if (io && strcmp(io, "Output") == 0) {
|
||||
char desc2[1024];
|
||||
snprintf(desc2, sizeof(desc2), "%s", desc ? desc : "");
|
||||
for (int i = 0; desc2[i]; i++) {
|
||||
if (desc2[i] == '\n')
|
||||
desc2[i] = '/';
|
||||
}
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc2});
|
||||
}
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc2});
|
||||
free(name);
|
||||
free(desc);
|
||||
free(io);
|
||||
}
|
||||
|
||||
snd_device_name_free_hint(hints);
|
||||
|
||||
+95
-249
@@ -25,18 +25,22 @@
|
||||
#include "options/m_option.h"
|
||||
#include "misc/ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
#include "ao_coreaudio_chmap.h"
|
||||
#include "ao_coreaudio_properties.h"
|
||||
#include "ao_coreaudio_utils.h"
|
||||
|
||||
struct priv {
|
||||
AudioDeviceID device;
|
||||
AudioUnit audio_unit;
|
||||
|
||||
uint64_t hw_latency_us;
|
||||
};
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap);
|
||||
AudioStreamBasicDescription original_asbd;
|
||||
AudioStreamID original_asbd_stream;
|
||||
|
||||
int change_physical_format;
|
||||
int exclusive;
|
||||
};
|
||||
|
||||
static int64_t ca_get_hardware_latency(struct ao *ao) {
|
||||
struct priv *p = ao->priv;
|
||||
@@ -52,12 +56,8 @@ static int64_t ca_get_hardware_latency(struct ao *ao) {
|
||||
&size);
|
||||
CHECK_CA_ERROR("cannot get audio unit latency");
|
||||
|
||||
uint32_t frames = 0;
|
||||
err = CA_GET_O(p->device, kAudioDevicePropertyLatency, &frames);
|
||||
CHECK_CA_ERROR("cannot get device latency");
|
||||
|
||||
uint64_t audiounit_latency_us = audiounit_latency_sec * 1e6;
|
||||
uint64_t device_latency_us = ca_frames_to_us(ao, frames);
|
||||
uint64_t device_latency_us = ca_get_device_latency_us(ao, p->device);
|
||||
|
||||
MP_VERBOSE(ao, "audiounit latency [us]: %lld\n", audiounit_latency_us);
|
||||
MP_VERBOSE(ao, "device latency [us]: %lld\n", device_latency_us);
|
||||
@@ -74,12 +74,14 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
AudioBuffer buf = buffer_list->mBuffers[0];
|
||||
void *planes[MP_NUM_CHANNELS] = {0};
|
||||
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
planes[n] = buffer_list->mBuffers[n].mData;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += p->hw_latency_us + ca_get_latency(ts) + ca_frames_to_us(ao, frames);
|
||||
|
||||
ao_read_data(ao, &buf.mData, frames, end);
|
||||
ao_read_data(ao, planes, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
@@ -122,8 +124,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static bool init_chmap(struct ao *ao);
|
||||
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
static void init_physical_format(struct ao *ao);
|
||||
|
||||
static bool reinit_device(struct ao *ao) {
|
||||
struct priv *p = ao->priv;
|
||||
@@ -144,8 +146,10 @@ coreaudio_error:
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
MP_WARN(ao, "detected IEC61937, redirecting to coreaudio_exclusive\n");
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (!af_fmt_is_pcm(ao->format) || p->exclusive) {
|
||||
MP_VERBOSE(ao, "redirecting to coreaudio_exclusive\n");
|
||||
ao->redirect = "coreaudio_exclusive";
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
@@ -153,10 +157,11 @@ static int init(struct ao *ao)
|
||||
if (!reinit_device(ao))
|
||||
goto coreaudio_error;
|
||||
|
||||
if (!init_chmap(ao))
|
||||
goto coreaudio_error;
|
||||
if (p->change_physical_format)
|
||||
init_physical_format(ao);
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
if (!ca_init_chmap(ao, p->device))
|
||||
goto coreaudio_error;
|
||||
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
@@ -170,95 +175,71 @@ coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static AudioChannelLayout* ca_query_layout(struct ao *ao, void *talloc_ctx)
|
||||
static void init_physical_format(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err;
|
||||
uint32_t psize;
|
||||
AudioChannelLayout *r = NULL;
|
||||
OSErr err;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyPreferredChannelLayout,
|
||||
.mScope = kAudioDevicePropertyScopeOutput,
|
||||
.mElement = kAudioObjectPropertyElementWildcard,
|
||||
};
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
|
||||
err = AudioObjectGetPropertyDataSize(p->device, &p_addr, 0, NULL, &psize);
|
||||
CHECK_CA_ERROR("could not get device preferred layout (size)");
|
||||
AudioStreamID *streams;
|
||||
size_t n_streams;
|
||||
|
||||
r = talloc_size(talloc_ctx, psize);
|
||||
err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
|
||||
&streams, &n_streams);
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
|
||||
err = AudioObjectGetPropertyData(p->device, &p_addr, 0, NULL, &psize, r);
|
||||
CHECK_CA_ERROR("could not get device preferred layout (get)");
|
||||
MP_VERBOSE(ao, "Found %zd substream(s).\n", n_streams);
|
||||
|
||||
coreaudio_error:
|
||||
return r;
|
||||
}
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
AudioStreamRangedDescription *formats;
|
||||
size_t n_formats;
|
||||
|
||||
static AudioChannelLayout* ca_query_stereo_layout(struct ao *ao, void *talloc_ctx)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err;
|
||||
const int nch = 2;
|
||||
uint32_t channels[nch];
|
||||
AudioChannelLayout *r = NULL;
|
||||
MP_VERBOSE(ao, "Looking at formats in substream %d...\n", i);
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyPreferredChannelsForStereo,
|
||||
.mScope = kAudioDevicePropertyScopeOutput,
|
||||
.mElement = kAudioObjectPropertyElementWildcard,
|
||||
};
|
||||
err = CA_GET_ARY(streams[i], kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
uint32_t psize = sizeof(channels);
|
||||
err = AudioObjectGetPropertyData(p->device, &p_addr, 0, NULL, &psize, channels);
|
||||
CHECK_CA_ERROR("could not get device preferred stereo layout");
|
||||
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
||||
continue; // try next one
|
||||
|
||||
psize = sizeof(AudioChannelLayout) + nch * sizeof(AudioChannelDescription);
|
||||
r = talloc_zero_size(talloc_ctx, psize);
|
||||
r->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
|
||||
r->mNumberChannelDescriptions = nch;
|
||||
|
||||
AudioChannelDescription desc = {0};
|
||||
desc.mChannelFlags = kAudioChannelFlags_AllOff;
|
||||
uint32_t direction;
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyDirection, &direction);
|
||||
CHECK_CA_ERROR("could not get stream direction");
|
||||
if (direction != 0) {
|
||||
MP_VERBOSE(ao, "Not an output stream.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
for(int i = 0; i < nch; i++) {
|
||||
desc.mChannelLabel = channels[i];
|
||||
r->mChannelDescriptions[i] = desc;
|
||||
AudioStreamBasicDescription best_asbd = {0};
|
||||
|
||||
for (int j = 0; j < n_formats; j++) {
|
||||
AudioStreamBasicDescription *stream_asbd = &formats[j].mFormat;
|
||||
|
||||
ca_print_asbd(ao, "- ", stream_asbd);
|
||||
|
||||
if (!best_asbd.mFormatID || ca_asbd_is_better(&asbd, &best_asbd,
|
||||
stream_asbd))
|
||||
best_asbd = *stream_asbd;
|
||||
}
|
||||
|
||||
if (best_asbd.mFormatID) {
|
||||
p->original_asbd_stream = streams[i];
|
||||
err = CA_GET(p->original_asbd_stream,
|
||||
kAudioStreamPropertyPhysicalFormat,
|
||||
&p->original_asbd);
|
||||
CHECK_CA_WARN("could not get current physical stream format");
|
||||
|
||||
if (!ca_change_physical_format_sync(ao, streams[i], best_asbd))
|
||||
p->original_asbd = (AudioStreamBasicDescription){0};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
coreaudio_error:
|
||||
return r;
|
||||
}
|
||||
|
||||
static bool init_chmap(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
void *ta_ctx = talloc_new(NULL);
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = p};
|
||||
struct mp_chmap chmap = {0};
|
||||
|
||||
AudioChannelLayout *ml = ca_query_layout(ao, ta_ctx);
|
||||
if (ml && ca_layout_to_mp_chmap(ao, ml, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
|
||||
AudioChannelLayout *sl = ca_query_stereo_layout(ao, ta_ctx);
|
||||
if (sl && ca_layout_to_mp_chmap(ao, sl, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
|
||||
talloc_free(ta_ctx);
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
|
||||
MP_ERR(ao, "could not select a suitable channel map among the "
|
||||
"hardware supported ones. Make sure to configure your "
|
||||
"output device correctly in 'Audio MIDI Setup.app'\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
@@ -351,13 +332,23 @@ static void uninit(struct ao *ao)
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
|
||||
if (p->original_asbd.mFormatID) {
|
||||
OSStatus err = CA_SET(p->original_asbd_stream,
|
||||
kAudioStreamPropertyPhysicalFormat,
|
||||
&p->original_asbd);
|
||||
CHECK_CA_WARN("could not restore physical stream format");
|
||||
}
|
||||
}
|
||||
|
||||
static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr,
|
||||
const AudioObjectPropertyAddress addr[],
|
||||
void *ctx) {
|
||||
reinit_device(ctx);
|
||||
ao_hotplug_event(ctx);
|
||||
void *ctx)
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
MP_VERBOSE(ao, "Handling potential hotplug event...\n");
|
||||
reinit_device(ao);
|
||||
ao_hotplug_event(ao);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
@@ -381,8 +372,8 @@ static int hotplug_init(struct ao *ao)
|
||||
err = AudioObjectAddPropertyListener(
|
||||
kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao);
|
||||
if (err != noErr) {
|
||||
char *c1 = fourcc_repr(ao, hotplug_properties[i]);
|
||||
char *c2 = fourcc_repr(ao, err);
|
||||
char *c1 = fourcc_repr(hotplug_properties[i]);
|
||||
char *c2 = fourcc_repr(err);
|
||||
MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2);
|
||||
goto coreaudio_error;
|
||||
}
|
||||
@@ -406,163 +397,13 @@ static void hotplug_uninit(struct ao *ao)
|
||||
err = AudioObjectRemovePropertyListener(
|
||||
kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao);
|
||||
if (err != noErr) {
|
||||
char *c1 = fourcc_repr(ao, hotplug_properties[i]);
|
||||
char *c2 = fourcc_repr(ao, err);
|
||||
char *c1 = fourcc_repr(hotplug_properties[i]);
|
||||
char *c2 = fourcc_repr(err);
|
||||
MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Channel Mapping functions
|
||||
static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
|
||||
{ kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
|
||||
{ kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
|
||||
{ kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
|
||||
{ kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
|
||||
{ kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
|
||||
{ kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
|
||||
{ kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
|
||||
{ kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
|
||||
{ kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
|
||||
{ kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
|
||||
{ kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
|
||||
{ kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
|
||||
{ kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
|
||||
{ kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
|
||||
{ kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
|
||||
{ kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
|
||||
{ kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
|
||||
|
||||
// unofficial extensions
|
||||
{ kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
|
||||
{ kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
|
||||
{ kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
|
||||
{ kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
|
||||
{ kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
|
||||
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
{ kAudioChannelLabel_Unknown, MP_SPEAKER_ID_UNKNOWN0 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
return speaker_map[i][1];
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, l))
|
||||
return;
|
||||
|
||||
AudioChannelDescription *descs = layout->mChannelDescriptions;
|
||||
|
||||
mp_msg(ao->log, l, "layout: tag: <%u>, bitmap: <%u>, "
|
||||
"descriptions <%u>\n",
|
||||
(unsigned) layout->mChannelLayoutTag,
|
||||
(unsigned) layout->mChannelBitmap,
|
||||
(unsigned) layout->mNumberChannelDescriptions);
|
||||
|
||||
for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
|
||||
AudioChannelDescription d = descs[i];
|
||||
mp_msg(ao->log, l, " - description %d: label <%u, %u>, "
|
||||
" flags: <%u>, coords: <%f, %f, %f>\n", i,
|
||||
(unsigned) d.mChannelLabel,
|
||||
(unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
|
||||
(unsigned) d.mChannelFlags,
|
||||
d.mCoordinates[0],
|
||||
d.mCoordinates[1],
|
||||
d.mCoordinates[2]);
|
||||
}
|
||||
}
|
||||
|
||||
static AudioChannelLayout *ca_layout_to_custom_layout(
|
||||
struct ao *ao, void *talloc_ctx, AudioChannelLayout *l)
|
||||
{
|
||||
AudioChannelLayoutTag tag = l->mChannelLayoutTag;
|
||||
AudioChannelLayout *r;
|
||||
OSStatus err;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t), &l->mChannelBitmap, &psize);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)");
|
||||
r = talloc_size(NULL, psize);
|
||||
err = AudioFormatGetProperty(
|
||||
kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (get)");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize);
|
||||
r = talloc_size(NULL, psize);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)");
|
||||
err = AudioFormatGetProperty(
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
|
||||
} else {
|
||||
r = l;
|
||||
}
|
||||
|
||||
return r;
|
||||
coreaudio_error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap)
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
MP_DBG(ao, "input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_DEBUG, layout);
|
||||
|
||||
AudioChannelLayout *l = ca_layout_to_custom_layout(ao, talloc_ctx, layout);
|
||||
if (!l)
|
||||
goto coreaudio_error;
|
||||
|
||||
MP_VERBOSE(ao, "converted input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, l);
|
||||
|
||||
if (l->mNumberChannelDescriptions > MP_NUM_CHANNELS) {
|
||||
MP_VERBOSE(ao, "layout has too many descriptions (%u, max: %d)\n",
|
||||
(unsigned) l->mNumberChannelDescriptions, MP_NUM_CHANNELS);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int n = 0; n < l->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = l->mChannelDescriptions[n].mChannelLabel;
|
||||
uint8_t speaker = ca_label_to_mp_speaker_id(label);
|
||||
if (speaker < 0) {
|
||||
MP_VERBOSE(ao, "channel label=%u unusable to build channel "
|
||||
"bitmap, skipping layout\n", (unsigned) label);
|
||||
goto coreaudio_error;
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(talloc_ctx);
|
||||
return chmap->num > 0;
|
||||
coreaudio_error:
|
||||
MP_VERBOSE(ao, "converted input channel layout (failed):\n");
|
||||
ca_log_layout(ao, MSGL_V, layout);
|
||||
talloc_free(talloc_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_coreaudio = {
|
||||
@@ -577,4 +418,9 @@ const struct ao_driver audio_out_coreaudio = {
|
||||
.hotplug_uninit = hotplug_uninit,
|
||||
.list_devs = ca_get_device_list,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]){
|
||||
OPT_FLAG("change-physical-format", change_physical_format, 0),
|
||||
OPT_FLAG("exclusive", exclusive, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include "ao_coreaudio_utils.h"
|
||||
|
||||
#include "ao_coreaudio_chmap.h"
|
||||
|
||||
static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
|
||||
{ kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
|
||||
{ kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
|
||||
{ kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
|
||||
{ kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
|
||||
{ kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
|
||||
{ kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
|
||||
{ kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
|
||||
{ kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
|
||||
{ kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
|
||||
{ kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
|
||||
{ kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
|
||||
{ kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
|
||||
{ kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
|
||||
{ kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
|
||||
{ kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
|
||||
{ kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
|
||||
{ kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
|
||||
|
||||
// unofficial extensions
|
||||
{ kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
|
||||
{ kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
|
||||
{ kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
|
||||
{ kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
|
||||
{ kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
|
||||
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
{ kAudioChannelLabel_Unknown, MP_SPEAKER_ID_NA },
|
||||
|
||||
{ 0, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
return speaker_map[i][1];
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, l))
|
||||
return;
|
||||
|
||||
AudioChannelDescription *descs = layout->mChannelDescriptions;
|
||||
|
||||
mp_msg(ao->log, l, "layout: tag: <%u>, bitmap: <%u>, "
|
||||
"descriptions <%u>\n",
|
||||
(unsigned) layout->mChannelLayoutTag,
|
||||
(unsigned) layout->mChannelBitmap,
|
||||
(unsigned) layout->mNumberChannelDescriptions);
|
||||
|
||||
for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
|
||||
AudioChannelDescription d = descs[i];
|
||||
mp_msg(ao->log, l, " - description %d: label <%u, %u>, "
|
||||
" flags: <%u>, coords: <%f, %f, %f>\n", i,
|
||||
(unsigned) d.mChannelLabel,
|
||||
(unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
|
||||
(unsigned) d.mChannelFlags,
|
||||
d.mCoordinates[0],
|
||||
d.mCoordinates[1],
|
||||
d.mCoordinates[2]);
|
||||
}
|
||||
}
|
||||
|
||||
static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
|
||||
void *talloc_ctx,
|
||||
AudioChannelLayout *l)
|
||||
{
|
||||
AudioChannelLayoutTag tag = l->mChannelLayoutTag;
|
||||
AudioChannelLayout *r;
|
||||
OSStatus err;
|
||||
|
||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t), &l->mChannelBitmap, &psize);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)");
|
||||
r = talloc_size(NULL, psize);
|
||||
err = AudioFormatGetProperty(
|
||||
kAudioFormatProperty_ChannelLayoutForBitmap,
|
||||
sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (get)");
|
||||
} else if (tag != kAudioChannelLayoutTag_UseChannelDescriptions) {
|
||||
uint32_t psize;
|
||||
err = AudioFormatGetPropertyInfo(
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize);
|
||||
r = talloc_size(NULL, psize);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)");
|
||||
err = AudioFormatGetProperty(
|
||||
kAudioFormatProperty_ChannelLayoutForTag,
|
||||
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize, r);
|
||||
CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
|
||||
} else {
|
||||
r = l;
|
||||
}
|
||||
|
||||
return r;
|
||||
coreaudio_error:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool ca_layout_to_mp_chmap(struct ao *ao, AudioChannelLayout *layout,
|
||||
struct mp_chmap *chmap)
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
MP_DBG(ao, "input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_DEBUG, layout);
|
||||
|
||||
AudioChannelLayout *l = ca_layout_to_custom_layout(ao, talloc_ctx, layout);
|
||||
if (!l)
|
||||
goto coreaudio_error;
|
||||
|
||||
MP_VERBOSE(ao, "converted input channel layout:\n");
|
||||
ca_log_layout(ao, MSGL_V, l);
|
||||
|
||||
if (l->mNumberChannelDescriptions > MP_NUM_CHANNELS) {
|
||||
MP_VERBOSE(ao, "layout has too many descriptions (%u, max: %d)\n",
|
||||
(unsigned) l->mNumberChannelDescriptions, MP_NUM_CHANNELS);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int n = 0; n < l->mNumberChannelDescriptions; n++) {
|
||||
AudioChannelLabel label = l->mChannelDescriptions[n].mChannelLabel;
|
||||
int speaker = ca_label_to_mp_speaker_id(label);
|
||||
if (speaker < 0) {
|
||||
MP_VERBOSE(ao, "channel label=%u unusable to build channel "
|
||||
"bitmap, skipping layout\n", (unsigned) label);
|
||||
goto coreaudio_error;
|
||||
} else {
|
||||
chmap->speaker[n] = speaker;
|
||||
chmap->num = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(talloc_ctx);
|
||||
return chmap->num > 0;
|
||||
coreaudio_error:
|
||||
MP_VERBOSE(ao, "converted input channel layout (failed):\n");
|
||||
ca_log_layout(ao, MSGL_V, layout);
|
||||
talloc_free(talloc_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
static AudioChannelLayout* ca_query_layout(struct ao *ao,
|
||||
AudioDeviceID device,
|
||||
void *talloc_ctx)
|
||||
{
|
||||
OSStatus err;
|
||||
uint32_t psize;
|
||||
AudioChannelLayout *r = NULL;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyPreferredChannelLayout,
|
||||
.mScope = kAudioDevicePropertyScopeOutput,
|
||||
.mElement = kAudioObjectPropertyElementWildcard,
|
||||
};
|
||||
|
||||
err = AudioObjectGetPropertyDataSize(device, &p_addr, 0, NULL, &psize);
|
||||
CHECK_CA_ERROR("could not get device preferred layout (size)");
|
||||
|
||||
r = talloc_size(talloc_ctx, psize);
|
||||
|
||||
err = AudioObjectGetPropertyData(device, &p_addr, 0, NULL, &psize, r);
|
||||
CHECK_CA_ERROR("could not get device preferred layout (get)");
|
||||
|
||||
coreaudio_error:
|
||||
return r;
|
||||
}
|
||||
|
||||
static AudioChannelLayout* ca_query_stereo_layout(struct ao *ao,
|
||||
AudioDeviceID device,
|
||||
void *talloc_ctx)
|
||||
{
|
||||
OSStatus err;
|
||||
const int nch = 2;
|
||||
uint32_t channels[nch];
|
||||
AudioChannelLayout *r = NULL;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyPreferredChannelsForStereo,
|
||||
.mScope = kAudioDevicePropertyScopeOutput,
|
||||
.mElement = kAudioObjectPropertyElementWildcard,
|
||||
};
|
||||
|
||||
uint32_t psize = sizeof(channels);
|
||||
err = AudioObjectGetPropertyData(device, &p_addr, 0, NULL, &psize, channels);
|
||||
CHECK_CA_ERROR("could not get device preferred stereo layout");
|
||||
|
||||
psize = sizeof(AudioChannelLayout) + nch * sizeof(AudioChannelDescription);
|
||||
r = talloc_zero_size(talloc_ctx, psize);
|
||||
r->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
|
||||
r->mNumberChannelDescriptions = nch;
|
||||
|
||||
AudioChannelDescription desc = {0};
|
||||
desc.mChannelFlags = kAudioChannelFlags_AllOff;
|
||||
|
||||
for(int i = 0; i < nch; i++) {
|
||||
desc.mChannelLabel = channels[i];
|
||||
r->mChannelDescriptions[i] = desc;
|
||||
}
|
||||
|
||||
coreaudio_error:
|
||||
return r;
|
||||
}
|
||||
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
void *ta_ctx = talloc_new(NULL);
|
||||
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = ta_ctx};
|
||||
struct mp_chmap chmap = {0};
|
||||
|
||||
mp_chmap_sel_add_map(&chmap_sel, &(struct mp_chmap)MP_CHMAP_INIT_MONO);
|
||||
|
||||
AudioChannelLayout *ml = ca_query_layout(ao, device, ta_ctx);
|
||||
if (ml && ca_layout_to_mp_chmap(ao, ml, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
|
||||
AudioChannelLayout *sl = ca_query_stereo_layout(ao, device, ta_ctx);
|
||||
if (sl && ca_layout_to_mp_chmap(ao, sl, &chmap))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
|
||||
MP_ERR(ao, "could not select a suitable channel map among the "
|
||||
"hardware supported ones. Make sure to configure your "
|
||||
"output device correctly in 'Audio MIDI Setup.app'\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
talloc_free(ta_ctx);
|
||||
return true;
|
||||
|
||||
coreaudio_error:
|
||||
talloc_free(ta_ctx);
|
||||
return false;
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
/*
|
||||
* Copyright (C) 2002 Anders Johansson ajh@atri.curtin.edu.au
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
@@ -17,15 +15,11 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_DSP_H
|
||||
#define MPLAYER_DSP_H
|
||||
#ifndef MPV_COREAUDIO_CHMAP_H
|
||||
#define MPV_COREAUDIO_CHMAP_H
|
||||
|
||||
/* Implementation of routines used for DSP */
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
|
||||
/* Size of floating point type used in routines */
|
||||
#define FLOAT_TYPE float
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device);
|
||||
|
||||
#include "window.h"
|
||||
#include "filter.h"
|
||||
|
||||
#endif /* MPLAYER_DSP_H */
|
||||
#endif
|
||||
+205
-388
@@ -41,297 +41,100 @@
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao_coreaudio_chmap.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
|
||||
static bool ca_format_is_digital(AudioStreamBasicDescription asbd)
|
||||
{
|
||||
switch (asbd.mFormatID)
|
||||
case 'IAC3':
|
||||
case 'iac3':
|
||||
case kAudioFormat60958AC3:
|
||||
case kAudioFormatAC3:
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ca_stream_supports_digital(struct ao *ao, AudioStreamID stream)
|
||||
{
|
||||
AudioStreamRangedDescription *formats = NULL;
|
||||
size_t n_formats;
|
||||
|
||||
OSStatus err =
|
||||
CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
CHECK_CA_ERROR("Could not get number of stream formats.");
|
||||
|
||||
for (int i = 0; i < n_formats; i++) {
|
||||
AudioStreamBasicDescription asbd = formats[i].mFormat;
|
||||
ca_print_asbd(ao, "supported format:", &(asbd));
|
||||
if (ca_format_is_digital(asbd)) {
|
||||
talloc_free(formats);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(formats);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ca_device_supports_digital(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
AudioStreamID *streams = NULL;
|
||||
size_t n_streams;
|
||||
|
||||
/* Retrieve all the output streams. */
|
||||
OSStatus err =
|
||||
CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams.");
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
if (ca_stream_supports_digital(ao, streams[i])) {
|
||||
talloc_free(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static OSStatus ca_property_listener(
|
||||
AudioObjectPropertySelector selector,
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
void *talloc_ctx = talloc_new(NULL);
|
||||
|
||||
for (int i = 0; i < n_addresses; i++) {
|
||||
if (addresses[i].mSelector == selector) {
|
||||
if (data) *(volatile int *)data = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
talloc_free(talloc_ctx);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_stream_listener(
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioStreamPropertyPhysicalFormat,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
static OSStatus ca_device_listener(
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
return ca_property_listener(kAudioDevicePropertyDeviceHasChanged,
|
||||
object, n_addresses, addresses, data);
|
||||
}
|
||||
|
||||
static OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid) {
|
||||
*pid = getpid();
|
||||
OSStatus err = CA_SET(device, kAudioDevicePropertyHogMode, pid);
|
||||
if (err != noErr)
|
||||
*pid = -1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid) {
|
||||
if (*pid == getpid()) {
|
||||
*pid = -1;
|
||||
return CA_SET(device, kAudioDevicePropertyHogMode, &pid);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_mixing(struct ao *ao, AudioDeviceID device,
|
||||
uint32_t val, bool *changed) {
|
||||
*changed = false;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertySupportsMixing,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (AudioObjectHasProperty(device, &p_addr)) {
|
||||
OSStatus err;
|
||||
Boolean writeable = 0;
|
||||
err = CA_SETTABLE(device, kAudioDevicePropertySupportsMixing,
|
||||
&writeable);
|
||||
|
||||
if (!CHECK_CA_WARN("can't tell if mixing property is settable")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!writeable)
|
||||
return noErr;
|
||||
|
||||
err = CA_SET(device, kAudioDevicePropertySupportsMixing, &val);
|
||||
if (err != noErr)
|
||||
return err;
|
||||
|
||||
if (!CHECK_CA_WARN("can't set mix mode")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_disable_mixing(struct ao *ao,
|
||||
AudioDeviceID device, bool *changed) {
|
||||
return ca_change_mixing(ao, device, 0, changed);
|
||||
}
|
||||
|
||||
static OSStatus ca_enable_mixing(struct ao *ao,
|
||||
AudioDeviceID device, bool changed) {
|
||||
if (changed) {
|
||||
bool dont_care = false;
|
||||
return ca_change_mixing(ao, device, 1, &dont_care);
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_device_listening(AudioDeviceID device,
|
||||
void *flag, bool enabled)
|
||||
{
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertyDeviceHasChanged,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (enabled) {
|
||||
return AudioObjectAddPropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
} else {
|
||||
return AudioObjectRemovePropertyListener(
|
||||
device, &p_addr, ca_device_listener, flag);
|
||||
}
|
||||
}
|
||||
|
||||
static OSStatus ca_enable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, true);
|
||||
}
|
||||
|
||||
static OSStatus ca_disable_device_listener(AudioDeviceID device, void *flag) {
|
||||
return ca_change_device_listening(device, flag, false);
|
||||
}
|
||||
|
||||
static bool ca_change_format(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format)
|
||||
{
|
||||
OSStatus err = noErr;
|
||||
AudioObjectPropertyAddress p_addr;
|
||||
volatile int stream_format_changed = 0;
|
||||
|
||||
ca_print_asbd(ao, "setting stream format:", &change_format);
|
||||
|
||||
/* Install the callback. */
|
||||
p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioStreamPropertyPhysicalFormat,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
err = AudioObjectAddPropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
if (!CHECK_CA_WARN("can't add property listener during format change")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Change the format. */
|
||||
err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &change_format);
|
||||
if (!CHECK_CA_WARN("error changing physical format")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronious,
|
||||
* it is also not Atomic, in its behaviour.
|
||||
* Therefore we check 5 times before we really give up. */
|
||||
bool format_set = false;
|
||||
for (int i = 0; !format_set && i < 5; i++) {
|
||||
for (int j = 0; !stream_format_changed && j < 50; j++)
|
||||
mp_sleep_us(10000);
|
||||
|
||||
if (stream_format_changed) {
|
||||
stream_format_changed = 0;
|
||||
} else {
|
||||
MP_VERBOSE(ao, "reached timeout\n");
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription actual_format;
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
|
||||
ca_print_asbd(ao, "actual format in use:", &actual_format);
|
||||
if (actual_format.mSampleRate == change_format.mSampleRate &&
|
||||
actual_format.mFormatID == change_format.mFormatID &&
|
||||
actual_format.mFramesPerPacket == change_format.mFramesPerPacket) {
|
||||
format_set = true;
|
||||
}
|
||||
}
|
||||
|
||||
err = AudioObjectRemovePropertyListener(stream, &p_addr, ca_stream_listener,
|
||||
(void *)&stream_format_changed);
|
||||
|
||||
if (!CHECK_CA_WARN("can't remove property listener")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return format_set;
|
||||
}
|
||||
|
||||
|
||||
struct priv {
|
||||
AudioDeviceID device; // selected device
|
||||
|
||||
bool paused;
|
||||
|
||||
// digital render callback
|
||||
// audio render callback
|
||||
AudioDeviceIOProcID render_cb;
|
||||
|
||||
// pid set for hog mode, (-1) means that hog mode on the device was
|
||||
// released. hog mode is exclusive access to a device
|
||||
pid_t hog_pid;
|
||||
|
||||
// stream selected for digital playback by the detection in init
|
||||
AudioStreamID stream;
|
||||
|
||||
// stream index in an AudioBufferList
|
||||
int stream_idx;
|
||||
|
||||
// format we changed the stream to: for the digital case each application
|
||||
// sets the stream format for a device to what it needs
|
||||
// format we changed the stream to, and the original format to restore
|
||||
AudioStreamBasicDescription stream_asbd;
|
||||
AudioStreamBasicDescription original_asbd;
|
||||
|
||||
bool changed_mixing;
|
||||
int stream_asbd_changed;
|
||||
bool reload_requested;
|
||||
|
||||
atomic_bool reload_requested;
|
||||
|
||||
uint32_t hw_latency_us;
|
||||
};
|
||||
|
||||
static OSStatus render_cb_digital(
|
||||
static OSStatus property_listener_cb(
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
struct ao *ao = data;
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
// Check whether we need to reset the compressed output stream.
|
||||
AudioStreamBasicDescription f;
|
||||
OSErr err = CA_GET(p->stream, kAudioStreamPropertyVirtualFormat, &f);
|
||||
CHECK_CA_WARN("could not get stream format");
|
||||
if (err != noErr || !ca_asbd_equals(&p->stream_asbd, &f)) {
|
||||
if (atomic_compare_exchange_strong(&p->reload_requested,
|
||||
&(bool){false}, true))
|
||||
{
|
||||
ao_request_reload(ao);
|
||||
MP_INFO(ao, "Stream format changed! Reloading.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus enable_property_listener(struct ao *ao, bool enabled)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
uint32_t selectors[] = {kAudioDevicePropertyDeviceHasChanged,
|
||||
kAudioHardwarePropertyDevices};
|
||||
AudioDeviceID devs[] = {p->device,
|
||||
kAudioObjectSystemObject};
|
||||
assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs));
|
||||
|
||||
OSStatus status = noErr;
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(devs); n++) {
|
||||
AudioObjectPropertyAddress addr = {
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
.mSelector = selectors[n],
|
||||
};
|
||||
AudioDeviceID device = devs[n];
|
||||
|
||||
OSStatus status2;
|
||||
if (enabled) {
|
||||
status2 = AudioObjectAddPropertyListener(
|
||||
device, &addr, property_listener_cb, ao);
|
||||
} else {
|
||||
status2 = AudioObjectRemovePropertyListener(
|
||||
device, &addr, property_listener_cb, ao);
|
||||
}
|
||||
if (status == noErr)
|
||||
status = status2;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static OSStatus render_cb_compressed(
|
||||
AudioDeviceID device, const AudioTimeStamp *ts,
|
||||
const void *in_data, const AudioTimeStamp *in_ts,
|
||||
AudioBufferList *out_data, const AudioTimeStamp *out_ts, void *ctx)
|
||||
@@ -355,58 +158,112 @@ static OSStatus render_cb_digital(
|
||||
|
||||
ao_read_data(ao, &buf.mData, pseudo_frames, end);
|
||||
|
||||
// Check whether we need to reset the digital output stream.
|
||||
if (p->stream_asbd_changed) {
|
||||
p->stream_asbd_changed = 0;
|
||||
if (!p->reload_requested && ca_stream_supports_digital(ao, p->stream)) {
|
||||
p->reload_requested = true;
|
||||
ao_request_reload(ao);
|
||||
MP_INFO(ao, "Stream format changed! Reloading.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd);
|
||||
// Apparently, audio devices can have multiple sub-streams. It's not clear to
|
||||
// me what devices with multiple streams actually do. So only select the first
|
||||
// one that fulfills some minimum requirements.
|
||||
// If this is not sufficient, we could duplicate the device list entries for
|
||||
// each sub-stream, and make it explicit.
|
||||
static int select_stream(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
AudioStreamID *streams;
|
||||
size_t n_streams;
|
||||
OSStatus err;
|
||||
|
||||
/* Get a list of all the streams on this device. */
|
||||
err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
|
||||
&streams, &n_streams);
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
uint32_t direction;
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyDirection, &direction);
|
||||
CHECK_CA_WARN("could not get stream direction");
|
||||
if (err == noErr && direction != 0) {
|
||||
MP_VERBOSE(ao, "Substream %d is not an output stream.\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (af_fmt_is_pcm(ao->format) || ca_stream_supports_compressed(ao,
|
||||
streams[i]))
|
||||
{
|
||||
MP_VERBOSE(ao, "Using substream %d/%zd.\n", i, n_streams);
|
||||
p->stream = streams[i];
|
||||
p->stream_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
if (p->stream_idx < 0) {
|
||||
MP_ERR(ao, "No useable substream found.\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
coreaudio_error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int find_best_format(struct ao *ao, AudioStreamBasicDescription *out_fmt)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
// Build ASBD for the input format
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
ca_print_asbd(ao, "our format:", &asbd);
|
||||
|
||||
*out_fmt = (AudioStreamBasicDescription){0};
|
||||
|
||||
AudioStreamRangedDescription *formats;
|
||||
size_t n_formats;
|
||||
OSStatus err;
|
||||
|
||||
err = CA_GET_ARY(p->stream, kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
CHECK_CA_ERROR("could not get number of stream formats");
|
||||
|
||||
for (int j = 0; j < n_formats; j++) {
|
||||
AudioStreamBasicDescription *stream_asbd = &formats[j].mFormat;
|
||||
|
||||
ca_print_asbd(ao, "- ", stream_asbd);
|
||||
|
||||
if (!out_fmt->mFormatID || ca_asbd_is_better(&asbd, out_fmt, stream_asbd))
|
||||
*out_fmt = *stream_asbd;
|
||||
}
|
||||
|
||||
talloc_free(formats);
|
||||
|
||||
if (!out_fmt->mFormatID) {
|
||||
MP_ERR(ao, "no format found\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
coreaudio_error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
OSStatus err = ca_select_device(ao, ao->device, &p->device);
|
||||
CHECK_CA_ERROR("failed to select device");
|
||||
CHECK_CA_ERROR_L(coreaudio_error_nounlock, "failed to select device");
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
bool supports_digital = false;
|
||||
/* Probe whether device support S/PDIF stream output if input is AC3 stream,
|
||||
* or anything else IEC61937-framed. */
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
if (ca_device_supports_digital(ao, p->device))
|
||||
supports_digital = true;
|
||||
if (!af_fmt_is_pcm(ao->format) && !af_fmt_is_spdif(ao->format)) {
|
||||
MP_ERR(ao, "Unsupported format.\n");
|
||||
goto coreaudio_error_nounlock;
|
||||
}
|
||||
|
||||
if (!supports_digital) {
|
||||
MP_ERR(ao, "selected device doesn't support digital formats\n");
|
||||
goto coreaudio_error;
|
||||
} // closes if (!supports_digital)
|
||||
|
||||
// Build ASBD for the input format
|
||||
AudioStreamBasicDescription asbd;
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
|
||||
return init_digital(ao, asbd);
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = noErr;
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(p->device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_WARN("could not check whether device is alive");
|
||||
@@ -420,107 +277,69 @@ static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
|
||||
err = ca_disable_mixing(ao, p->device, &p->changed_mixing);
|
||||
CHECK_CA_WARN("failed to disable mixing");
|
||||
|
||||
AudioStreamID *streams;
|
||||
size_t n_streams;
|
||||
|
||||
/* Get a list of all the streams on this device. */
|
||||
err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
|
||||
&streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams");
|
||||
|
||||
for (int i = 0; i < n_streams && p->stream_idx < 0; i++) {
|
||||
bool digital = ca_stream_supports_digital(ao, streams[i]);
|
||||
|
||||
if (digital) {
|
||||
err = CA_GET(streams[i], kAudioStreamPropertyPhysicalFormat,
|
||||
&p->original_asbd);
|
||||
if (!CHECK_CA_WARN("could not get stream's physical format to "
|
||||
"revert to, getting the next one"))
|
||||
continue;
|
||||
|
||||
AudioStreamRangedDescription *formats;
|
||||
size_t n_formats;
|
||||
|
||||
err = CA_GET_ARY(streams[i],
|
||||
kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
||||
continue; // try next one
|
||||
|
||||
int req_rate_format = -1;
|
||||
int max_rate_format = -1;
|
||||
|
||||
p->stream = streams[i];
|
||||
p->stream_idx = i;
|
||||
|
||||
for (int j = 0; j < n_formats; j++)
|
||||
if (ca_format_is_digital(formats[j].mFormat)) {
|
||||
// select the digital format that has exactly the same
|
||||
// samplerate. If an exact match cannot be found, select
|
||||
// the format with highest samplerate as backup.
|
||||
if (formats[j].mFormat.mSampleRate == asbd.mSampleRate) {
|
||||
req_rate_format = j;
|
||||
break;
|
||||
} else if (max_rate_format < 0 ||
|
||||
formats[j].mFormat.mSampleRate >
|
||||
formats[max_rate_format].mFormat.mSampleRate)
|
||||
max_rate_format = j;
|
||||
}
|
||||
|
||||
if (req_rate_format >= 0)
|
||||
p->stream_asbd = formats[req_rate_format].mFormat;
|
||||
else
|
||||
p->stream_asbd = formats[max_rate_format].mFormat;
|
||||
|
||||
talloc_free(formats);
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
if (p->stream_idx < 0) {
|
||||
MP_WARN(ao , "can't find any digital output stream format\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
if (!ca_change_format(ao, p->stream, p->stream_asbd))
|
||||
if (select_stream(ao) < 0)
|
||||
goto coreaudio_error;
|
||||
|
||||
void *changed = (void *) &(p->stream_asbd_changed);
|
||||
err = ca_enable_device_listener(p->device, changed);
|
||||
CHECK_CA_ERROR("cannot install format change listener during init");
|
||||
AudioStreamBasicDescription hwfmt;
|
||||
if (find_best_format(ao, &hwfmt) < 0)
|
||||
goto coreaudio_error;
|
||||
|
||||
if (p->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
|
||||
MP_WARN(ao, "stream has non-native byte order, output may fail\n");
|
||||
err = CA_GET(p->stream, kAudioStreamPropertyPhysicalFormat,
|
||||
&p->original_asbd);
|
||||
CHECK_CA_ERROR("could not get stream's original physical format");
|
||||
|
||||
// Even if changing the physical format fails, we can try using the current
|
||||
// virtual format.
|
||||
ca_change_physical_format_sync(ao, p->stream, hwfmt);
|
||||
|
||||
if (!ca_init_chmap(ao, p->device))
|
||||
goto coreaudio_error;
|
||||
|
||||
err = CA_GET(p->stream, kAudioStreamPropertyVirtualFormat, &p->stream_asbd);
|
||||
CHECK_CA_ERROR("could not get stream's virtual format");
|
||||
|
||||
ca_print_asbd(ao, "virtual format", &p->stream_asbd);
|
||||
|
||||
int new_format = ca_asbd_to_mp_format(&p->stream_asbd);
|
||||
|
||||
// If both old and new formats are spdif, avoid changing it due to the
|
||||
// imperfect mapping between mp and CA formats.
|
||||
if (!(af_fmt_is_spdif(ao->format) && af_fmt_is_spdif(new_format)))
|
||||
ao->format = new_format;
|
||||
|
||||
if (!ao->format || af_fmt_is_planar(ao->format)) {
|
||||
MP_ERR(ao, "hardware format not supported\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
ao->samplerate = p->stream_asbd.mSampleRate;
|
||||
ao->bps = ao->samplerate *
|
||||
(p->stream_asbd.mBytesPerPacket /
|
||||
p->stream_asbd.mFramesPerPacket);
|
||||
|
||||
uint32_t latency_frames = 0;
|
||||
err = CA_GET_O(p->device, kAudioDevicePropertyLatency, &latency_frames);
|
||||
if (err != noErr) {
|
||||
CHECK_CA_WARN("cannot get device latency");
|
||||
latency_frames = 0;
|
||||
if (ao->channels.num != p->stream_asbd.mChannelsPerFrame) {
|
||||
// We really expect that ca_init_chmap() fixes the layout to the HW's.
|
||||
MP_ERR(ao, "number of channels changed, and unknown channel layout!\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
p->hw_latency_us = ca_frames_to_us(ao, latency_frames);
|
||||
p->hw_latency_us = ca_get_device_latency_us(ao, p->device);
|
||||
MP_VERBOSE(ao, "base latency: %d microseconds\n", (int)p->hw_latency_us);
|
||||
|
||||
err = enable_property_listener(ao, true);
|
||||
CHECK_CA_ERROR("cannot install format change listener during init");
|
||||
|
||||
err = AudioDeviceCreateIOProcID(p->device,
|
||||
(AudioDeviceIOProc)render_cb_digital,
|
||||
(AudioDeviceIOProc)render_cb_compressed,
|
||||
(void *)ao,
|
||||
&p->render_cb);
|
||||
CHECK_CA_ERROR("failed to register digital render callback");
|
||||
CHECK_CA_ERROR("failed to register audio render callback");
|
||||
|
||||
return CONTROL_TRUE;
|
||||
|
||||
coreaudio_error:
|
||||
err = enable_property_listener(ao, false);
|
||||
CHECK_CA_WARN("can't remove format change listener");
|
||||
err = ca_unlock_device(p->device, &p->hog_pid);
|
||||
CHECK_CA_WARN("can't release hog mode");
|
||||
coreaudio_error_nounlock:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
@@ -529,8 +348,7 @@ static void uninit(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = noErr;
|
||||
|
||||
void *changed = (void *) &(p->stream_asbd_changed);
|
||||
err = ca_disable_device_listener(p->device, changed);
|
||||
err = enable_property_listener(ao, false);
|
||||
CHECK_CA_WARN("can't remove device listener, this may cause a crash");
|
||||
|
||||
err = AudioDeviceStop(p->device, p->render_cb);
|
||||
@@ -539,7 +357,7 @@ static void uninit(struct ao *ao)
|
||||
err = AudioDeviceDestroyIOProcID(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("failed to remove device render callback");
|
||||
|
||||
if (!ca_change_format(ao, p->stream, p->original_asbd))
|
||||
if (!ca_change_physical_format_sync(ao, p->stream, p->original_asbd))
|
||||
MP_WARN(ao, "can't revert to original device format");
|
||||
|
||||
err = ca_enable_mixing(ao, p->device, p->changed_mixing);
|
||||
@@ -554,7 +372,7 @@ static void audio_pause(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
OSStatus err = AudioDeviceStop(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("can't stop digital device");
|
||||
CHECK_CA_WARN("can't stop audio device");
|
||||
}
|
||||
|
||||
static void audio_resume(struct ao *ao)
|
||||
@@ -562,7 +380,7 @@ static void audio_resume(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
OSStatus err = AudioDeviceStart(p->device, p->render_cb);
|
||||
CHECK_CA_WARN("can't start digital device");
|
||||
CHECK_CA_WARN("can't start audio device");
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
@@ -577,7 +395,6 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
|
||||
.list_devs = ca_get_device_list,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv){
|
||||
.stream_asbd_changed = 0,
|
||||
.hog_pid = -1,
|
||||
.stream = 0,
|
||||
.stream_idx = -1,
|
||||
|
||||
+350
-29
@@ -28,6 +28,7 @@
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "osdep/semaphore.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
CFStringRef cfstr_from_cstr(char *str)
|
||||
@@ -88,7 +89,7 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
OSStatus err = noErr;
|
||||
*device = kAudioObjectUnknown;
|
||||
|
||||
if (name) {
|
||||
if (name && name[0]) {
|
||||
CFStringRef uid = cfstr_from_cstr(name);
|
||||
AudioValueTranslation v = (AudioValueTranslation) {
|
||||
.mInputData = &uid,
|
||||
@@ -116,9 +117,8 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
|
||||
if (mp_msg_test(ao->log, MSGL_V)) {
|
||||
char *desc;
|
||||
err = CA_GET_STR(*device, kAudioObjectPropertyName, &desc);
|
||||
CHECK_CA_WARN("could not get selected audio device name");
|
||||
if (err == noErr) {
|
||||
OSStatus err2 = CA_GET_STR(*device, kAudioObjectPropertyName, &desc);
|
||||
if (err2 == noErr) {
|
||||
MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n",
|
||||
desc, *device);
|
||||
talloc_free(desc);
|
||||
@@ -129,7 +129,7 @@ coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
char *fourcc_repr(void *talloc_ctx, uint32_t code)
|
||||
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.
|
||||
@@ -146,64 +146,119 @@ char *fourcc_repr(void *talloc_ctx, uint32_t code)
|
||||
valid_fourcc = false;
|
||||
}
|
||||
|
||||
char *repr;
|
||||
if (valid_fourcc)
|
||||
repr = talloc_asprintf(talloc_ctx, "'%c%c%c%c'",
|
||||
fcc[0], fcc[1], fcc[2], fcc[3]);
|
||||
snprintf(buf, buf_size, "'%c%c%c%c'", fcc[0], fcc[1], fcc[2], fcc[3]);
|
||||
else
|
||||
repr = talloc_asprintf(NULL, "%u", (unsigned int)code);
|
||||
snprintf(buf, buf_size, "%u", (unsigned int)code);
|
||||
|
||||
return repr;
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
{
|
||||
if (code == noErr) return true;
|
||||
|
||||
char *error_string = fourcc_repr(NULL, code);
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, error_string);
|
||||
talloc_free(error_string);
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, fourcc_repr(code));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd)
|
||||
static void ca_fill_asbd_raw(AudioStreamBasicDescription *asbd, int mp_format,
|
||||
int samplerate, int num_channels)
|
||||
{
|
||||
asbd->mSampleRate = ao->samplerate;
|
||||
asbd->mSampleRate = samplerate;
|
||||
// Set "AC3" for other spdif formats too - unknown if that works.
|
||||
asbd->mFormatID = AF_FORMAT_IS_IEC61937(ao->format) ?
|
||||
asbd->mFormatID = af_fmt_is_spdif(mp_format) ?
|
||||
kAudioFormat60958AC3 :
|
||||
kAudioFormatLinearPCM;
|
||||
asbd->mChannelsPerFrame = ao->channels.num;
|
||||
asbd->mBitsPerChannel = af_fmt2bits(ao->format);
|
||||
asbd->mChannelsPerFrame = num_channels;
|
||||
asbd->mBitsPerChannel = af_fmt_to_bytes(mp_format) * 8;
|
||||
asbd->mFormatFlags = kAudioFormatFlagIsPacked;
|
||||
|
||||
if ((ao->format & AF_FORMAT_TYPE_MASK) == AF_FORMAT_F)
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsFloat;
|
||||
int channels_per_buffer = num_channels;
|
||||
if (af_fmt_is_planar(mp_format)) {
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsNonInterleaved;
|
||||
channels_per_buffer = 1;
|
||||
}
|
||||
|
||||
if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_SI)
|
||||
if (af_fmt_is_float(mp_format)) {
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsFloat;
|
||||
} else if (!af_fmt_is_unsigned(mp_format)) {
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsSignedInteger;
|
||||
}
|
||||
|
||||
if (BYTE_ORDER == BIG_ENDIAN)
|
||||
asbd->mFormatFlags |= kAudioFormatFlagIsBigEndian;
|
||||
|
||||
asbd->mFramesPerPacket = 1;
|
||||
asbd->mBytesPerPacket = asbd->mBytesPerFrame =
|
||||
asbd->mFramesPerPacket * asbd->mChannelsPerFrame *
|
||||
asbd->mFramesPerPacket * channels_per_buffer *
|
||||
(asbd->mBitsPerChannel / 8);
|
||||
}
|
||||
|
||||
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
ca_fill_asbd_raw(asbd, ao->format, ao->samplerate, ao->channels.num);
|
||||
}
|
||||
|
||||
bool ca_formatid_is_compressed(uint32_t formatid)
|
||||
{
|
||||
switch (formatid)
|
||||
case 'IAC3':
|
||||
case 'iac3':
|
||||
case kAudioFormat60958AC3:
|
||||
case kAudioFormatAC3:
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// This might be wrong, but for now it's sufficient for us.
|
||||
static uint32_t ca_normalize_formatid(uint32_t formatID)
|
||||
{
|
||||
return ca_formatid_is_compressed(formatID) ? kAudioFormat60958AC3 : formatID;
|
||||
}
|
||||
|
||||
bool ca_asbd_equals(const AudioStreamBasicDescription *a,
|
||||
const AudioStreamBasicDescription *b)
|
||||
{
|
||||
int flags = kAudioFormatFlagIsPacked | kAudioFormatFlagIsFloat |
|
||||
kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsBigEndian;
|
||||
bool spdif = ca_formatid_is_compressed(a->mFormatID) &&
|
||||
ca_formatid_is_compressed(b->mFormatID);
|
||||
|
||||
return (a->mFormatFlags & flags) == (b->mFormatFlags & flags) &&
|
||||
a->mBitsPerChannel == b->mBitsPerChannel &&
|
||||
ca_normalize_formatid(a->mFormatID) ==
|
||||
ca_normalize_formatid(b->mFormatID) &&
|
||||
(spdif || a->mBytesPerPacket == b->mBytesPerPacket) &&
|
||||
(spdif || a->mChannelsPerFrame == b->mChannelsPerFrame) &&
|
||||
a->mSampleRate == b->mSampleRate;
|
||||
}
|
||||
|
||||
// Return the AF_FORMAT_* (AF_FORMAT_S16 etc.) corresponding to the asbd.
|
||||
int ca_asbd_to_mp_format(const AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
|
||||
AudioStreamBasicDescription mp_asbd = {0};
|
||||
ca_fill_asbd_raw(&mp_asbd, fmt, asbd->mSampleRate, asbd->mChannelsPerFrame);
|
||||
if (ca_asbd_equals(&mp_asbd, asbd))
|
||||
return af_fmt_is_spdif(fmt) ? AF_FORMAT_S_AC3 : fmt;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ca_print_asbd(struct ao *ao, const char *description,
|
||||
const AudioStreamBasicDescription *asbd)
|
||||
{
|
||||
uint32_t flags = asbd->mFormatFlags;
|
||||
char *format = fourcc_repr(NULL, asbd->mFormatID);
|
||||
char *format = fourcc_repr(asbd->mFormatID);
|
||||
int mpfmt = ca_asbd_to_mp_format(asbd);
|
||||
|
||||
MP_VERBOSE(ao,
|
||||
"%s %7.1fHz %" PRIu32 "bit [%s]"
|
||||
"[%" PRIu32 "][%" PRIu32 "][%" PRIu32 "]"
|
||||
"[%" PRIu32 "][%" PRIu32 "] "
|
||||
"%s %s %s%s%s%s\n",
|
||||
"%s %7.1fHz %" PRIu32 "bit %s "
|
||||
"[%" PRIu32 "][%" PRIu32 "bpp][%" PRIu32 "fbp]"
|
||||
"[%" PRIu32 "bpf][%" PRIu32 "ch] "
|
||||
"%s %s %s%s%s%s (%s)\n",
|
||||
description, asbd->mSampleRate, asbd->mBitsPerChannel, format,
|
||||
asbd->mFormatFlags, asbd->mBytesPerPacket, asbd->mFramesPerPacket,
|
||||
asbd->mBytesPerFrame, asbd->mChannelsPerFrame,
|
||||
@@ -212,9 +267,49 @@ void ca_print_asbd(struct ao *ao, const char *description,
|
||||
(flags & kAudioFormatFlagIsSignedInteger) ? "S" : "U",
|
||||
(flags & kAudioFormatFlagIsPacked) ? " packed" : "",
|
||||
(flags & kAudioFormatFlagIsAlignedHigh) ? " aligned" : "",
|
||||
(flags & kAudioFormatFlagIsNonInterleaved) ? " P" : "");
|
||||
(flags & kAudioFormatFlagIsNonInterleaved) ? " P" : "",
|
||||
mpfmt ? af_fmt_to_str(mpfmt) : "-");
|
||||
}
|
||||
|
||||
talloc_free(format);
|
||||
// Return whether new is an improvement over old. Assume a higher value means
|
||||
// better quality, and we always prefer the value closest to the requested one,
|
||||
// which is still larger than the requested one.
|
||||
// Equal values prefer the new one (so ca_asbd_is_better() checks other params).
|
||||
static bool value_is_better(double req, double old, double new)
|
||||
{
|
||||
if (new >= req) {
|
||||
return old < req || new <= old;
|
||||
} else {
|
||||
return old < req && new >= old;
|
||||
}
|
||||
}
|
||||
|
||||
// Return whether new is an improvement over old (req is the requested format).
|
||||
bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
AudioStreamBasicDescription *old,
|
||||
AudioStreamBasicDescription *new)
|
||||
{
|
||||
if (new->mChannelsPerFrame > MP_NUM_CHANNELS)
|
||||
return false;
|
||||
if (old->mChannelsPerFrame > MP_NUM_CHANNELS)
|
||||
return true;
|
||||
if (req->mFormatID != new->mFormatID)
|
||||
return false;
|
||||
if (req->mFormatID != old->mFormatID)
|
||||
return true;
|
||||
|
||||
if (!value_is_better(req->mBitsPerChannel, old->mBitsPerChannel,
|
||||
new->mBitsPerChannel))
|
||||
return false;
|
||||
|
||||
if (!value_is_better(req->mSampleRate, old->mSampleRate, new->mSampleRate))
|
||||
return false;
|
||||
|
||||
if (!value_is_better(req->mChannelsPerFrame, old->mChannelsPerFrame,
|
||||
new->mChannelsPerFrame))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
||||
@@ -232,3 +327,229 @@ int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
|
||||
return (out - now) * 1e-3;
|
||||
}
|
||||
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream)
|
||||
{
|
||||
AudioStreamRangedDescription *formats = NULL;
|
||||
size_t n_formats;
|
||||
|
||||
OSStatus err =
|
||||
CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats,
|
||||
&formats, &n_formats);
|
||||
|
||||
CHECK_CA_ERROR("Could not get number of stream formats.");
|
||||
|
||||
for (int i = 0; i < n_formats; i++) {
|
||||
AudioStreamBasicDescription asbd = formats[i].mFormat;
|
||||
if (ca_formatid_is_compressed(asbd.mFormatID)) {
|
||||
talloc_free(formats);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(formats);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
AudioStreamID *streams = NULL;
|
||||
size_t n_streams;
|
||||
|
||||
/* Retrieve all the output streams. */
|
||||
OSStatus err =
|
||||
CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams.");
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
if (ca_stream_supports_compressed(ao, streams[i])) {
|
||||
talloc_free(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid)
|
||||
{
|
||||
*pid = getpid();
|
||||
OSStatus err = CA_SET(device, kAudioDevicePropertyHogMode, pid);
|
||||
if (err != noErr)
|
||||
*pid = -1;
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid)
|
||||
{
|
||||
if (*pid == getpid()) {
|
||||
*pid = -1;
|
||||
return CA_SET(device, kAudioDevicePropertyHogMode, &pid);
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static OSStatus ca_change_mixing(struct ao *ao, AudioDeviceID device,
|
||||
uint32_t val, bool *changed)
|
||||
{
|
||||
*changed = false;
|
||||
|
||||
AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) {
|
||||
.mSelector = kAudioDevicePropertySupportsMixing,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
if (AudioObjectHasProperty(device, &p_addr)) {
|
||||
OSStatus err;
|
||||
Boolean writeable = 0;
|
||||
err = CA_SETTABLE(device, kAudioDevicePropertySupportsMixing,
|
||||
&writeable);
|
||||
|
||||
if (!CHECK_CA_WARN("can't tell if mixing property is settable")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (!writeable)
|
||||
return noErr;
|
||||
|
||||
err = CA_SET(device, kAudioDevicePropertySupportsMixing, &val);
|
||||
if (err != noErr)
|
||||
return err;
|
||||
|
||||
if (!CHECK_CA_WARN("can't set mix mode")) {
|
||||
return err;
|
||||
}
|
||||
|
||||
*changed = true;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed)
|
||||
{
|
||||
return ca_change_mixing(ao, device, 0, changed);
|
||||
}
|
||||
|
||||
OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed)
|
||||
{
|
||||
if (changed) {
|
||||
bool dont_care = false;
|
||||
return ca_change_mixing(ao, device, 1, &dont_care);
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
uint32_t latency_frames = 0;
|
||||
uint32_t latency_properties[] = {
|
||||
kAudioDevicePropertyLatency,
|
||||
kAudioDevicePropertyBufferFrameSize,
|
||||
kAudioDevicePropertySafetyOffset,
|
||||
};
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(latency_properties); n++) {
|
||||
uint32_t temp;
|
||||
OSStatus err = CA_GET_O(device, latency_properties[n], &temp);
|
||||
CHECK_CA_WARN("cannot get device latency");
|
||||
if (err == noErr) {
|
||||
latency_frames += temp;
|
||||
MP_VERBOSE(ao, "Latency property %s: %d frames\n",
|
||||
fourcc_repr(latency_properties[n]), (int)temp);
|
||||
}
|
||||
}
|
||||
|
||||
return ca_frames_to_us(ao, latency_frames);
|
||||
}
|
||||
|
||||
static OSStatus ca_change_format_listener(
|
||||
AudioObjectID object, uint32_t n_addresses,
|
||||
const AudioObjectPropertyAddress addresses[],
|
||||
void *data)
|
||||
{
|
||||
sem_t *sem = data;
|
||||
sem_post(sem);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format)
|
||||
{
|
||||
OSStatus err = noErr;
|
||||
bool format_set = false;
|
||||
|
||||
ca_print_asbd(ao, "setting stream physical format:", &change_format);
|
||||
|
||||
sem_t wakeup;
|
||||
if (sem_init(&wakeup, 0, 0)) {
|
||||
MP_WARN(ao, "OOM\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription prev_format;
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &prev_format);
|
||||
CHECK_CA_ERROR("can't get current physical format");
|
||||
|
||||
/* Install the callback. */
|
||||
AudioObjectPropertyAddress p_addr = {
|
||||
.mSelector = kAudioStreamPropertyPhysicalFormat,
|
||||
.mScope = kAudioObjectPropertyScopeGlobal,
|
||||
.mElement = kAudioObjectPropertyElementMaster,
|
||||
};
|
||||
|
||||
err = AudioObjectAddPropertyListener(stream, &p_addr,
|
||||
ca_change_format_listener,
|
||||
&wakeup);
|
||||
CHECK_CA_ERROR("can't add property listener during format change");
|
||||
|
||||
/* Change the format. */
|
||||
err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &change_format);
|
||||
CHECK_CA_WARN("error changing physical format");
|
||||
|
||||
/* The AudioStreamSetProperty is not only asynchronous,
|
||||
* it is also not Atomic, in its behaviour. */
|
||||
struct timespec timeout = mp_rel_time_to_timespec(0.5);
|
||||
AudioStreamBasicDescription actual_format = {0};
|
||||
while (1) {
|
||||
err = CA_GET(stream, kAudioStreamPropertyPhysicalFormat, &actual_format);
|
||||
if (!CHECK_CA_WARN("could not retrieve physical format"))
|
||||
break;
|
||||
|
||||
format_set = ca_asbd_equals(&change_format, &actual_format);
|
||||
if (format_set)
|
||||
break;
|
||||
|
||||
if (sem_timedwait(&wakeup, &timeout)) {
|
||||
MP_VERBOSE(ao, "reached timeout\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ca_print_asbd(ao, "actual format in use:", &actual_format);
|
||||
|
||||
if (!format_set) {
|
||||
MP_WARN(ao, "changing physical format failed\n");
|
||||
// Some drivers just fuck up and get into a broken state. Restore the
|
||||
// old format in this case.
|
||||
err = CA_SET(stream, kAudioStreamPropertyPhysicalFormat, &prev_format);
|
||||
CHECK_CA_WARN("error restoring physical format");
|
||||
}
|
||||
|
||||
err = AudioObjectRemovePropertyListener(stream, &p_addr,
|
||||
ca_change_format_listener,
|
||||
&wakeup);
|
||||
CHECK_CA_ERROR("can't remove property listener");
|
||||
|
||||
coreaudio_error:
|
||||
sem_destroy(&wakeup);
|
||||
return format_set;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
CFStringRef cfstr_from_cstr(char *str);
|
||||
char *cfstr_get_cstr(CFStringRef cfstr);
|
||||
|
||||
char *fourcc_repr(void *talloc_ctx, uint32_t code);
|
||||
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) \
|
||||
@@ -52,11 +54,28 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
|
||||
void ca_get_device_list(struct ao *ao, struct ao_device_list *list);
|
||||
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device);
|
||||
|
||||
bool ca_formatid_is_compressed(uint32_t formatid);
|
||||
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd);
|
||||
void ca_print_asbd(struct ao *ao, const char *description,
|
||||
const AudioStreamBasicDescription *asbd);
|
||||
bool ca_asbd_equals(const AudioStreamBasicDescription *a,
|
||||
const AudioStreamBasicDescription *b);
|
||||
int ca_asbd_to_mp_format(const AudioStreamBasicDescription *asbd);
|
||||
bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
AudioStreamBasicDescription *old,
|
||||
AudioStreamBasicDescription *new);
|
||||
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
|
||||
bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device);
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream);
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_disable_mixing(struct ao *ao, AudioDeviceID device, bool *changed);
|
||||
OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed);
|
||||
int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device);
|
||||
bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format);
|
||||
|
||||
#endif /* MPV_COREAUDIO_UTILS_H */
|
||||
@@ -288,7 +288,7 @@ static int InitDirectSound(struct ao *ao)
|
||||
/* 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 hearable when it will have the focus.
|
||||
* 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
|
||||
@@ -444,7 +444,7 @@ static int init(struct ao *ao)
|
||||
int format = af_fmt_from_planar(ao->format);
|
||||
int rate = ao->samplerate;
|
||||
|
||||
if (!AF_FORMAT_IS_IEC61937(format)) {
|
||||
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))
|
||||
@@ -456,7 +456,7 @@ static int init(struct ao *ao)
|
||||
case AF_FORMAT_U8:
|
||||
break;
|
||||
default:
|
||||
if (AF_FORMAT_IS_IEC61937(format))
|
||||
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));
|
||||
@@ -465,7 +465,7 @@ static int init(struct ao *ao)
|
||||
//set our audio parameters
|
||||
ao->samplerate = rate;
|
||||
ao->format = format;
|
||||
ao->bps = ao->channels.num * rate * af_fmt2bps(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));
|
||||
@@ -478,7 +478,7 @@ static int init(struct ao *ao)
|
||||
? sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX) : 0;
|
||||
wformat.Format.nChannels = ao->channels.num;
|
||||
wformat.Format.nSamplesPerSec = rate;
|
||||
if (AF_FORMAT_IS_IEC61937(format)) {
|
||||
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;
|
||||
@@ -486,7 +486,7 @@ static int init(struct ao *ao)
|
||||
} else {
|
||||
wformat.Format.wFormatTag = (ao->channels.num > 2)
|
||||
? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM;
|
||||
int bps = af_fmt2bps(format);
|
||||
int bps = af_fmt_to_bytes(format);
|
||||
wformat.Format.wBitsPerSample = bps * 8;
|
||||
wformat.Format.nBlockAlign = wformat.Format.nChannels * bps;
|
||||
}
|
||||
@@ -616,9 +616,9 @@ static int check_free_buffer_size(struct ao *ao)
|
||||
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 postion of the play cursor
|
||||
// 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 postion where we actually write the data to
|
||||
// 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
|
||||
|
||||
+18
-24
@@ -25,7 +25,6 @@
|
||||
#include <limits.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/audioconvert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
@@ -59,33 +58,28 @@ struct priv {
|
||||
bool shutdown;
|
||||
};
|
||||
|
||||
static void select_format(struct ao *ao, AVCodec *codec)
|
||||
static bool supports_format(AVCodec *codec, int format)
|
||||
{
|
||||
int best_score = INT_MIN;
|
||||
int best_format = 0;
|
||||
|
||||
// Check the encoder's list of supported formats.
|
||||
for (const enum AVSampleFormat *sampleformat = codec->sample_fmts;
|
||||
sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
|
||||
++sampleformat)
|
||||
{
|
||||
int fmt = af_from_avformat(*sampleformat);
|
||||
if (!fmt) {
|
||||
MP_WARN(ao, "unsupported lavc format %s\n",
|
||||
av_get_sample_fmt_name(*sampleformat));
|
||||
continue;
|
||||
}
|
||||
int score = af_format_conversion_score(fmt, ao->format);
|
||||
if (score > best_score) {
|
||||
best_score = score;
|
||||
best_format = fmt;
|
||||
}
|
||||
if (af_from_avformat(*sampleformat) == format)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (best_format) {
|
||||
ao->format = best_format;
|
||||
} else {
|
||||
MP_ERR(ao, "sample format not found\n"); // shouldn't happen
|
||||
static void select_format(struct ao *ao, AVCodec *codec)
|
||||
{
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(ao->format, formats);
|
||||
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
if (supports_format(codec, formats[n])) {
|
||||
ao->format = formats[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +130,7 @@ static int init(struct ao *ao)
|
||||
|
||||
select_format(ao, codec);
|
||||
|
||||
ac->sample_size = af_fmt2bps(ao->format);
|
||||
ac->sample_size = af_fmt_to_bytes(ao->format);
|
||||
ac->stream->codec->sample_fmt = af_to_avformat(ao->format);
|
||||
ac->stream->codec->bits_per_raw_sample = ac->sample_size * 8;
|
||||
|
||||
@@ -241,7 +235,7 @@ static int encode(struct ao *ao, double apts, void **data)
|
||||
frame->format = af_to_avformat(ao->format);
|
||||
frame->nb_samples = ac->aframesize;
|
||||
|
||||
size_t num_planes = AF_FORMAT_IS_PLANAR(ao->format) ? ao->channels.num : 1;
|
||||
size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
|
||||
assert(num_planes <= AV_NUM_DATA_POINTERS);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
frame->extended_data[n] = data[n];
|
||||
@@ -350,7 +344,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
double pts = ectx->last_audio_in_pts;
|
||||
pts += ectx->samples_since_last_pts / (double)ao->samplerate;
|
||||
|
||||
size_t num_planes = AF_FORMAT_IS_PLANAR(ao->format) ? ao->channels.num : 1;
|
||||
size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
|
||||
|
||||
void *tempdata = NULL;
|
||||
void *padded[MP_NUM_CHANNELS];
|
||||
|
||||
+16
-2
@@ -56,6 +56,8 @@ struct priv {
|
||||
// called with sizes not aligned to this, a rounded size will be returned.
|
||||
// (This is not needed by the AO API, but many AOs behave this way.)
|
||||
int outburst; // samples
|
||||
|
||||
char **channel_layouts;
|
||||
};
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -88,8 +90,19 @@ static int init(struct ao *ao)
|
||||
|
||||
ao->untimed = priv->untimed;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
struct mp_chmap_sel sel = {.tmp = ao};
|
||||
if (priv->channel_layouts) {
|
||||
for (int n = 0; priv->channel_layouts[n]; n++) {
|
||||
struct mp_chmap map = {0};
|
||||
if (!mp_chmap_from_str(&map, bstr0(priv->channel_layouts[n]))) {
|
||||
MP_FATAL(ao, "Invalid channel map in option.\n");
|
||||
return -1;
|
||||
}
|
||||
mp_chmap_sel_add_map(&sel, &map);
|
||||
}
|
||||
} else {
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
}
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
|
||||
@@ -231,6 +244,7 @@ const struct ao_driver audio_out_null = {
|
||||
OPT_FLOATRANGE("latency", latency_sec, 0, 0, 100),
|
||||
OPT_FLAG("broken-eof", broken_eof, 0),
|
||||
OPT_FLAG("broken-delay", broken_delay, 0),
|
||||
OPT_STRINGLIST("channel-layouts", channel_layouts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+7
-26
@@ -94,40 +94,20 @@ static const struct mp_chmap oss_layouts[MP_NUM_CHANNELS + 1] = {
|
||||
#define AFMT_S16_NE MP_SELECT_LE_BE(AFMT_S16_LE, AFMT_S16_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_U16_NE) && defined(AFMT_U16_LE) && defined(AFMT_U16_BE)
|
||||
#define AFMT_U16_NE MP_SELECT_LE_BE(AFMT_U16_LE, AFMT_U16_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_U24_NE) && defined(AFMT_U24_LE) && defined(AFMT_U24_BE)
|
||||
#define AFMT_U24_NE MP_SELECT_LE_BE(AFMT_U24_LE, AFMT_U24_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_S24_NE) && defined(AFMT_S24_LE) && defined(AFMT_S24_BE)
|
||||
#define AFMT_S24_NE MP_SELECT_LE_BE(AFMT_S24_LE, AFMT_S24_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_U32_NE) && defined(AFMT_U32_LE) && defined(AFMT_U32_BE)
|
||||
#define AFMT_U32_NE AFMT_U32MP_SELECT_LE_BE(AFMT_U32_LE, AFMT_U32_BE)
|
||||
#endif
|
||||
|
||||
#if !defined(AFMT_S32_NE) && defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
|
||||
#define AFMT_S32_NE AFMT_S32MP_SELECT_LE_BE(AFMT_S32_LE, AFMT_S32_BE)
|
||||
#endif
|
||||
|
||||
static const int format_table[][2] = {
|
||||
{AFMT_U8, AF_FORMAT_U8},
|
||||
{AFMT_S8, AF_FORMAT_S8},
|
||||
{AFMT_U16_NE, AF_FORMAT_U16},
|
||||
{AFMT_S16_NE, AF_FORMAT_S16},
|
||||
#ifdef AFMT_U24_NE
|
||||
{AFMT_U24_NE, AF_FORMAT_U24},
|
||||
#endif
|
||||
#ifdef AFMT_S24_NE
|
||||
{AFMT_S24_NE, AF_FORMAT_S24},
|
||||
#endif
|
||||
#ifdef AFMT_U32_NE
|
||||
{AFMT_U32_NE, AF_FORMAT_U32},
|
||||
#endif
|
||||
#ifdef AFMT_S32_NE
|
||||
{AFMT_S32_NE, AF_FORMAT_S32},
|
||||
#endif
|
||||
@@ -204,7 +184,7 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_OK;
|
||||
#endif
|
||||
|
||||
if (AF_FORMAT_IS_SPECIAL(ao->format))
|
||||
if (!af_fmt_is_pcm(ao->format))
|
||||
return CONTROL_TRUE;
|
||||
|
||||
if ((fd = open(p->oss_mixer_device, O_RDONLY)) != -1) {
|
||||
@@ -267,7 +247,7 @@ static bool try_format(struct ao *ao, int *format)
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
int oss_format = format2oss(*format);
|
||||
if (oss_format == -1 && AF_FORMAT_IS_IEC61937(*format))
|
||||
if (oss_format == -1 && af_fmt_is_spdif(*format))
|
||||
oss_format = AFMT_AC3;
|
||||
|
||||
if (oss_format == -1) {
|
||||
@@ -323,7 +303,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
|
||||
if (AF_FORMAT_IS_IEC61937(format)) {
|
||||
if (af_fmt_is_spdif(format)) {
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &samplerate) == -1)
|
||||
goto fail;
|
||||
// Probably could be fixed by setting number of channels; needs testing.
|
||||
@@ -333,7 +313,8 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
}
|
||||
}
|
||||
|
||||
int try_formats[] = {format, AF_FORMAT_S32, AF_FORMAT_S24, AF_FORMAT_S16, 0};
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
format = try_formats[n];
|
||||
if (try_format(ao, &format))
|
||||
@@ -347,7 +328,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
|
||||
MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(format));
|
||||
|
||||
if (!AF_FORMAT_IS_IEC61937(format)) {
|
||||
if (!af_fmt_is_spdif(format)) {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
for (int n = 0; n < MP_NUM_CHANNELS + 1; n++)
|
||||
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
|
||||
@@ -412,7 +393,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
}
|
||||
}
|
||||
|
||||
p->outburst -= p->outburst % (channels.num * af_fmt2bps(format)); // round down
|
||||
p->outburst -= p->outburst % (channels.num * af_fmt_to_bytes(format)); // round down
|
||||
|
||||
return 0;
|
||||
|
||||
|
||||
+3
-3
@@ -73,7 +73,7 @@ static void fput32le(uint32_t val, FILE *fp)
|
||||
static void write_wave_header(struct ao *ao, FILE *fp, uint64_t data_length)
|
||||
{
|
||||
uint16_t fmt = ao->format == AF_FORMAT_FLOAT ? WAV_ID_FLOAT_PCM : WAV_ID_PCM;
|
||||
int bits = af_fmt2bits(ao->format);
|
||||
int bits = af_fmt_to_bytes(ao->format) * 8;
|
||||
|
||||
// Master RIFF chunk
|
||||
fput32le(WAV_ID_RIFF, fp);
|
||||
@@ -135,7 +135,7 @@ static int init(struct ao *ao)
|
||||
case AF_FORMAT_FLOAT:
|
||||
break;
|
||||
default:
|
||||
if (!AF_FORMAT_IS_IEC61937(ao->format))
|
||||
if (!af_fmt_is_spdif(ao->format))
|
||||
ao->format = AF_FORMAT_S16;
|
||||
break;
|
||||
}
|
||||
@@ -146,7 +146,7 @@ static int init(struct ao *ao)
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
return -1;
|
||||
|
||||
ao->bps = ao->channels.num * ao->samplerate * af_fmt2bps(ao->format);
|
||||
ao->bps = ao->channels.num * ao->samplerate * af_fmt_to_bytes(ao->format);
|
||||
|
||||
MP_INFO(ao, "File: %s (%s)\nPCM: Samplerate: %d Hz Channels: %d Format: %s\n",
|
||||
priv->outputfilename,
|
||||
|
||||
@@ -210,7 +210,7 @@ static pa_encoding_t map_digital_format(int format)
|
||||
case AF_FORMAT_S_AAC: return PA_ENCODING_MPEG2_AAC_IEC61937;
|
||||
#endif
|
||||
default:
|
||||
if (AF_FORMAT_IS_IEC61937(format))
|
||||
if (af_fmt_is_spdif(format))
|
||||
return PA_ENCODING_ANY;
|
||||
return PA_ENCODING_PCM;
|
||||
}
|
||||
|
||||
Loaded 100 of 352 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user