Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca73b609f6 | ||
|
|
08a6827b3d | ||
|
|
2f7dc16995 | ||
|
|
15a6d0028e | ||
|
|
5e608fc4c8 | ||
|
|
546f038ded | ||
|
|
351c083487 | ||
|
|
7beee68f8d | ||
|
|
1298b9d201 | ||
|
|
1a893e8257 | ||
|
|
93ec08cc7f | ||
|
|
4bf9a78b36 | ||
|
|
c75f98e3ab | ||
|
|
034b33d695 | ||
|
|
dee7cdfae7 | ||
|
|
861c10268d | ||
|
|
802f594a85 | ||
|
|
7428272f93 | ||
|
|
4a0fcdfbd8 | ||
|
|
4d1269d9db | ||
|
|
afe713c71c | ||
|
|
cd893626cb | ||
|
|
2e7a4f717c | ||
|
|
5865086aa8 | ||
|
|
20dffe0621 | ||
|
|
f66ee85fdf | ||
|
|
4d2b865088 | ||
|
|
157b242289 | ||
|
|
bca59516cc | ||
|
|
8479449d14 | ||
|
|
36cc33ff5a | ||
|
|
945303a92e | ||
|
|
7f625ea29b | ||
|
|
5056777b86 | ||
|
|
cb52cfae1a | ||
|
|
31bce1cbe7 | ||
|
|
ca97239cb6 | ||
|
|
935846fc40 | ||
|
|
45f8d767b9 | ||
|
|
b13bb9fbdd | ||
|
|
7d5133b713 | ||
|
|
f022bf61a9 | ||
|
|
b50d54ed40 | ||
|
|
3ca9598d5c | ||
|
|
9428294634 | ||
|
|
36099d27f8 | ||
|
|
9fe4e10c7d | ||
|
|
a832624af9 | ||
|
|
5a4a69cb58 | ||
|
|
fba98cfb05 | ||
|
|
4e750e31a1 | ||
|
|
db03f01300 | ||
|
|
40ebedabcf | ||
|
|
47ed894d19 | ||
|
|
1d46368404 | ||
|
|
58479906d8 | ||
|
|
fae9d7744c | ||
|
|
5df811bd53 | ||
|
|
eb08cd75c1 | ||
|
|
25525cee62 | ||
|
|
3101cb3847 | ||
|
|
fea87c4253 | ||
|
|
814869759c | ||
|
|
b24bd4e570 | ||
|
|
d828887792 | ||
|
|
0b3d1d6faf | ||
|
|
43d575616c | ||
|
|
05b392bc94 | ||
|
|
1f01c05fef | ||
|
|
d65b6cf077 | ||
|
|
c0eea89b4d | ||
|
|
37ec321287 | ||
|
|
e5b2af0a80 | ||
|
|
1b19ba6c57 | ||
|
|
a645c6b2ec | ||
|
|
11289d5238 | ||
|
|
98b8e60b14 | ||
|
|
7d8eee36a5 | ||
|
|
78fe41f246 | ||
|
|
88498bcc92 | ||
|
|
407f5b407a | ||
|
|
9fa0e6bf6a | ||
|
|
e5f884e68c | ||
|
|
76d8683e2e | ||
|
|
fb9bbf2a0d | ||
|
|
e8b073584d | ||
|
|
14602b0201 | ||
|
|
b092bb0f11 | ||
|
|
a469554594 | ||
|
|
e02c9b9902 | ||
|
|
0ab3184526 | ||
|
|
958053ff56 | ||
|
|
1a339fa09d | ||
|
|
f18c4175ad | ||
|
|
60dade1040 | ||
|
|
e51cf79181 | ||
|
|
36b0dea89e | ||
|
|
a9c2a8e162 | ||
|
|
02d40eee1b | ||
|
|
4c6f36611d | ||
|
|
e60728a622 | ||
|
|
ba3d90d9ed | ||
|
|
1ccc56eff0 | ||
|
|
ed94f8dc00 | ||
|
|
49bc07faea | ||
|
|
9f2970f28a | ||
|
|
11f915f5ef | ||
|
|
df65ac95ba | ||
|
|
b18399befe | ||
|
|
a79327189e | ||
|
|
3c2d20e414 | ||
|
|
8f67fa13f1 | ||
|
|
6bd2bdc745 | ||
|
|
044f1ffcd5 | ||
|
|
76211609e3 | ||
|
|
dc16d85379 | ||
|
|
59d422f042 | ||
|
|
7dd69ef77c | ||
|
|
5731597342 | ||
|
|
a208179ffe | ||
|
|
d6af6efbf9 | ||
|
|
63441d36ef | ||
|
|
2a28712b44 | ||
|
|
fff5fda74b | ||
|
|
eb33556cbf | ||
|
|
b4173c1fd2 | ||
|
|
67ce9813d6 | ||
|
|
4fc5c1fa13 | ||
|
|
9c639016f8 | ||
|
|
ba19ffe19a | ||
|
|
a702cff794 | ||
|
|
137e34e3e9 | ||
|
|
0e340ce804 | ||
|
|
6c8362ef54 | ||
|
|
8135e25600 | ||
|
|
bfc33da250 | ||
|
|
05e75e7946 | ||
|
|
d2349cb833 | ||
|
|
a5610b2a0d | ||
|
|
c6b9288465 | ||
|
|
c767451796 | ||
|
|
4b32d51b96 | ||
|
|
d8807ca833 | ||
|
|
ce4d227986 | ||
|
|
0be3a94e0b | ||
|
|
36565c099d | ||
|
|
44f00a1f58 | ||
|
|
205b2da160 | ||
|
|
1c550326ce | ||
|
|
9825bbb8cf | ||
|
|
67689ff6b4 | ||
|
|
76844c9c51 | ||
|
|
65f0825315 | ||
|
|
6435d9ae7f | ||
|
|
2e768ad0d8 | ||
|
|
7af6cabcba | ||
|
|
78227706ad | ||
|
|
20a1f250c6 | ||
|
|
b3baff399e | ||
|
|
c490b3f8ea | ||
|
|
f2b026f941 | ||
|
|
f4351e6f7d | ||
|
|
ff24285eb1 | ||
|
|
e7e06a47a0 | ||
|
|
020730da0b | ||
|
|
435bc003c0 | ||
|
|
10584159df | ||
|
|
11c573fda0 | ||
|
|
41d559c4a9 | ||
|
|
b2dc69e347 | ||
|
|
ed7bc3a5f3 | ||
|
|
8f1c40f702 | ||
|
|
b8de7d6ff3 | ||
|
|
c2a24323bc | ||
|
|
028e51d8af | ||
|
|
3d88e6f4c2 | ||
|
|
f9bcb5c42c | ||
|
|
7bfb240309 | ||
|
|
3ca0a7fd4d | ||
|
|
67b36c66d3 | ||
|
|
4e7cbb7606 | ||
|
|
9ee9313465 | ||
|
|
bd62d78854 | ||
|
|
3c123281a7 | ||
|
|
4b48966d87 | ||
|
|
66810c1550 | ||
|
|
17f58455b0 | ||
|
|
401bd57d44 | ||
|
|
c647516278 | ||
|
|
987eecdb5a | ||
|
|
fdb39f313b | ||
|
|
4381753207 | ||
|
|
07915b1227 | ||
|
|
46d2f1f08d | ||
|
|
9de51b6032 | ||
|
|
e00ca83006 | ||
|
|
614ad62f89 | ||
|
|
567df04012 | ||
|
|
8f82dc92aa | ||
|
|
dd357a7d53 | ||
|
|
abaab930f0 | ||
|
|
c59ebbe399 | ||
|
|
9efb0278e7 | ||
|
|
36161f0456 | ||
|
|
c33faee6ba | ||
|
|
cacb0ad3dc | ||
|
|
576dabf654 | ||
|
|
ec625266c8 | ||
|
|
84aa9e7146 | ||
|
|
9a47023c44 | ||
|
|
b04f0cad43 | ||
|
|
9eadc068fa | ||
|
|
b0951d71f8 | ||
|
|
e3b3e28deb | ||
|
|
14b429de8d | ||
|
|
5a8c48fde2 | ||
|
|
596f66cccf | ||
|
|
428fc1cbef | ||
|
|
c338c0d90a | ||
|
|
e3e2c794ef | ||
|
|
af9c6c1133 | ||
|
|
cdbd20581e | ||
|
|
f60826c3a1 | ||
|
|
6d7cfdfae5 | ||
|
|
5532d8cffe | ||
|
|
98f871a261 | ||
|
|
8ed76d2561 | ||
|
|
ef402a1c8c | ||
|
|
4655923d38 | ||
|
|
52dd38a48a | ||
|
|
0b4120919a | ||
|
|
f81ae9c3fc | ||
|
|
7d10728aaa | ||
|
|
965ba23303 | ||
|
|
9975835bde | ||
|
|
9aa0638f0f | ||
|
|
59a04562b1 | ||
|
|
aaa076b631 | ||
|
|
b4c6fb0f52 | ||
|
|
a04ac8204f | ||
|
|
af7b412d1c | ||
|
|
fbcf2bf207 | ||
|
|
ebf7c04057 | ||
|
|
bfb3a78964 | ||
|
|
9b0102dd8b | ||
|
|
4aa1be44c2 | ||
|
|
290341c777 | ||
|
|
e42a194062 | ||
|
|
2c572e2bb1 | ||
|
|
9ca1a1b068 | ||
|
|
8163b8d390 | ||
|
|
569383bc54 | ||
|
|
e1b4e5e727 | ||
|
|
38e5b141c6 | ||
|
|
bd0a636d8b | ||
|
|
b99da1c0a5 | ||
|
|
2edf00fb94 | ||
|
|
03791fae16 | ||
|
|
4d9c6ab6b9 | ||
|
|
a7f3cf4737 | ||
|
|
410a1b49ed | ||
|
|
782fa455b5 | ||
|
|
b2073d08b0 | ||
|
|
0c2f11cee6 | ||
|
|
749f5c8d65 | ||
|
|
12b90e744d | ||
|
|
047eb1b914 | ||
|
|
ef2225c4a2 | ||
|
|
4485e205b9 | ||
|
|
b0da883b13 | ||
|
|
4961682f1e | ||
|
|
7d0285b5bb | ||
|
|
c15af6630f | ||
|
|
2f20168b0b | ||
|
|
f40e0cb0f2 | ||
|
|
5f41fbb6d9 | ||
|
|
075aa21797 | ||
|
|
93fb79166b | ||
|
|
8ecd404264 | ||
|
|
013a8f75f3 | ||
|
|
0a9ab1b076 | ||
|
|
ba68e570de | ||
|
|
dae88644e6 | ||
|
|
ca2c5c26d5 | ||
|
|
4d281927e5 | ||
|
|
8bfeecbc6f | ||
|
|
33cffdcbac | ||
|
|
f0223e1b83 | ||
|
|
496b13227b | ||
|
|
a76eecdc2c | ||
|
|
ae2972557f | ||
|
|
d7b6ebf643 | ||
|
|
42fd7d0bbf | ||
|
|
ad3f6d2f97 | ||
|
|
79f82a2ca4 | ||
|
|
ed73ba8964 | ||
|
|
f706076dd1 | ||
|
|
0200a71e2f | ||
|
|
8ce5e794fb | ||
|
|
70ea586eb5 | ||
|
|
775b86212d | ||
|
|
c917992359 | ||
|
|
8288fa6978 | ||
|
|
3075017ebf | ||
|
|
16eca7139a | ||
|
|
16d033814c | ||
|
|
e4cb6fd0fd | ||
|
|
ecf4d7a843 | ||
|
|
1b786a71c1 | ||
|
|
628805866d | ||
|
|
fd90c003d2 | ||
|
|
14c2f20bff | ||
|
|
6f5f92feab | ||
|
|
4184f8585d | ||
|
|
39e03f6072 | ||
|
|
55c88fdb8f | ||
|
|
9daa842b5f | ||
|
|
e0c67977b2 | ||
|
|
6f27a165a8 | ||
|
|
4a5a4b676e | ||
|
|
9f802d134b | ||
|
|
3263f47d33 | ||
|
|
7ee01860d9 | ||
|
|
b037121430 | ||
|
|
e76fda8594 | ||
|
|
d6921678b9 | ||
|
|
70b74d32cd | ||
|
|
b6c7d899ca | ||
|
|
74c3c2ccb4 | ||
|
|
aa974b2aa7 | ||
|
|
38d614d8d6 | ||
|
|
938ad6ebc0 | ||
|
|
a4c436bac2 | ||
|
|
0874e4e461 | ||
|
|
fe23715876 | ||
|
|
3325c7a912 | ||
|
|
bb07b22d42 | ||
|
|
4d6601924a | ||
|
|
7fff1b6c10 | ||
|
|
1f2d8ed01c | ||
|
|
66dfb96fa1 | ||
|
|
fc76d41194 | ||
|
|
4527409c8d | ||
|
|
7f714c6984 | ||
|
|
5cc796dacc | ||
|
|
ed13206a18 | ||
|
|
441e384390 | ||
|
|
1f881eca65 | ||
|
|
b9e7478760 | ||
|
|
5d6fb5267d | ||
|
|
13b90bcf91 | ||
|
|
4a579ad166 | ||
|
|
b67d2ede67 | ||
|
|
288ed66a35 | ||
|
|
0dbad9503f | ||
|
|
fca64d913b | ||
|
|
a2cf846a5c | ||
|
|
d969a0a27d | ||
|
|
a17456608c | ||
|
|
31ee350cce | ||
|
|
6751b5b1c2 | ||
|
|
eb224a4a55 | ||
|
|
d0afd37709 | ||
|
|
8f9785d128 | ||
|
|
f17246fec1 | ||
|
|
ceca1602e9 | ||
|
|
223821d91c | ||
|
|
d7c38a0b23 | ||
|
|
7b5a2588bd | ||
|
|
706bb1d0c7 | ||
|
|
3ffa70e2da | ||
|
|
251f4e5d77 | ||
|
|
0ec0c147ed | ||
|
|
76947798ea | ||
|
|
e167812406 | ||
|
|
2cce782527 | ||
|
|
4107a8be6c | ||
|
|
6b2b2b75b9 | ||
|
|
d6890c19dd | ||
|
|
830f0aed97 | ||
|
|
1dcf511376 | ||
|
|
02f9087de9 | ||
|
|
562f563ff1 | ||
|
|
1b80e124db | ||
|
|
7d2228c673 | ||
|
|
44dc79dcb0 | ||
|
|
8762818dd2 | ||
|
|
c82fed85b9 | ||
|
|
3f73520897 | ||
|
|
c5e4538bc4 | ||
|
|
235eb60671 | ||
|
|
abf2efb107 | ||
|
|
f670c64e59 | ||
|
|
57228b6581 | ||
|
|
664e8fe66a | ||
|
|
976daf1942 | ||
|
|
bf3549839e | ||
|
|
3e71eb8676 | ||
|
|
268431c858 | ||
|
|
40f29a33df | ||
|
|
7916201401 | ||
|
|
733bcc03f6 | ||
|
|
3a2bc158bb | ||
|
|
9f595f3a80 | ||
|
|
7b1e73139f | ||
|
|
bff8cfe3f0 | ||
|
|
ff08df5bb1 | ||
|
|
4e7f4f10ce | ||
|
|
4c2edecd7d | ||
|
|
20df21746a | ||
|
|
000a0e2775 | ||
|
|
6161cfd781 | ||
|
|
b01623e0d2 | ||
|
|
9282a34fbf | ||
|
|
e3d93fde2f | ||
|
|
0870859e3d | ||
|
|
5997248505 | ||
|
|
f151ac57cb | ||
|
|
f92e45bb8c | ||
|
|
92778873ad | ||
|
|
b79190561f | ||
|
|
09af5760bb | ||
|
|
2f6dc93276 | ||
|
|
beb8d27912 | ||
|
|
59f9547fb5 | ||
|
|
e34c5dc17c | ||
|
|
f4f24c105f | ||
|
|
d7db42d27f | ||
|
|
3d4071e6e5 | ||
|
|
87d8f292f5 | ||
|
|
07c54d8c5c | ||
|
|
42125844f9 | ||
|
|
2e8bb48ae8 | ||
|
|
34fe10e159 | ||
|
|
880ea467ca | ||
|
|
c1b15ae437 | ||
|
|
9224ae4fff | ||
|
|
7393f4d320 | ||
|
|
60d3327b0b | ||
|
|
a4392168f9 | ||
|
|
4f7a56e0c5 | ||
|
|
7019e0dcfe | ||
|
|
171ec0a7e4 | ||
|
|
debc17663d | ||
|
|
afb167cfd2 | ||
|
|
1742614505 | ||
|
|
8b3306924d | ||
|
|
4b567aeac8 | ||
|
|
a9f97b26d8 | ||
|
|
e197ca3dd5 | ||
|
|
7f3c7100d5 | ||
|
|
eaa97daf65 | ||
|
|
196950e905 | ||
|
|
76e7e78ce9 | ||
|
|
054c02ad64 | ||
|
|
6d36fad83c | ||
|
|
eaced0ebb0 | ||
|
|
1d5991ef30 | ||
|
|
3d367e009c | ||
|
|
0366ba2531 | ||
|
|
b9f804b566 | ||
|
|
76276c9210 | ||
|
|
edb4970ca8 | ||
|
|
93403b13a4 | ||
|
|
2a0f9fc158 | ||
|
|
3c1566e736 | ||
|
|
20cc22437e | ||
|
|
ef324e1316 | ||
|
|
3766024dcd | ||
|
|
8c8dcc698b | ||
|
|
7eb3427573 | ||
|
|
ce42a96533 | ||
|
|
f8263e82cc | ||
|
|
573bfae7e4 | ||
|
|
e7febd6184 | ||
|
|
5441a12a1e | ||
|
|
3deef308c8 | ||
|
|
415fc6e327 | ||
|
|
d8b013d458 | ||
|
|
0d359879c9 | ||
|
|
9e64b93822 | ||
|
|
11f5713e3b | ||
|
|
cabfbc34b5 | ||
|
|
bd25fc5307 | ||
|
|
d36ff64b29 | ||
|
|
3dd5621cec | ||
|
|
04be2b43ec | ||
|
|
e6e6b0dcc7 | ||
|
|
ab868fe425 | ||
|
|
f6d02457e2 | ||
|
|
28021feabb | ||
|
|
50bf59b652 | ||
|
|
123e0239a0 | ||
|
|
c88ab96a78 | ||
|
|
70b2be3cf7 | ||
|
|
0cd501dac1 | ||
|
|
0d964471b8 | ||
|
|
828f38e10d | ||
|
|
db08e28304 | ||
|
|
45ce4ba877 | ||
|
|
dba143de4d | ||
|
|
6827901230 | ||
|
|
4d87c700e0 | ||
|
|
082029f850 | ||
|
|
ca67928d7a | ||
|
|
c59db62f51 | ||
|
|
cda7203934 | ||
|
|
0536ef306c | ||
|
|
7533d3c3a8 | ||
|
|
2115e2306e | ||
|
|
d652d479f1 | ||
|
|
1d3b680427 | ||
|
|
cc3cdcb0f0 | ||
|
|
0d9ca23b35 | ||
|
|
da662ef182 | ||
|
|
4f49334318 | ||
|
|
342e36ea11 | ||
|
|
07753bbb4a | ||
|
|
0192eeb09c | ||
|
|
e7ea893c2f | ||
|
|
e2a176ede2 | ||
|
|
86004909ac | ||
|
|
17e7f653d3 | ||
|
|
3a76bfdccc | ||
|
|
845f32732f | ||
|
|
48fa7da865 | ||
|
|
a5f53da229 | ||
|
|
cbfc001918 | ||
|
|
6d4b4c0de3 | ||
|
|
23edaf4412 | ||
|
|
83ab873497 | ||
|
|
d53c0604ca | ||
|
|
69d062ce37 | ||
|
|
55993fde6d | ||
|
|
b478d2b1ce | ||
|
|
2d6fdccb92 | ||
|
|
48143b0cf5 | ||
|
|
d040ab1119 | ||
|
|
7816a61609 | ||
|
|
c0cc145069 | ||
|
|
4a11c28237 | ||
|
|
2d345c59d6 | ||
|
|
e3bee23fe4 | ||
|
|
154ff98128 | ||
|
|
0a406f97e0 | ||
|
|
6e974f77bd | ||
|
|
cd8daee3d3 | ||
|
|
57f43c35ec | ||
|
|
212f83ba2e | ||
|
|
b62066433d | ||
|
|
87d3af6f19 | ||
|
|
5103b5dc2c | ||
|
|
b71c8251b0 | ||
|
|
6632d6e287 | ||
|
|
ebe0f5d313 | ||
|
|
b1a11191fd | ||
|
|
34cf655ddd | ||
|
|
cf8855cd2e | ||
|
|
442ff93626 | ||
|
|
1eec7d2315 | ||
|
|
8e1390e734 | ||
|
|
95dce50347 | ||
|
|
88c29b1301 | ||
|
|
185e63a3e2 | ||
|
|
cf411a9489 | ||
|
|
a9a4d6349a | ||
|
|
baa18f76ca | ||
|
|
e894f75bb5 | ||
|
|
f798bc3c25 | ||
|
|
9c22108fec | ||
|
|
6092c967ab | ||
|
|
ad1d845682 | ||
|
|
877775f84e | ||
|
|
c809b73db6 | ||
|
|
3cb616a286 | ||
|
|
48943a73f6 | ||
|
|
08bcf1d92d | ||
|
|
722f9b63c2 | ||
|
|
6aad532aa3 | ||
|
|
3bf7df4a5e | ||
|
|
828bd2963c | ||
|
|
89f81da481 | ||
|
|
97816bbef0 | ||
|
|
0da5688c84 | ||
|
|
33e5755c23 | ||
|
|
2dd020efc2 | ||
|
|
d7188ce753 | ||
|
|
8457287bd0 | ||
|
|
49e704cb19 | ||
|
|
a90abe0546 | ||
|
|
dc21473907 | ||
|
|
ed6ae656b0 | ||
|
|
7677c7c32c | ||
|
|
4a6bb49215 | ||
|
|
ff506c1e49 | ||
|
|
d9ca235c68 | ||
|
|
5e50fe3049 | ||
|
|
d480b1261b | ||
|
|
f6a582e0b2 | ||
|
|
5cbadbe721 | ||
|
|
0030e049cd | ||
|
|
451fc931b0 | ||
|
|
8a2db0c4b1 | ||
|
|
016c4405fb | ||
|
|
9708248eb3 | ||
|
|
b51f6c59b9 | ||
|
|
882d8ece57 | ||
|
|
a4eda0c984 | ||
|
|
8b7da7a8e5 | ||
|
|
c8d955571d | ||
|
|
e2f71f509f | ||
|
|
e530783cdb | ||
|
|
98d62c49bd | ||
|
|
5f8402e3ec | ||
|
|
69ae23fdd1 | ||
|
|
31718c48ce | ||
|
|
d588bdaaf7 | ||
|
|
12c6700a3c | ||
|
|
019d594d0b | ||
|
|
97b1482d53 | ||
|
|
5abe0bd593 | ||
|
|
58e201e6bd | ||
|
|
286d421666 | ||
|
|
a6aab5dfd6 | ||
|
|
2d1769a534 | ||
|
|
a42b8b1142 | ||
|
|
80540be211 | ||
|
|
b138bdc01c | ||
|
|
8b0a111c59 | ||
|
|
dcda8bd36a | ||
|
|
bded247fb5 | ||
|
|
a3c9685257 | ||
|
|
6186cc79e6 | ||
|
|
fb1c7bde42 | ||
|
|
f2f91cf570 | ||
|
|
5feaaba0fd | ||
|
|
885497a445 | ||
|
|
4e34615872 | ||
|
|
ad50e640dc |
No files matched your search
@@ -12,7 +12,7 @@ Releases are listed here: https://github.com/mpv-player/mpv/releases
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to
|
||||
http://sprunge.us or a similar site, and replace this text with a link to it.
|
||||
https://0x0.st/ or a similar site, and replace this text with a link to it.
|
||||
|
||||
Providing a log file is strongly encouraged. It is very helpful for reproducing
|
||||
bugs or getting important technical context. Without a log it might not be
|
||||
|
||||
+16
-22
@@ -1,20 +1,26 @@
|
||||
language: c
|
||||
language: generic
|
||||
|
||||
os:
|
||||
- linux
|
||||
#- osx
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
sudo: required
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- LIBAV=ffmpeg-git
|
||||
- LIBAV=libav-git
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh CC=gcc
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:stable-deps CI_SCRIPT=ci/build-tumbleweed.sh CC=clang
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:snapshot-deps CI_SCRIPT=ci/build-tumbleweed.sh CC=gcc
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci:snapshot-deps CI_SCRIPT=ci/build-tumbleweed.sh CC=clang
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:i686 CI_SCRIPT=ci/build-mingw64.sh TARGET=i686-w64-mingw32
|
||||
- CONTAINER=registry.cirno.systems/kiwi/containers/mpv-ci-mingw64:x86_64 CI_SCRIPT=ci/build-mingw64.sh TARGET=x86_64-w64-mingw32
|
||||
global:
|
||||
# Coverity token
|
||||
- secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
|
||||
# Travis token for mpv.io
|
||||
- secure: "nlTVLJK6kRhtXvhKCoJ3YdFGHuKaq/eHowfPw25hqRWuBOZd+HjHY5KIYjV7SxuKFDpJE4GpNcvA3Q31nsqomxpkLYgrwjg6TSazN7ZP+x85ZgV1QGFebrPfGm2n5UR5CAPAwFoeF3pZheLi4bajVzwq1fWW+x3grS188P9OZso="
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
|
||||
branches:
|
||||
only:
|
||||
@@ -22,22 +28,10 @@ branches:
|
||||
- ci
|
||||
- coverity_scan
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: LIBAV=libav-git
|
||||
- os: osx
|
||||
env: LIBAV=ffmpeg-git
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
- os: linux
|
||||
compiler: clang
|
||||
|
||||
before_install: TOOLS/travis-deps libass-stable $LIBAV
|
||||
before_install:
|
||||
- docker pull $CONTAINER
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
- docker run --env CC --env TARGET -v $TRAVIS_BUILD_DIR:/build $CONTAINER /bin/sh -c "cd /build && $CI_SCRIPT"
|
||||
after_failure: cat ./build/config.log
|
||||
after_script: TOOLS/travis-rebuild-website
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ licensed as LGPLv2.1+. Also see the list of files with specific licenses below
|
||||
All new contributions must be LGPLv2.1+ licensed. Using a more liberal license
|
||||
compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
If changes are done on GPL code, must come with the implicit agreement that the
|
||||
Changes done to GPL code must come with the implicit/explicit agreement that the
|
||||
project can relicense the changes to LGPLv2.1+ at a later point without asking
|
||||
the contributor. This is a safeguard for making potential relicensing of
|
||||
remaining GPL code to LGPLv2.1+ easier.
|
||||
@@ -34,8 +34,6 @@ a LGPL mode to mpv, MPlayer code had to be relicensed from GPLv2+ to LGPLv2.1+
|
||||
by asking the MPlayer authors for permission. Since permission could not be
|
||||
obtained from everyone, LGPL mode disables the following features, some of
|
||||
them quite central:
|
||||
- no audio filtering, which breaks: --af, pitch correction, fine control over
|
||||
downmix/upmix/resampling behavior
|
||||
- Linux X11 video output
|
||||
- BSD audio output via OSS
|
||||
- NVIDIA/Linux hardware decoding (vdpau, although nvdec usually works)
|
||||
@@ -47,15 +45,8 @@ at all.
|
||||
|
||||
The following files are still GPL only (--enable-lgpl disables them):
|
||||
|
||||
audio/filter/* will be replaced with new filter chain
|
||||
audio/filter/af_format.c mostly LGPL (except af glue code)
|
||||
audio/filter/af_lavc3enc.c as above
|
||||
audio/filter/af_lavfi.c as above
|
||||
audio/filter/af_scaletempo.c as above
|
||||
audio/filter/af_rubberband.c as above
|
||||
audio/out/ao_jack.c will stay GPL
|
||||
audio/out/ao_oss.c will stay GPL
|
||||
audio/audio.* needed by af code only
|
||||
demux/demux_tv.c will stay GPL
|
||||
stream/ai_* will stay GPL (TV code)
|
||||
stream/audio_in.* will stay GPL (TV code)
|
||||
@@ -88,7 +79,6 @@ The following files are still GPL only (--enable-lgpl disables them):
|
||||
The following files contain some optional GPL code (--enable-lgpl disables it):
|
||||
|
||||
options/parse_commandline.c dvd:// expansion
|
||||
player/audio.c libaf glue code
|
||||
|
||||
None of the exceptions listed above affect the final binary if it's built as
|
||||
LGPL. Linked libraries still can affect the final license (for example if
|
||||
|
||||
@@ -33,6 +33,61 @@ API changes
|
||||
::
|
||||
|
||||
--- mpv 0.29.0 ---
|
||||
1.101 - add MPV_RENDER_PARAM_ADVANCED_CONTROL and related API
|
||||
- add MPV_RENDER_PARAM_NEXT_FRAME_INFO and related symbols
|
||||
- add MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME
|
||||
- add MPV_RENDER_PARAM_SKIP_RENDERING
|
||||
- add mpv_render_context_get_info()
|
||||
1.100 - bump API number to avoid confusion with mpv release versions
|
||||
- actually apply the GL_MP_MPGetNativeDisplay change for the new render
|
||||
API. This also means compatibility for anything but x11 and wayland
|
||||
through the old opengl-cb GL_MP_MPGetNativeDisplay method is now
|
||||
unsupported.
|
||||
- deprecate mpv_get_wakeup_pipe(). It's complex, but easy to replace
|
||||
using normal API (just set a wakeup callback to a function which
|
||||
writes to a pipe).
|
||||
- add a 1st class hook API, which replaces the hacky mpv_command()
|
||||
based one. The old API is deprecated and will be removed soon. The
|
||||
old API was never meant to be stable, while the new API is.
|
||||
1.29 - the behavior of mpv_terminate_destroy() and mpv_detach_destroy()
|
||||
changes subtly (see documentation in the header file). In particular,
|
||||
mpv_detach_destroy() will not leave the player running in all
|
||||
situations anymore (it gets closer to refcounting).
|
||||
- rename mpv_detach_destroy() to mpv_destroy() (the old function will
|
||||
remain valid as deprecated alias)
|
||||
- add mpv_create_weak_client(), which makes use of above changes
|
||||
- MPV_EVENT_SHUTDOWN is now returned exactly once if a mpv_handle
|
||||
should terminate, instead of spamming the event queue with this event
|
||||
1.28 - deprecate the render opengl_cb API, and replace it with render.h
|
||||
and render_gl.h. The goal is allowing support for APIs other than
|
||||
OpenGL. The old API is emulated with the new API.
|
||||
Likewise, the "opengl-cb" VO is renamed to "libmpv".
|
||||
mpv_get_sub_api() is deprecated along the opengl_cb API.
|
||||
The new API is relatively similar, but not the same. The rough
|
||||
equivalents are:
|
||||
mpv_opengl_cb_init_gl => mpv_render_context_create
|
||||
mpv_opengl_cb_set_update_callback => mpv_render_context_set_update_callback
|
||||
mpv_opengl_cb_draw => mpv_render_context_render
|
||||
mpv_opengl_cb_report_flip => mpv_render_context_report_swap
|
||||
mpv_opengl_cb_uninit_gl => mpv_render_context_free
|
||||
The VO opengl-cb is also renamed to "libmpv".
|
||||
Also, the GL_MP_MPGetNativeDisplay pseudo extension is not used by the
|
||||
render API anymore, and the old opengl-cb API only handles the "x11"
|
||||
and "wl" names anymore. Support for everything else has been removed.
|
||||
The new render API uses proper API parameters, e.g. for X11 you pass
|
||||
MPV_RENDER_PARAM_X11_DISPLAY directly.
|
||||
- deprecate the qthelper.hpp header file. This provided some C++ helper
|
||||
utility functions for Qt with use of libmpv. There is no reason to
|
||||
keep this in the mpv git repository, nor to make it part of the libmpv
|
||||
API. If you're using this header, you can safely copy it into your
|
||||
project - it uses only libmpv public API. Alternatively, it could be
|
||||
maintained in a separate repository by interested parties.
|
||||
1.27 - make opengl-cb the default VO. This causes a subtle behavior change
|
||||
if the API user called mpv_opengl_cb_init_gl(), but does not set
|
||||
the "vo" option. Before, it would still have used another VO (like
|
||||
on the CLI, e.g. vo=gpu). Now it'll behave as if vo=opengl-cb was
|
||||
used.
|
||||
--- mpv 0.28.0 ---
|
||||
1.26 - remove glMPGetNativeDisplay("drm") support
|
||||
- add mpv_opengl_cb_window_pos and mpv_opengl_cb_drm_params and
|
||||
support via glMPGetNativeDisplay() for using it
|
||||
|
||||
+6
-7
@@ -3,8 +3,7 @@ General usage
|
||||
|
||||
::
|
||||
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] \
|
||||
[-ofps outfps | -oautofps] [-oharddup] [-ocopyts | -orawts] [-oneverdrop] \
|
||||
mpv infile -o outfile [-of outfileformat] [-ofopts formatoptions] [-orawts] \
|
||||
[(any other mpv options)] \
|
||||
-ovc outvideocodec [-ovcopts outvideocodecoptions] \
|
||||
-oac outaudiocodec [-oacopts outaudiocodecoptions]
|
||||
@@ -60,13 +59,13 @@ for.
|
||||
Typical MPEG-4 Part 2 ("ASP", "DivX") encoding, AVI container::
|
||||
|
||||
mpv infile -o outfile.avi \
|
||||
-ofps 25 \
|
||||
--vf=fps=25 \
|
||||
-ovc mpeg4 -ovcopts qscale=4 \
|
||||
-oac libmp3lame -oacopts ab=128k
|
||||
|
||||
Note: AVI does not support variable frame rate, so -ofps must be used. The
|
||||
frame rate should ideally match the input (25 for PAL, 24000/1001 or 30000/1001
|
||||
for NTSC)
|
||||
Note: AVI does not support variable frame rate, so the fps filter must be used.
|
||||
The frame rate should ideally match the input (25 for PAL, 24000/1001 or
|
||||
30000/1001 for NTSC)
|
||||
|
||||
Typical MPEG-4 Part 10 ("AVC", "H.264") encoding, Matroska (MKV) container::
|
||||
|
||||
@@ -129,7 +128,7 @@ What works
|
||||
==========
|
||||
|
||||
* Encoding at variable frame rate (default)
|
||||
* Encoding at constant frame rate using -ofps framerate -oharddup
|
||||
* Encoding at constant frame rate using --vf=fps=RATE
|
||||
* 2-pass encoding (specify flags=+pass1 in the first pass's -ovcopts, specify
|
||||
flags=+pass2 in the second pass)
|
||||
* Hardcoding subtitles using vobsub, ass or srt subtitle rendering (just
|
||||
|
||||
@@ -19,6 +19,94 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.29.0 ---
|
||||
- drop --opensles-sample-rate, as --audio-samplerate should be used if desired
|
||||
- drop deprecated --videotoolbox-format, --ff-aid, --ff-vid, --ff-sid,
|
||||
--ad-spdif-dtshd, --softvol options
|
||||
- fix --external-files: strictly never select any tracks from them, unless
|
||||
explicitly selected (this may or may not be expected)
|
||||
- --ytdl is now always enabled, even for libmpv
|
||||
- add a number of --audio-resample-* options, which should from now on be
|
||||
used instead of --af-defaults=lavrresample:...
|
||||
- deprecate --vf-defaults and --af-defaults. These didn't work with the
|
||||
lavfi bridge, so they have very little use left. The only potential use
|
||||
is with af_lavrresample (going to be deprecated, --audio-resample-... set
|
||||
its defaults), and various hw deinterlacing filters (like vf_vavpp), for
|
||||
which you will have to stop using --deinterlace=yes, and instead use the
|
||||
vf toggle commands and the filter enable/disable flag to customize it.
|
||||
- deprecate --af=lavrresample. Use the ``--audio-resample-...`` options to
|
||||
customize resampling, or the libavfilter ``--af=aresample`` filter.
|
||||
- add --osd-on-seek
|
||||
- remove outfmt sub-parameter from "format" video filter (no replacement)
|
||||
- some behavior changes in the video filter chain, including:
|
||||
- before, using an incompatible filter with hwdec would disable hwdec;
|
||||
now it disables the filter at runtime instead
|
||||
- inserting an incompatible filter with hwdec at runtime would refuse
|
||||
to insert the filter; now it will add it successfully, but disables
|
||||
the filter slightly later
|
||||
- some behavior changes in the audio filter chain, including:
|
||||
- a manually inserted lavrresample filter is not necessarily used for
|
||||
sample format conversion anymore, so it's pretty useless
|
||||
- changing playback speed will not respect --af-defaults anymore
|
||||
- having libavfilter based filters after the scaletempo or rubberband
|
||||
filters is not supported anymore, and may desync if playback speed is
|
||||
changed (libavfilter does not support the metadata for playback speed)
|
||||
- the lavcac3enc filter does not auto detach itself anymore; instead it
|
||||
passes through the data after converting it to the sample rate and
|
||||
channel configuration the ac3 encoder expects; also, if the audio
|
||||
format changes midstream in a way that causes the filter to switch
|
||||
between PCM and AC3 output, the audio output won't be reconfigured,
|
||||
and audio playback will fail due to libswresample being unable to
|
||||
convert between PCM and AC3 (Note: the responsible developer didn't
|
||||
give a shit. Later changes might have improved or worsened this.)
|
||||
- inserting a filter that changes the output sample format will not
|
||||
reconfigure the AO - you need to run an additional "ao-reload"
|
||||
command to force this if you want that
|
||||
- using "strong" gapless audio (--gapless-audio=yes) can fail if the
|
||||
audio formats are not convertible (such as switching between PCM and
|
||||
AC3 passthrough)
|
||||
- if filters do not pass through PTS values correctly, A/V sync can
|
||||
result over time. Some libavfilter filters are known to be affected by
|
||||
this, such as af_loudnorm, which can desync over time, depending on
|
||||
how the audio track was muxed (af_lavfi's fix-pts suboption can help).
|
||||
- remove out-format sub-parameter from "format" audio filter (no replacement)
|
||||
- --lavfi-complex now requires uniquely named filter pads. In addition,
|
||||
unconnected filter pads are not allowed anymore (that means every filter
|
||||
pad must be connected either to another filter, or to a video/audio track
|
||||
or video/audio output). If they are disconnected at runtime, the stream
|
||||
will probably stall.
|
||||
- rename --vo=opengl-cb to --vo=libmpv (goes in hand with the opengl-cb
|
||||
API deprecation, see client-api-changes.rst)
|
||||
- deprecate the OpenGL cocoa backend, option choice --gpu-context=cocoa
|
||||
when used with --gpu-api=opengl (use --vo=libmpv)
|
||||
- make --deinterlace=yes always deinterlace, instead of trying to check
|
||||
certain unreliable video metadata. Also flip the defaults of all builtin
|
||||
HW deinterlace filters to always deinterlace.
|
||||
- change vf_vavpp default to use the best deinterlace algorithm by default
|
||||
- remove a compatibility hack that allowed CLI aliases to be set as property
|
||||
(such as "sub-file"), deprecated in mpv 0.26.0
|
||||
- deprecate the old command based hook API, and introduce a proper C API
|
||||
(the high level Lua API for this does not change)
|
||||
- rename the the lua-settings/ config directory to script-opts/
|
||||
- the way the player waits for scripts getting loaded changes slightly. Now
|
||||
scripts are loaded in parallel, and block the player from continuing
|
||||
playback only in the player initialization phase. It could change again in
|
||||
the future. (This kind of waiting was always a feature to prevent that
|
||||
playback is started while scripts are only half-loaded.)
|
||||
- deprecate --ovoffset, --oaoffset, --ovfirst, --oafirst
|
||||
- remove the following encoding options: --ocopyts (now the default, old
|
||||
timestamp handling is gone), --oneverdrop (now default), --oharddup (you
|
||||
need to use --vf=fps=VALUE), --ofps, --oautofps, --omaxfps
|
||||
- remove --video-stereo-mode. This option was broken out of laziness, and
|
||||
nobody wants to fix it. Automatic 3D down-conversion to 2D is also broken,
|
||||
although you can just insert the stereo3d filter manually. The obscurity
|
||||
of 3D content doesn't justify such an option anyway.
|
||||
- change cycle-values command to use the current value, instead of an
|
||||
internal counter that remembered the current position.
|
||||
- edition and disc title switching will now fully reload playback (may have
|
||||
consequences for scripts, client API, or when using file-local options)
|
||||
- remove deprecated ao/vo auto profiles. Consider using scripts like
|
||||
auto-profiles.lua instead.
|
||||
--- mpv 0.28.0 ---
|
||||
- rename --hwdec=mediacodec option to mediacodec-copy, to reflect
|
||||
conventions followed by other hardware video decoding APIs
|
||||
@@ -59,11 +147,12 @@ Interface changes
|
||||
now.
|
||||
- change the --hwdec option from a choice to a plain string (affects
|
||||
introspection of the option/property), also affects some properties
|
||||
- rename --hwdec=rpi to --hwdec=mmal, sane for the -copy variant (no
|
||||
- rename --hwdec=rpi to --hwdec=mmal, same for the -copy variant (no
|
||||
backwards compatibility)
|
||||
- deprecate the --ff-aid, --ff-vid, -ff-sid options and properties (there is
|
||||
- deprecate the --ff-aid, --ff-vid, --ff-sid options and properties (there is
|
||||
no replacement, but you can manually query the track property and use the
|
||||
"ff-index" field to find the mpv track ID to imitate this behavior)
|
||||
- rename --no-ometadata to --no-ocopy-metadata
|
||||
--- mpv 0.27.0 ---
|
||||
- drop previously deprecated --field-dominance option
|
||||
- drop previously deprecated "osd" command
|
||||
|
||||
+30
-5
@@ -5,7 +5,8 @@ Audio filters allow you to modify the audio stream and its properties. The
|
||||
syntax is:
|
||||
|
||||
``--af=...``
|
||||
Setup a chain of audio filters. See ``--vf`` for the syntax.
|
||||
Setup a chain of audio filters. See ``--vf`` (`VIDEO FILTERS`_) for the
|
||||
full syntax.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -29,6 +30,12 @@ Available filters are:
|
||||
This filter is automatically enabled if the audio output does not support
|
||||
the audio configuration of the file being played.
|
||||
|
||||
.. warning::
|
||||
|
||||
Deprecated. Either use the ``--audio-resample-...`` options to customize
|
||||
resampling, or the libavfilter ``--af=aresample`` filter, which has its
|
||||
own options.
|
||||
|
||||
It supports only the following sample formats: u8, s16, s32, float.
|
||||
|
||||
``filter-size=<length>``
|
||||
@@ -46,7 +53,8 @@ Available filters are:
|
||||
Do not detach if input and output audio format/rate/channels match.
|
||||
(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:...``.)
|
||||
prefer setting them with the ``--audio-resample-...`` options.) This
|
||||
does not do anything anymore and the filter will never detach.
|
||||
``normalize=<yes|no|auto>``
|
||||
Whether to normalize when remixing channel layouts (default: auto).
|
||||
``auto`` uses the value set by ``--audio-normalize-downmix``.
|
||||
@@ -91,7 +99,7 @@ Available filters are:
|
||||
Select the libavcodec encoder used. Currently, this should be an AC-3
|
||||
encoder, and using another codec will fail horribly.
|
||||
|
||||
``format=format:srate:channels:out-format:out-srate:out-channels``
|
||||
``format=format:srate:channels:out-srate:out-channels``
|
||||
Does not do any format conversion itself. Rather, it may cause the
|
||||
filter system to insert necessary conversion filters before or after this
|
||||
filter if needed. It is primarily useful for controlling the audio format
|
||||
@@ -120,8 +128,6 @@ Available filters are:
|
||||
Force mixing to a specific channel layout. See ``--audio-channels`` option
|
||||
for possible values.
|
||||
|
||||
``<out-format>``
|
||||
|
||||
``<out-srate>``
|
||||
|
||||
``<out-channels>``
|
||||
@@ -219,6 +225,12 @@ Available filters are:
|
||||
change the playback pitch at runtime. Note that speed is controlled
|
||||
using the standard ``speed`` property, not ``af-command``.
|
||||
|
||||
``multiply-pitch <factor>``
|
||||
Multiply the current value of ``<pitch-scale>`` dynamically. For
|
||||
example: 0.5 to go down by an octave, 1.5 to go up by a perfect fifth.
|
||||
If you want to go up or down by semi-tones, use 1.059463094352953 and
|
||||
0.9438743126816935
|
||||
|
||||
``lavfi=graph``
|
||||
Filter audio using FFmpeg's libavfilter.
|
||||
|
||||
@@ -233,3 +245,16 @@ Available filters are:
|
||||
|
||||
``o=<string>``
|
||||
AVOptions.
|
||||
|
||||
``fix-pts=<yes|no>``
|
||||
Determine PTS based on sample count (default: no). If this is enabled,
|
||||
the player won't rely on libavfilter passing through PTS accurately.
|
||||
Instead, it pass a sample count as PTS to libavfilter, and compute the
|
||||
PTS used by mpv based on that and the input PTS. This helps with filters
|
||||
which output a recomputed PTS instead of the original PTS (including
|
||||
filters which require the PTS to start at 0). mpv normally expects
|
||||
filters to not touch the PTS (or only to the extent of changing frame
|
||||
boundaries), so this is not the default, but it will be needed to use
|
||||
broken filters. In practice, these broken filters will either cause slow
|
||||
A/V desync over time (with some files), or break playback completely if
|
||||
you seek or start playback from the middle of a file.
|
||||
+19
-5
@@ -27,7 +27,7 @@ Available audio output drivers are:
|
||||
.. warning::
|
||||
|
||||
To get multichannel/surround audio, use ``--audio-channels=auto``. The
|
||||
default for this option is ``auto-safe``, which makes this audio otuput
|
||||
default for this option is ``auto-safe``, which makes this audio output
|
||||
explicitly reject multichannel output, as there is no way to detect
|
||||
whether a certain channel layout is actually supported.
|
||||
|
||||
@@ -110,10 +110,21 @@ Available audio output drivers are:
|
||||
exclusive mode (bypasses the sound server).
|
||||
|
||||
``openal``
|
||||
Experimental OpenAL audio output driver
|
||||
OpenAL audio output driver
|
||||
|
||||
.. note:: This driver is not very useful. Playing multi-channel audio with
|
||||
it is slow.
|
||||
``--openal-num-buffers=<2-128>``
|
||||
Specify the number of audio buffers to use. Lower values are better for
|
||||
lower CPU usage. Default: 4.
|
||||
|
||||
``--openal-num-samples=<256-32768>``
|
||||
Specify the number of complete samples to use for each buffer. Higher
|
||||
values are better for lower CPU usage. Default: 8192.
|
||||
|
||||
``--openal-direct-channels=<yes|no>``
|
||||
Enable OpenAL Soft's direct channel extension when available to avoid
|
||||
tinting the sound with ambisonics or HRTF.
|
||||
Channels are dropped when when they are not available as downmixing
|
||||
will be disabled. Default: no.
|
||||
|
||||
``pulse``
|
||||
PulseAudio audio output driver
|
||||
@@ -128,7 +139,7 @@ Available audio output drivers are:
|
||||
Set the audio buffer size in milliseconds. A higher value buffers
|
||||
more data, and has a lower probability of buffer underruns. A smaller
|
||||
value makes the audio stream react faster, e.g. to playback speed
|
||||
changes. Default: 250.
|
||||
changes.
|
||||
|
||||
``--pulse-latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: no). If
|
||||
@@ -198,6 +209,9 @@ Available audio output drivers are:
|
||||
If not empty, this is a ``,`` separated list of channel layouts the
|
||||
AO allows. This can be used to test channel layout selection.
|
||||
|
||||
``--ao-null-format``
|
||||
Force the audio output format the AO will accept. If unset accepts any.
|
||||
|
||||
``pcm``
|
||||
Raw PCM/WAVE file writer audio output
|
||||
|
||||
|
||||
+36
-72
@@ -8,9 +8,8 @@ You can encode files from one format/codec to another using this facility.
|
||||
|
||||
``--of=<format>``
|
||||
Specifies the output format (overrides autodetection by the file name
|
||||
extension of the file specified by ``-o``). This can be a comma separated
|
||||
list of possible formats to try. See ``--of=help`` for a full list of
|
||||
supported formats.
|
||||
extension of the file specified by ``-o``). See ``--of=help`` for a full
|
||||
list of supported formats.
|
||||
|
||||
``--ofopts=<options>``
|
||||
Specifies the output format options for libavformat.
|
||||
@@ -22,50 +21,16 @@ You can encode files from one format/codec to another using this facility.
|
||||
``--ofopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ofopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ofopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ofopts-clr``
|
||||
``--ofopts=""``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ofps=<float value>``
|
||||
Specifies the output format time base (default: 24000). Low values like 25
|
||||
limit video fps by dropping frames.
|
||||
|
||||
``--oautofps``
|
||||
Sets the output format time base to the guessed frame rate of the input
|
||||
video (simulates MEncoder behavior, useful for AVI; may cause frame drops).
|
||||
Note that not all codecs and not all formats support VFR encoding, and some
|
||||
which do have bugs when a target bitrate is specified - use ``--ofps`` or
|
||||
``--oautofps`` to force CFR encoding in these cases.
|
||||
|
||||
``--omaxfps=<float value>``
|
||||
Specifies the minimum distance of adjacent frames (default: 0, which means
|
||||
unset). Content of lower frame rate is not readjusted to this frame rate;
|
||||
content of higher frame rate is decimated to this frame rate.
|
||||
|
||||
``--oharddup``
|
||||
If set, the frame rate given by ``--ofps`` is attained not by skipping time
|
||||
codes, but by duplicating frames (constant frame rate mode).
|
||||
|
||||
``--oneverdrop``
|
||||
If set, frames are never dropped. Instead, time codes of video are
|
||||
readjusted to always increase. This may cause AV desync, though; to work
|
||||
around this, use a high-fps time base using ``--ofps`` and absolutely
|
||||
avoid ``--oautofps``.
|
||||
|
||||
``--oac=<codec>``
|
||||
Specifies the output audio codec. This can be a comma separated list of
|
||||
possible codecs to try. See ``--oac=help`` for a full list of supported
|
||||
codecs.
|
||||
Specifies the output audio codec. See ``--oac=help`` for a full list of
|
||||
supported codecs.
|
||||
|
||||
``--oaoffset=<value>``
|
||||
Shifts audio data by the given time (in seconds) by adding/removing
|
||||
samples at the start.
|
||||
samples at the start. Deprecated.
|
||||
|
||||
``--oacopts=<options>``
|
||||
Specifies the output audio codec options for libavcodec.
|
||||
@@ -82,30 +47,22 @@ You can encode files from one format/codec to another using this facility.
|
||||
``--oacopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--oacopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--oacopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--oacopts-clr``
|
||||
``--oacopts=""``
|
||||
Completely empties the options list.
|
||||
|
||||
``--oafirst``
|
||||
Force the audio stream to become the first stream in the output.
|
||||
By default, the order is unspecified.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
|
||||
``--ovc=<codec>``
|
||||
Specifies the output video codec. This can be a comma separated list of
|
||||
possible codecs to try. See ``--ovc=help`` for a full list of supported
|
||||
codecs.
|
||||
Specifies the output video codec. See ``--ovc=help`` for a full list of
|
||||
supported codecs.
|
||||
|
||||
``--ovoffset=<value>``
|
||||
Shifts video data by the given time (in seconds) by shifting the pts
|
||||
values.
|
||||
values. Deprecated.
|
||||
|
||||
``--ovcopts <options>``
|
||||
``--ovcopts=<options>``
|
||||
Specifies the output video codec options for libavcodec.
|
||||
See --ovcopts=help for a full list of supported options.
|
||||
|
||||
@@ -123,26 +80,12 @@ You can encode files from one format/codec to another using this facility.
|
||||
``--ovcopts-add=<options1[,options2,...]>``
|
||||
Appends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts-pre=<options1[,options2,...]>``
|
||||
Prepends the options given as arguments to the options list.
|
||||
|
||||
``--ovcopts-del=<index1[,index2,...]>``
|
||||
Deletes the options at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
|
||||
``--ovcopts-clr``
|
||||
``--ovcopts=""``
|
||||
Completely empties the options list.
|
||||
|
||||
``--ovfirst``
|
||||
Force the video stream to become the first stream in the output.
|
||||
By default, the order is unspecified.
|
||||
|
||||
``--ocopyts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
container formats, e.g. AVI). Discontinuities are still fixed.
|
||||
By default, audio pts are set to playback time and video pts are
|
||||
synchronized to match audio pts, as some output formats do not support
|
||||
anything else.
|
||||
By default, the order is unspecified. Deprecated.
|
||||
|
||||
``--orawts``
|
||||
Copies input pts to the output video (not supported by some output
|
||||
@@ -150,6 +93,27 @@ You can encode files from one format/codec to another using this facility.
|
||||
and all pts are passed through as-is. Never seek backwards or use multiple
|
||||
input files in this mode!
|
||||
|
||||
``--no-ometadata``
|
||||
``--no-ocopy-metadata``
|
||||
Turns off copying of metadata from input files to output files when
|
||||
encoding (which is enabled by default).
|
||||
|
||||
``--oset-metadata=<metadata-tag[,metadata-tag,...]>``
|
||||
Specifies metadata to include in the output file.
|
||||
Supported keys vary between output formats. For example, Matroska (MKV) and
|
||||
FLAC allow almost arbitrary keys, while support in MP4 and MP3 is more
|
||||
limited.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oset-metadata=title="Output title",comment="Another tag"``"
|
||||
adds a title and a comment to the output file.
|
||||
|
||||
``--oremove-metadata=<metadata-tag[,metadata-tag,...]>``
|
||||
Specifies metadata to exclude from the output file when copying from the
|
||||
input file.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
"``--oremove-metadata=comment,genre``"
|
||||
excludes copying of the the comment and genre tags to the output
|
||||
file.
|
||||
+87
-58
@@ -499,35 +499,29 @@ Input Commands that are Possibly Subject to Change
|
||||
|
||||
- ``a vf set flip`` turn video upside-down on the ``a`` key
|
||||
- ``b vf set ""`` remove all video filters on ``b``
|
||||
- ``c vf toggle lavfi=gradfun`` toggle debanding on ``c``
|
||||
- ``c vf toggle gradfun`` toggle debanding on ``c``
|
||||
|
||||
.. admonition:: Example how to toggle disabled filters at runtime
|
||||
|
||||
- Add something ``vf-add=@deband:!lavfi=[gradfun]`` to ``mpv.conf``. The
|
||||
``@deband:`` is the label, and ``deband`` is an arbitrary, user-given
|
||||
name for this filter entry. The ``!`` before the filter name disables
|
||||
the filter by default. Everything after this is the normal filter name
|
||||
and the filter parameters.
|
||||
- Add something like ``vf-add=@deband:!gradfun`` to ``mpv.conf``.
|
||||
The ``@deband:`` is the label, an arbitrary, user-given name for this
|
||||
filter entry. The ``!`` before the filter name disables the filter by
|
||||
default. Everything after this is the normal filter name and possibly
|
||||
filter parameters, like in the normal ``--vf`` syntax.
|
||||
- Add ``a vf toggle @deband`` to ``input.conf``. This toggles the
|
||||
"disabled" flag for the filter identified with ``deband``.
|
||||
"disabled" flag for the filter with the label ``deband`` when the
|
||||
``a`` key is hit.
|
||||
|
||||
``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
|
||||
reset to 0 once the last value is reached.
|
||||
|
||||
The internal counter is associated using the property name and the value
|
||||
list. If multiple commands (bound to different keys) use the same name
|
||||
and value list, they will share the internal counter.
|
||||
given property to the next value in the list. The command will use the
|
||||
current value of the property/option, and use it to determine the current
|
||||
position in the list of values. Once it has found it, it will set the
|
||||
next value in the list (wrapping around to the first item if needed).
|
||||
|
||||
The special argument ``!reverse`` can be used to cycle the value list in
|
||||
reverse. Compared with a command that just lists the value in reverse, this
|
||||
command will actually share the internal counter with the forward-cycling
|
||||
key binding (as long as the rest of the arguments are the same).
|
||||
|
||||
Note that there is a static limit of (as of this writing) 10 arguments
|
||||
(this limit could be raised on demand).
|
||||
reverse. The only advantage is that you don't need to reverse the value
|
||||
list yourself when adding a second key binding for cycling backwards.
|
||||
|
||||
``enable-section "<section>" [flags]``
|
||||
Enable all key bindings in the named input section.
|
||||
@@ -729,7 +723,26 @@ Input Commands that are Possibly Subject to Change
|
||||
merely sets all option values listed within the profile.
|
||||
|
||||
``load-script "<path>"``
|
||||
Load a script, similar to the ``--script`` option.
|
||||
Load a script, similar to the ``--script`` option. Whether this waits for
|
||||
the script to finish initialization or not changed multiple times, and the
|
||||
future behavior is left undefined.
|
||||
|
||||
``change-list "<option>" "<operation>" "<value>"``
|
||||
This command changes list options as described in `List Options`_. The
|
||||
``<option>`` parameter is the normal option name, while ``<operation>`` is
|
||||
the suffix or action used on the option.
|
||||
|
||||
Some operations take no value, but the command still requires the value
|
||||
parameter. In these cases, the value must be an empty string.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``change-list glsl-shaders append file.glsl``
|
||||
|
||||
Add a filename to the ``glsl-shaders`` list. The command line
|
||||
equivalent is ``--glsl-shaders-append=file.glsl`` or alternatively
|
||||
``--glsl-shader=file.glsl``.
|
||||
|
||||
|
||||
Undocumented commands: ``tv-last-channel`` (TV/DVB only),
|
||||
``ao-reload`` (experimental/internal).
|
||||
@@ -744,6 +757,49 @@ and obscure way to handle events that require stricter coordination. There are
|
||||
no API stability guarantees made. Not following the protocol exactly can make
|
||||
the player freeze randomly. Basically, nobody should use this API.
|
||||
|
||||
The C API is described in the header files. The Lua API is described in the
|
||||
Lua section.
|
||||
|
||||
The following hooks are currently defined:
|
||||
|
||||
``on_load``
|
||||
Called when a file is to be opened, before anything is actually done.
|
||||
For example, you could read and write the ``stream-open-filename``
|
||||
property to redirect an URL to something else (consider support for
|
||||
streaming sites which rarely give the user a direct media URL), or
|
||||
you could set per-file options with by setting the property
|
||||
``file-local-options/<option name>``. The player will wait until all
|
||||
hooks are run.
|
||||
|
||||
``on_load_fail``
|
||||
Called after after a file has been opened, but failed to. This can be
|
||||
used to provide a fallback in case native demuxers failed to recognize
|
||||
the file, instead of always running before the native demuxers like
|
||||
``on_load``. Demux will only be retried if ``stream-open-filename``
|
||||
was changed.
|
||||
|
||||
``on_preloaded``
|
||||
Called after a file has been opened, and before tracks are selected and
|
||||
decoders are created. This has some usefulness if an API users wants
|
||||
to select tracks manually, based on the set of available tracks. It's
|
||||
also useful to initialize ``--lavfi-complex`` in a specific way by API,
|
||||
without having to "probe" the available streams at first.
|
||||
|
||||
Note that this does not yet apply default track selection. Which operations
|
||||
exactly can be done and not be done, and what information is available and
|
||||
what is not yet available yet, is all subject to change.
|
||||
|
||||
``on_unload``
|
||||
Run before closing a file, and before actually uninitializing
|
||||
everything. It's not possible to resume playback in this state.
|
||||
|
||||
Legacy hook API
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. warning::
|
||||
|
||||
The legacy API is deprecated and will be removed soon.
|
||||
|
||||
There are two special commands involved. Also, the client must listen for
|
||||
client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
|
||||
|
||||
@@ -765,7 +821,6 @@ client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
|
||||
used to correctly handle multiple hooks registered by the same client,
|
||||
as long as the ``id`` argument is unique in the client)
|
||||
3. something undefined, used by the hook mechanism to track hook execution
|
||||
(currently, it's the hook-name, but this might change without warning)
|
||||
|
||||
Upon receiving this message, the client can handle the event. While doing
|
||||
this, the player core will still react to requests, but playback will
|
||||
@@ -779,32 +834,6 @@ client messages (``MPV_EVENT_CLIENT_MESSAGE`` in the C API).
|
||||
argument of the client message that starts hook execution for the
|
||||
current client.
|
||||
|
||||
The following hooks are currently defined:
|
||||
|
||||
``on_load``
|
||||
Called when a file is to be opened, before anything is actually done.
|
||||
For example, you could read and write the ``stream-open-filename``
|
||||
property to redirect an URL to something else (consider support for
|
||||
streaming sites which rarely give the user a direct media URL), or
|
||||
you could set per-file options with by setting the property
|
||||
``file-local-options/<option name>``. The player will wait until all
|
||||
hooks are run.
|
||||
|
||||
``on_preloaded``
|
||||
Called after a file has been opened, and before tracks are selected and
|
||||
decoders are created. This has some usefulness if an API users wants
|
||||
to select tracks manually, based on the set of available tracks. It's
|
||||
also useful to initialize ``--lavfi-complex`` in a specific way by API,
|
||||
without having to "probe" the available streams at first.
|
||||
|
||||
Note that this does not yet apply default track selection. Which operations
|
||||
exactly can be done and not be done, and what information is available and
|
||||
what is not yet available yet, is all subject to change.
|
||||
|
||||
``on_unload``
|
||||
Run before closing a file, and before actually uninitializing
|
||||
everything. It's not possible to resume playback in this state.
|
||||
|
||||
Input Command Prefixes
|
||||
----------------------
|
||||
|
||||
@@ -1435,7 +1464,7 @@ Property list
|
||||
``hwdec-interop``
|
||||
This returns the currently loaded hardware decoding/output interop driver.
|
||||
This is known only once the VO has opened (and possibly later). With some
|
||||
VOs (like ``opengl``), this might be never known in advance, but only when
|
||||
VOs (like ``gpu``), this might be never known in advance, but only when
|
||||
the decoder attempted to create the hw decoder successfully. (Using
|
||||
``--gpu-hwdec-interop`` can load it eagerly.) If there are multiple
|
||||
drivers loaded, they will be separated by ``,``.
|
||||
@@ -1565,12 +1594,12 @@ Property list
|
||||
redrawing and frame display being somewhat disconnected, and you might
|
||||
have to pause and force a redraw.
|
||||
|
||||
Sub-properties:
|
||||
Sub-properties::
|
||||
|
||||
``video-frame-info/picture-type``
|
||||
``video-frame-info/interlaced``
|
||||
``video-frame-info/tff``
|
||||
``video-frame-info/repeat``
|
||||
video-frame-info/picture-type
|
||||
video-frame-info/interlaced
|
||||
video-frame-info/tff
|
||||
video-frame-info/repeat
|
||||
|
||||
``container-fps``
|
||||
Container FPS. This can easily contain bogus values. For videos that use
|
||||
@@ -2079,9 +2108,6 @@ Property list
|
||||
List of decoders supported. This lists decoders which can be passed to
|
||||
``--vd`` and ``--ad``.
|
||||
|
||||
``family``
|
||||
Decoder driver. Usually ``lavc`` for libavcodec.
|
||||
|
||||
``codec``
|
||||
Canonical codec name, which identifies the format the decoder can
|
||||
handle.
|
||||
@@ -2102,7 +2128,6 @@ Property list
|
||||
|
||||
MPV_FORMAT_NODE_ARRAY
|
||||
MPV_FORMAT_NODE_MAP (for each decoder entry)
|
||||
"family" MPV_FORMAT_STRING
|
||||
"codec" MPV_FORMAT_STRING
|
||||
"driver" MPV_FORMAT_STRING
|
||||
"description" MPV_FORMAT_STRING
|
||||
@@ -2112,6 +2137,10 @@ Property list
|
||||
The encoder names (``driver`` entries) can be passed to ``--ovc`` and
|
||||
``--oac`` (without the ``lavc:`` prefix required by ``--vd`` and ``--ad``).
|
||||
|
||||
``demuxer-lavf-list``
|
||||
List of available libavformat demuxers' names. This can be used to check
|
||||
for support for a specific format or use with ``--demuxer-lavf-format``.
|
||||
|
||||
``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.
|
||||
|
||||
+1
-8
@@ -180,14 +180,7 @@ extra commands can also be used as part of the protocol:
|
||||
{ "error": "success" }
|
||||
|
||||
``set_property_string``
|
||||
Like ``set_property``, but the argument value must be passed as string.
|
||||
|
||||
Example:
|
||||
|
||||
::
|
||||
|
||||
{ "command": ["set_property_string", "pause", "yes"] }
|
||||
{ "error": "success" }
|
||||
Alias for ``set_property``. Both commands accept native values and strings.
|
||||
|
||||
``observe_property``
|
||||
Watch a property for changes. If the given property is changed, then an
|
||||
|
||||
+26
-13
@@ -27,16 +27,16 @@ otherwise, the documented Lua options, script directories, loading, etc apply to
|
||||
JavaScript files too.
|
||||
|
||||
Script initialization and lifecycle is the same as with Lua, and most of the Lua
|
||||
functions at the modules ``mp``, ``mp.utils`` and ``mp.msg`` are available to
|
||||
JavaScript with identical APIs - including running commands, getting/setting
|
||||
properties, registering events/key-bindings/property-changes/hooks, etc.
|
||||
functions at the modules ``mp``, ``mp.utils``, ``mp.msg`` and ``mp.options`` are
|
||||
available to JavaScript with identical APIs - including running commands,
|
||||
getting/setting properties, registering events/key-bindings/hooks, etc.
|
||||
|
||||
Differences from Lua
|
||||
--------------------
|
||||
|
||||
No need to load modules. ``mp``, ``mp.utils`` and ``mp.msg`` are preloaded, and
|
||||
you can use e.g. ``var cwd = mp.utils.getcwd();`` without prior setup.
|
||||
``mp.options`` is currently not implemented, but ``mp.get_opt(...)`` is.
|
||||
No need to load modules. ``mp``, ``mp.utils``, ``mp.msg`` and ``mp.options``
|
||||
are preloaded, and you can use e.g. ``var cwd = mp.utils.getcwd();`` without
|
||||
prior setup.
|
||||
|
||||
Errors are slightly different. Where the Lua APIs return ``nil`` for error,
|
||||
the JavaScript ones return ``undefined``. Where Lua returns ``something, error``
|
||||
@@ -67,10 +67,6 @@ Unsupported Lua APIs and their JS alternatives
|
||||
|
||||
``mp.add_periodic_timer(seconds, fn)`` JS: ``id = setInterval(fn, ms)``
|
||||
|
||||
``mp.register_idle(fn)`` JS: ``id = setTimeout(fn)``
|
||||
|
||||
``mp.unregister_idle(fn)`` JS: ``clearTimeout(id)``
|
||||
|
||||
``utils.parse_json(str [, trail])`` JS: ``JSON.parse(str)``
|
||||
|
||||
``utils.format_json(v)`` JS: ``JSON.stringify(v)``
|
||||
@@ -87,8 +83,6 @@ Unsupported Lua APIs and their JS alternatives
|
||||
|
||||
``mp.dispatch_events([allow_wait])`` see event loop below.
|
||||
|
||||
``mp.options`` module is not implemented currently for JS.
|
||||
|
||||
Scripting APIs - identical to Lua
|
||||
---------------------------------
|
||||
|
||||
@@ -144,6 +138,10 @@ Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
|
||||
``mp.register_idle(fn)``
|
||||
|
||||
``mp.unregister_idle(fn)``
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
|
||||
``mp.register_script_message(name, fn)``
|
||||
@@ -180,8 +178,12 @@ Otherwise, where the Lua APIs return ``nil`` on error, JS returns ``undefined``.
|
||||
|
||||
``mp.utils.subprocess_detached(t)``
|
||||
|
||||
``mp.utils.getpid()`` (LE)
|
||||
|
||||
``mp.add_hook(type, priority, fn)``
|
||||
|
||||
``mp.options.read_options(obj [, identifier])`` (types: string/boolean/number)
|
||||
|
||||
Additional utilities
|
||||
--------------------
|
||||
|
||||
@@ -265,7 +267,7 @@ event loop iteration or at a later event loop iteration. This is true also for
|
||||
intervals - which also never call back twice at the same event loop iteration.
|
||||
|
||||
Additionally, timers are processed after the event queue is empty, so it's valid
|
||||
to use ``setTimeout(fn)`` instead of Lua's ``mp.register_idle(fn)``.
|
||||
to use ``setTimeout(fn)`` as a one-time idle observer.
|
||||
|
||||
CommonJS modules and ``require(id)``
|
||||
------------------------------------
|
||||
@@ -318,6 +320,10 @@ also print every event which mpv sends to your script:
|
||||
wait = 0;
|
||||
} else {
|
||||
wait = mp.process_timers() / 1000;
|
||||
if (wait != 0) {
|
||||
mp.notify_idle_observers();
|
||||
wait = mp.peek_timers_wait() / 1000;
|
||||
}
|
||||
}
|
||||
} while (mp.keep_running);
|
||||
}
|
||||
@@ -337,5 +343,12 @@ if there are such (event handlers, property observers, script messages, etc).
|
||||
and returns the duration in ms till the next due timer (possibly 0), or -1 if
|
||||
there are no pending timers. Must not be called recursively.
|
||||
|
||||
``mp.notify_idle_observers()`` calls back the idle observers, which we do when
|
||||
we're about to sleep (wait != 0), but the observers may add timers or take
|
||||
non-negligible duration to complete, so we re-calculate ``wait`` afterwards.
|
||||
|
||||
``mp.peek_timers_wait()`` returns the same values as ``mp.process_timers()``
|
||||
but without doing anything. Invalid result if called from a timer callback.
|
||||
|
||||
Note: ``exit()`` is also registered for the ``shutdown`` event, and its
|
||||
implementation is a simple ``mp.keep_running = false``.
|
||||
+10
-3
@@ -63,7 +63,10 @@ the player will more or less hang until the script returns from the main chunk
|
||||
(and ``mp_event_loop`` is called), or the script calls ``mp_event_loop`` or
|
||||
``mp.dispatch_events`` directly. This is done to make it possible for a script
|
||||
to fully setup event handlers etc. before playback actually starts. In older
|
||||
mpv versions, this happened asynchronously.
|
||||
mpv versions, this happened asynchronously. With mpv 0.29.0, this changes
|
||||
slightly, and it merely waits for scripts to be loaded in this manner before
|
||||
starting playback as part of the player initialization phase. Scripts run though
|
||||
initialization in parallel. This might change again.
|
||||
|
||||
mp functions
|
||||
------------
|
||||
@@ -427,7 +430,7 @@ are useful only in special situations.
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
Calls ``mpv_get_wakeup_pipe()`` and returns the read end of the wakeup
|
||||
pipe. (See ``client.h`` for details.)
|
||||
pipe. This is deprecated, but still works. (See ``client.h`` for details.)
|
||||
|
||||
``mp.get_next_timeout()``
|
||||
Return the relative time in seconds when the next timer (``mp.add_timeout``
|
||||
@@ -531,7 +534,7 @@ Example implementation::
|
||||
print(options.optionA)
|
||||
|
||||
|
||||
The config file will be stored in ``lua-settings/identifier.conf`` in mpv's user
|
||||
The config file will be stored in ``script-opts/identifier.conf`` in mpv's user
|
||||
folder. Comment lines can be started with # and stray spaces are not removed.
|
||||
Boolean values will be represented with yes/no.
|
||||
|
||||
@@ -682,6 +685,10 @@ strictly part of the guaranteed API.
|
||||
|
||||
The function returns ``nil``.
|
||||
|
||||
``utils.getpid()``
|
||||
Returns the process ID of the running mpv process. This can be used to identify
|
||||
the calling mpv when launching (detached) subprocesses.
|
||||
|
||||
``utils.parse_json(str [, trail])``
|
||||
Parses the given string argument as JSON, and returns it as a Lua table. On
|
||||
error, returns ``nil, error``. (Currently, ``error`` is just a string
|
||||
|
||||
+116
-14
@@ -58,6 +58,10 @@ Ctrl+LEFT and Ctrl+RIGHT
|
||||
Seek to the previous/next subtitle. Subject to some restrictions and
|
||||
might not always work; see ``sub-seek`` command.
|
||||
|
||||
Ctrl+Shift+Left and Ctrl+Shift+Right
|
||||
Adjust subtitle delay so that the next or previous subtitle is displayed
|
||||
now. This is especially useful to sync subtitles to audio.
|
||||
|
||||
[ and ]
|
||||
Decrease/increase current playback speed by 10%.
|
||||
|
||||
@@ -67,6 +71,17 @@ Ctrl+LEFT and Ctrl+RIGHT
|
||||
BACKSPACE
|
||||
Reset playback speed to normal.
|
||||
|
||||
Shift+BACKSPACE
|
||||
Undo the last seek. This works only if the playlist entry was not changed.
|
||||
Hitting it a second time will go back to the original position.
|
||||
See ``revert-seek`` command for details.
|
||||
|
||||
Shift+Ctrl+BACKSPACE
|
||||
Mark the current position. This will then be used by ``Shift+BACKSPACE``
|
||||
as revert position (once you seek back, the marker will be reset). You can
|
||||
use this to seek around in the file and then return to the exact position
|
||||
where you left off.
|
||||
|
||||
< and >
|
||||
Go backward/forward in the playlist.
|
||||
|
||||
@@ -115,8 +130,9 @@ ESC
|
||||
T
|
||||
Toggle stay-on-top (see also ``--ontop``).
|
||||
|
||||
w and e
|
||||
Decrease/increase pan-and-scan range.
|
||||
w and W
|
||||
Decrease/increase pan-and-scan range. The ``e`` key does the same as
|
||||
``W`` currently, but use is discouraged.
|
||||
|
||||
o (also P)
|
||||
Show progression bar, elapsed time and total duration on the OSD.
|
||||
@@ -130,8 +146,9 @@ v
|
||||
j and J
|
||||
Cycle through the available subtitles.
|
||||
|
||||
x and z
|
||||
Adjust subtitle delay by +/- 0.1 seconds.
|
||||
z and Z
|
||||
Adjust subtitle delay by +/- 0.1 seconds. The ``x`` key does the same as
|
||||
``Z`` currently, but use is discouraged.
|
||||
|
||||
l
|
||||
Set/clear A-B loop points. See ``ab-loop`` command for details.
|
||||
@@ -151,8 +168,9 @@ V
|
||||
Toggle subtitle VSFilter aspect compatibility mode. See
|
||||
``--sub-ass-vsfilter-aspect-compat`` for more info.
|
||||
|
||||
r and t
|
||||
Move subtitles up/down.
|
||||
r and R
|
||||
Move subtitles up/down. The ``t`` key does the same as ``R`` currently, but
|
||||
use is discouraged.
|
||||
|
||||
s
|
||||
Take a screenshot.
|
||||
@@ -180,8 +198,28 @@ d
|
||||
A
|
||||
Cycle aspect ratio override.
|
||||
|
||||
Ctrl h
|
||||
Toggle hardware video decoding on/off.
|
||||
|
||||
Alt+LEFT, Alt+RIGHT, Alt+UP, Alt+DOWN
|
||||
Move the video rectangle (panning).
|
||||
|
||||
Alt + and Alt -
|
||||
Combining ``Alt`` with the ``+`` or ``-`` keys changes video zoom.
|
||||
|
||||
Alt+BACKSPACE
|
||||
Reset the pan/zoom settings.
|
||||
|
||||
F9
|
||||
Show the playlist and the current position in it (useful only if a UI window
|
||||
is used, broken on the terminal).
|
||||
|
||||
F10
|
||||
Show the list of audio and subtitle streams (useful only if a UI window is
|
||||
used, broken on the terminal).
|
||||
|
||||
(The following keys are valid only when using a video output that supports the
|
||||
corresponding adjustment, or the software equalizer (``--vf=eq``).)
|
||||
corresponding adjustment.)
|
||||
|
||||
1 and 2
|
||||
Adjust contrast.
|
||||
@@ -435,6 +473,7 @@ Suffix Meaning
|
||||
-del Delete an existing item by integer index
|
||||
-pre Prepend 1 or more items
|
||||
-set Set a list of items
|
||||
-toggle Append an item, or remove if if it already exists
|
||||
============= ===============================================
|
||||
|
||||
Although some operations allow specifying multiple ``,``-separated items, using
|
||||
@@ -446,6 +485,11 @@ Some options (like ``--sub-file``, ``--audio-file``, ``--glsl-shader``) are
|
||||
aliases for the proper option with ``-append`` action. For example,
|
||||
``--sub-file`` is an alias for ``--sub-files-append``.
|
||||
|
||||
Some options only support a subset of the above.
|
||||
|
||||
Options of this type can be changed at runtime using the ``change-list``
|
||||
command, which takes the suffix as separate operation parameter.
|
||||
|
||||
Playing DVDs
|
||||
------------
|
||||
|
||||
@@ -567,6 +611,11 @@ description (shown by ``--profile=help``) can be defined with the
|
||||
``profile-desc`` option. To end the profile, start another one or use the
|
||||
profile name ``default`` to continue with normal options.
|
||||
|
||||
You can list profiles with ``--profile=help``, and show the contents of a
|
||||
profile with ``--show-profile=<name>`` (replace ``<name>`` with the profile
|
||||
name). You can apply profiles on start with the ``--profile=<name>`` option,
|
||||
or at runtime with the ``apply-profile <name>`` command.
|
||||
|
||||
.. admonition:: Example mpv config file with profiles
|
||||
|
||||
::
|
||||
@@ -689,6 +738,45 @@ listed.
|
||||
properties.)
|
||||
|
||||
|
||||
LOW LATENCY PLAYBACK
|
||||
====================
|
||||
|
||||
mpv is optimized for normal video playback, meaning it actually tries to buffer
|
||||
as much data as it seems to make sense. This will increase latency. Reducing
|
||||
latency is possible only by specifically disabling features which increase
|
||||
latency.
|
||||
|
||||
The builtin ``low-latency`` profile tries to apply some of the options which can
|
||||
reduce latency. You can use ``--profile=low-latency`` to apply all of them. You
|
||||
can list the contents with ``--show-profile=low-latency`` (some of the options
|
||||
are quite obscure, and may change every mpv release).
|
||||
|
||||
Be aware that some of the options can reduce playback quality.
|
||||
|
||||
Most latency is actually caused by inconvenient timing behavior. You can disable
|
||||
this with ``--untimed``, but it will likely break, unless the stream has no
|
||||
audio, and the input feeds data to the player at a constant rate.
|
||||
|
||||
Another common problem is with MJPEG streams. These do not signal the correct
|
||||
framerate. Using ``--untimed`` or ``--no-correct-pts --fps=60`` might help.
|
||||
|
||||
For livestreams, data can build up due to pausing the stream, due to slightly
|
||||
lower playback rate, or "buffering" pauses. If the demuxer cache is enabled,
|
||||
these can be skipped manually. The experimental ``drop-buffers`` command can
|
||||
be used to discard any buffered data, though it's very disruptive.
|
||||
|
||||
In some cases, manually tuning TCP buffer sizes and such can help to reduce
|
||||
latency.
|
||||
|
||||
Additional options that can be tried:
|
||||
|
||||
- ``--opengl-glfinish=yes``, can reduce buffering in the graphics driver
|
||||
- ``--opengl-swapinterval=0``, same
|
||||
- ``--vo=xv``, same
|
||||
- without audio ``--framedrop=no --speed=1.01`` may help for live sources
|
||||
(results can be mixed)
|
||||
|
||||
|
||||
PROTOCOLS
|
||||
=========
|
||||
|
||||
@@ -726,14 +814,13 @@ PROTOCOLS
|
||||
|
||||
``bd://[title][/device]`` ``--bluray-device=PATH``
|
||||
|
||||
Play a Blu-ray disc. Currently, this does not accept ISO files. Instead,
|
||||
you must mount the ISO file as filesystem, and point ``--bluray-device``
|
||||
to the mounted directory directly.
|
||||
Play a Blu-ray disc. Since libbluray 1.0.1, you can read from ISO files
|
||||
by passing them to ``--bluray-device``.
|
||||
|
||||
``title`` can be: ``longest`` or ``first`` (selects the default
|
||||
playlist); ``mpls/<number>`` (selects <number>.mpls playlist);
|
||||
``<number>`` (select playlist with the same index). You can list
|
||||
the available playlists with ``--msg-level=bd=v``.
|
||||
``<number>`` (select playlist with the same index). mpv will list
|
||||
the available playlists on loading.
|
||||
|
||||
``bluray://`` is an alias.
|
||||
|
||||
@@ -748,7 +835,7 @@ PROTOCOLS
|
||||
``dvdread://...:``
|
||||
|
||||
Play a DVD using the old libdvdread code. This is what MPlayer and
|
||||
older mpv versions use for ``dvd://``. Use is discouraged. It's
|
||||
older mpv versions used for ``dvd://``. Use is discouraged. It's
|
||||
provided only for compatibility and for transition, and to work
|
||||
around outstanding dvdnav bugs (see "DVD library choices" above).
|
||||
|
||||
@@ -795,10 +882,25 @@ PROTOCOLS
|
||||
``PATH`` itself should start with a third ``/`` to make the path an
|
||||
absolute path.
|
||||
|
||||
``appending://PATH``
|
||||
|
||||
Play a local file, but assume it's being appended to. This is useful for
|
||||
example for files that are currently being downloaded to disk. This will
|
||||
block playback, and stop playback only if no new data was appended after
|
||||
a timeout of about 2 seconds.
|
||||
|
||||
Using this is still a bit of a bad idea, because there is no way to detect
|
||||
if a file is actually being appended, or if it's still written. If you're
|
||||
trying to play the output of some program, consider using a pipe
|
||||
(``something | mpv -``). If it really has to be a file on disk, use tail to
|
||||
make it wait forever, e.g. ``tail -f -c +0 file.mkv | mpv -``.
|
||||
|
||||
``fd://123``
|
||||
|
||||
Read data from the given file descriptor (for example 123). This is similar
|
||||
to piping data to stdin via ``-``, but can use an arbitrary file descriptor.
|
||||
mpv may modify some file descriptor properties when the stream layer "opens"
|
||||
it.
|
||||
|
||||
``fdclose://123``
|
||||
|
||||
@@ -1072,7 +1174,7 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
``--write-filename-in-watch-later-config`` option, and the player will
|
||||
add the media filename to the contents of the resume config file.
|
||||
|
||||
``~/.config/mpv/lua-settings/osc.conf``
|
||||
``~/.config/mpv/script-opts/osc.conf``
|
||||
This is loaded by the OSC script. See the `ON SCREEN CONTROLLER`_ docs
|
||||
for details.
|
||||
|
||||
|
||||
+369
-119
@@ -64,17 +64,6 @@ Track Selection
|
||||
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.
|
||||
|
||||
``--ff-aid=<ID|auto|no>``, ``--ff-sid=<ID|auto|no>``, ``--ff-vid=<ID|auto|no>``
|
||||
Select audio/subtitle/video streams by the FFmpeg stream index. The FFmpeg
|
||||
stream index is relatively arbitrary, but useful when interacting with
|
||||
other software using FFmpeg (consider ``ffprobe``).
|
||||
|
||||
Note that with external tracks (added with ``--sub-files`` and similar
|
||||
options), there will be streams with duplicate IDs. In this case, the
|
||||
first stream in order is selected.
|
||||
|
||||
Deprecated.
|
||||
|
||||
``--edition=<ID|auto>``
|
||||
(Matroska files only)
|
||||
Specify the edition (set of chapters) to use, where 0 is the first. If set
|
||||
@@ -129,8 +118,8 @@ Playback Control
|
||||
``--start='#2' --end='#4'``
|
||||
Plays chapters 2 and 3, and exits.
|
||||
|
||||
``--end=<time>``
|
||||
Stop at given absolute time. Use ``--length`` if the time should be relative
|
||||
``--end=<relative time>``
|
||||
Stop at given time. Use ``--length`` if the time should be relative
|
||||
to ``--start``. See ``--start`` for valid option values and examples.
|
||||
|
||||
``--length=<relative time>``
|
||||
@@ -547,15 +536,23 @@ Program Behavior
|
||||
Enable the youtube-dl hook-script. It will look at the input URL, and will
|
||||
play the video located on the website. This works with many streaming sites,
|
||||
not just the one that the script is named after. This requires a recent
|
||||
version of youtube-dl to be installed on the system. (Enabled by default,
|
||||
except when the client API / libmpv is used.)
|
||||
version of youtube-dl to be installed on the system. (Enabled by default.)
|
||||
|
||||
If the script can't do anything with an URL, it will do nothing.
|
||||
|
||||
The `try_ytdl_first` script option accepts a boolean 'yes' or 'no', and if
|
||||
'yes' will try parsing the URL with youtube-dl first, instead of the default
|
||||
where it's only after mpv failed to open it. This mostly depends on whether
|
||||
most of your URLs need youtube-dl parsing.
|
||||
|
||||
The `exclude` script option accepts a ``|``-separated list of URL patterns
|
||||
which mpv should not use with youtube-dl. The patterns are matched after
|
||||
the ``http(s)://`` part of the URL.
|
||||
|
||||
The `use_manifests` script option makes mpv use the master manifest URL for
|
||||
formats like HLS and DASH, if available, allowing for video/audio selection
|
||||
in runtime. It's disabled ("no") by default for performance reasons.
|
||||
|
||||
``^`` matches the beginning of the URL, ``$`` matches its end, and you
|
||||
should use ``%`` before any of the characters ``^$()%|,.[]*+-?`` to match
|
||||
that character.
|
||||
@@ -586,10 +583,18 @@ Program Behavior
|
||||
There is no sanity checking so it's possible to break things (i.e.
|
||||
passing invalid parameters to youtube-dl).
|
||||
|
||||
A proxy URL can be passed for youtube-dl to use it in parsing the website.
|
||||
This is useful for geo-restricted URLs. After youtube-dl parsing, some
|
||||
URLs also require a proxy for playback, so this can pass that proxy
|
||||
information to mpv. Take note that SOCKS proxies aren't supported and
|
||||
https URLs also bypass the proxy. This is a limitation in FFmpeg.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
- ``--ytdl-raw-options=username=user,password=pass``
|
||||
- ``--ytdl-raw-options=force-ipv6=``
|
||||
- ``--ytdl-raw-options=proxy=[http://127.0.0.1:3128]``
|
||||
- ``--ytdl-raw-options-append=proxy=http://127.0.0.1:3128``
|
||||
|
||||
``--load-stats-overlay=<yes|no>``
|
||||
Enable the builtin script that shows useful playback information on a key
|
||||
@@ -663,6 +668,26 @@ Video
|
||||
``--vo=vdpau`` has its own code for the ``vo`` framedrop mode. Slight
|
||||
differences to other VOs are possible.
|
||||
|
||||
``--video-latency-hacks=<yes|no>``
|
||||
Enable some things which tend to reduce video latency by 1 or 2 frames
|
||||
(default: no). Note that this option might be removed without notice once
|
||||
the player's timing code does not inherently need to do these things
|
||||
anymore.
|
||||
|
||||
This does:
|
||||
|
||||
- Use the demuxer reported FPS for frame dropping. This avoids that the
|
||||
player needs to decode 1 frame in advance, lowering total latency in
|
||||
effect. This also means that if the demuxer reported FPS is wrong, or
|
||||
the video filter chain changes FPS (e.g. deinterlacing), then it could
|
||||
drop too many or not enough frames.
|
||||
- Disable waiting for the first video frame. Normally the player waits for
|
||||
the first video frame to be fully rendered before starting playback
|
||||
properly. Some VOs will lazily initialize stuff when rendering the first
|
||||
frame, so if this is not done, there is some likeliness that the VO has
|
||||
to drop some frames if rendering the first frame takes longer than needed.
|
||||
|
||||
|
||||
``--display-fps=<fps>``
|
||||
Set the display FPS used with the ``--video-sync=display-*`` modes. By
|
||||
default, a detected value is used. Keep in mind that setting an incorrect
|
||||
@@ -691,8 +716,9 @@ Video
|
||||
:videotoolbox: requires ``--vo=gpu`` (OS X 10.8 and up),
|
||||
or ``--vo=opengl-cb`` (iOS 9.0 and up)
|
||||
:videotoolbox-copy: copies video back into system RAM (OS X 10.8 or iOS 9.0 and up)
|
||||
:dxva2: requires ``--vo=gpu`` with ``--gpu-context=angle`` or
|
||||
``--gpu-context=dxinterop`` (Windows only)
|
||||
:dxva2: requires ``--vo=gpu`` with ``--gpu-context=d3d11``,
|
||||
``--gpu-context=angle`` or ``--gpu-context=dxinterop``
|
||||
(Windows only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:d3d11va: requires ``--vo=gpu`` with ``--gpu-context=d3d11`` or
|
||||
``--gpu-context=angle`` (Windows 8+ only)
|
||||
@@ -862,18 +888,19 @@ Video
|
||||
older hardware. d3d11va can always use ``yuv420p``, which uses an opaque
|
||||
format, with likely no advantages.
|
||||
|
||||
``--videotoolbox-format=<name>``
|
||||
Set the internal pixel format used by ``--hwdec=videotoolbox`` on OSX. The
|
||||
choice of the format can influence performance considerably. On the other
|
||||
hand, there doesn't appear to be a good way to detect the best format for
|
||||
the given hardware. ``nv12``, the default, works better on modern hardware,
|
||||
while ``uyvy422`` appears to be better for old hardware. ``yuv420p`` also
|
||||
works.
|
||||
Since mpv 0.25.0, ``no`` is an accepted value, which lets the decoder pick
|
||||
the format on newer FFmpeg versions (will use ``nv12`` on older versions).
|
||||
``--cuda-decode-device=<auto|0..>``
|
||||
Choose the GPU device used for decoding when using the ``cuda`` hwdec.
|
||||
|
||||
Deprecated. Use ``--hwdec-image-format`` if you really need this. If both
|
||||
are specified, ``--hwdec-image-format`` wins.
|
||||
By default, the device that is being used to provide OpenGL output will
|
||||
also be used for decoding (and in the vast majority of cases, only one
|
||||
GPU will be present).
|
||||
|
||||
Note that when using the ``cuda-copy`` hwdec, a different option must be
|
||||
passed: ``--vd-lavc-o=gpu=<0..>``.
|
||||
|
||||
``--vaapi-device=<device file>``
|
||||
Choose the DRM device for ``vaapi-copy``. This should be the path to a
|
||||
DRM device file. (Default: ``/dev/dri/renderD128``)
|
||||
|
||||
``--panscan=<0.0-1.0>``
|
||||
Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9
|
||||
@@ -993,8 +1020,8 @@ Video
|
||||
determined using a fixed framerate value (either using the ``--fps``
|
||||
option, or using file information). Sometimes, files with very broken
|
||||
timestamps can be played somewhat well in this mode. Note that video
|
||||
filters, subtitle rendering and audio synchronization can be completely
|
||||
broken in this mode.
|
||||
filters, subtitle rendering, seeking (including hr-seeks and backstepping),
|
||||
and audio synchronization can be completely broken in this mode.
|
||||
|
||||
``--fps=<float>``
|
||||
Override video framerate. Useful if the original value is wrong or missing.
|
||||
@@ -1019,6 +1046,8 @@ Video
|
||||
``--deinterlace=auto`` was removed, which used to mean that the default
|
||||
interlacing option of possibly inserted video filters was used.)
|
||||
|
||||
Note that this will make video look worse if it's not actually interlaced.
|
||||
|
||||
``--frames=<number>``
|
||||
Play/convert only first ``<number>`` video frames, then quit.
|
||||
|
||||
@@ -1081,22 +1110,19 @@ Video
|
||||
N frames fail to decode in a row. 1 is equivalent to ``yes``.
|
||||
|
||||
``--vd-lavc-dr=<yes|no>``
|
||||
Enable direct rendering (default: no). If this is set to ``yes``, the
|
||||
Enable direct rendering (default: yes). If this is set to ``yes``, the
|
||||
video will be decoded directly to GPU video memory (or staging buffers).
|
||||
This can speed up video upload, and may help with large resolutions or
|
||||
slow hardware. This works only with the following VOs:
|
||||
|
||||
- ``gpu``: requires at least OpenGL 4.4.
|
||||
- ``gpu``: requires at least OpenGL 4.4 or Vulkan.
|
||||
|
||||
(In particular, this can't be made work with ``opengl-cb``.)
|
||||
(In particular, this can't be made work with ``opengl-cb``, but the libmpv
|
||||
render API has optional support.)
|
||||
|
||||
Using video filters of any kind that write to the image data (or output
|
||||
newly allocated frames) will silently disable the DR code path.
|
||||
|
||||
There are some corner cases that will result in undefined behavior (crashes
|
||||
and other strange behavior) if this option is enabled. These are pending
|
||||
towards being fixed properly at a later point.
|
||||
|
||||
``--vd-lavc-bitexact``
|
||||
Only use bit-exact algorithms in all decoding steps (for codec testing).
|
||||
|
||||
@@ -1159,6 +1185,18 @@ Video
|
||||
on the machine and use that, up to the maximum of 16. You can set more than
|
||||
16 threads manually.
|
||||
|
||||
``--vd-lavc-assume-old-x264=<yes|no>``
|
||||
Assume the video was encoded by an old, buggy x264 version (default: no).
|
||||
Normally, this is autodetected by libavcodec. But if the bitstream contains
|
||||
no x264 version info (or it was somehow skipped), and the stream was in fact
|
||||
encoded by an old x264 version (build 150 or earlier), and if the stream
|
||||
uses ``4:4:4`` chroma, then libavcodec will by default show corrupted video.
|
||||
This option sets the libavcodec ``x264_build`` option to ``150``, which
|
||||
means that if the stream contains no version info, or was not encoded by
|
||||
x264 at all, it assumes it was encoded by the old version. Enabling this
|
||||
option is pretty safe if you want your broken files to work, but in theory
|
||||
this can break on streams not encoded by x264, or if a stream encoded by a
|
||||
newer x264 version contains no version info.
|
||||
|
||||
|
||||
Audio
|
||||
@@ -1183,7 +1221,9 @@ Audio
|
||||
While the option normally takes one of the strings as indicated by the
|
||||
methods above, you can also force the device for most AOs by building it
|
||||
manually. For example ``name/foobar`` forces the AO ``name`` to use the
|
||||
device ``foobar``.
|
||||
device ``foobar``. However, the ``--ao`` option will strictly force a
|
||||
specific AO. To avoid confusion, don't use ``--ao`` and ``--audio-device``
|
||||
together.
|
||||
|
||||
.. admonition:: Example for ALSA
|
||||
|
||||
@@ -1424,16 +1464,6 @@ Audio
|
||||
want. For example, most A/V receivers connected via HDMI and that can
|
||||
do 7.1 would be served by: ``--audio-channels=7.1,5.1,stereo``
|
||||
|
||||
``--audio-normalize-downmix=<yes|no>``
|
||||
Enable/disable normalization if surround audio is downmixed to stereo
|
||||
(default: no). If this is disabled, downmix can cause clipping. If it's
|
||||
enabled, the output might be too silent. It depends on the source audio.
|
||||
|
||||
Technically, this changes the ``normalize`` suboption of the
|
||||
``lavrresample`` audio filter, which performs the downmixing.
|
||||
|
||||
If downmix happens outside of mpv for some reason, this has no effect.
|
||||
|
||||
``--audio-display=<no|attachment>``
|
||||
Setting this option to ``attachment`` (default) will display image
|
||||
attachments (e.g. album cover art) when playing audio files. It will
|
||||
@@ -1484,9 +1514,10 @@ Audio
|
||||
changes, the audio device is closed and reopened. This means that
|
||||
you will normally get gapless audio with files that were encoded
|
||||
using the same settings, but might not be gapless in other cases.
|
||||
(Unlike with ``yes``, you don't have to worry about corner cases
|
||||
like the first file setting a very low quality output format, and
|
||||
ruining the playback of higher quality files that follow.)
|
||||
The exact conditions under which the audio device is kept open is
|
||||
an implementation detail, and can change from version to version.
|
||||
Currently, the device is kept even if the sample format changes,
|
||||
but the sample formats are convertible.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -2013,7 +2044,8 @@ Subtitles
|
||||
Default: 55.
|
||||
|
||||
``--sub-back-color=<color>``
|
||||
See ``--sub-color``. Color used for sub text background.
|
||||
See ``--sub-color``. Color used for sub text background. You can use
|
||||
``--sub-shadow-offset`` to change its size relative to the text.
|
||||
|
||||
``--sub-blur=<0..20.0>``
|
||||
Gaussian blur factor. 0 means no blur applied (default).
|
||||
@@ -2193,7 +2225,7 @@ Window
|
||||
|
||||
.. admonition:: Note (X11)
|
||||
|
||||
This option does works properly only with window managers which
|
||||
This option works properly only with window managers which
|
||||
understand the EWMH ``_NET_WM_FULLSCREEN_MONITORS`` hint.
|
||||
|
||||
.. admonition:: Note (OS X)
|
||||
@@ -2514,8 +2546,7 @@ Window
|
||||
always re-enabled when the player is paused.
|
||||
|
||||
This is not supported on all video outputs or platforms. Sometimes it is
|
||||
implemented, but does not work (known to happen with GNOME). You might be
|
||||
able to work around this using ``--heartbeat-cmd`` instead.
|
||||
implemented, but does not work (especially with Linux "desktops").
|
||||
|
||||
``--wid=<ID>``
|
||||
This tells mpv to attach to an existing window. If a VO is selected that
|
||||
@@ -2696,7 +2727,7 @@ Demuxer
|
||||
``--demuxer-lavf-analyzeduration=<value>``
|
||||
Maximum length in seconds to analyze the stream properties.
|
||||
|
||||
``--demuxer-lavf-probe-info=<yes|no|auto>``
|
||||
``--demuxer-lavf-probe-info=<yes|no|auto|nostreams>``
|
||||
Whether to probe stream information (default: auto). Technically, this
|
||||
controls whether libavformat's ``avformat_find_stream_info()`` function
|
||||
is called. Usually it's safer to call it, but it can also make startup
|
||||
@@ -2705,6 +2736,10 @@ Demuxer
|
||||
The ``auto`` choice (the default) tries to skip this for a few know-safe
|
||||
whitelisted formats, while calling it for everything else.
|
||||
|
||||
The ``nostreams`` choice only calls it if and only if the file seems to
|
||||
contain no streams after opening (helpful in cases when calling the function
|
||||
is needed to detect streams at all, such as with FLV files).
|
||||
|
||||
``--demuxer-lavf-probescore=<1-100>``
|
||||
Minimum required libavformat probe score. Lower values will require
|
||||
less data to be loaded (makes streams start faster), but makes file
|
||||
@@ -2728,12 +2763,6 @@ Demuxer
|
||||
imperfect behavior from libavformat demuxers. Passing ``no`` disables
|
||||
these. For debugging and testing only.
|
||||
|
||||
``--demuxer-lavf-genpts-mode=<no|lavf>``
|
||||
Mode for deriving missing packet PTS values from packet DTS. ``lavf``
|
||||
enables libavformat's ``genpts`` option. ``no`` disables it. This used
|
||||
to be enabled by default, but then it was deemed as not needed anymore.
|
||||
Enabling this might help with timestamp problems, or make them worse.
|
||||
|
||||
``--demuxer-lavf-o=<key>=<value>[,<key>=<value>[,...]]``
|
||||
Pass AVOptions to libavformat demuxer.
|
||||
|
||||
@@ -2860,7 +2889,7 @@ Demuxer
|
||||
``--demuxer-rawvideo-size=<value>``
|
||||
Frame size in bytes when using ``--demuxer=rawvideo``.
|
||||
|
||||
``--demuxer-max-bytes=<bytes>``
|
||||
``--demuxer-max-bytes=<bytesize>``
|
||||
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 option can be used to
|
||||
@@ -2872,9 +2901,10 @@ Demuxer
|
||||
Set these limits higher 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.
|
||||
See ``--list-options`` for defaults and value range. ``<bytesize>`` options
|
||||
accept suffixes such as ``KiB`` and ``MiB``.
|
||||
|
||||
``--demuxer-max-back-bytes=<value>``
|
||||
``--demuxer-max-back-bytes=<bytesize>``
|
||||
This controls how much past data the demuxer is allowed to preserve. This
|
||||
is useful only if the ``--demuxer-seekable-cache`` option is enabled.
|
||||
Unlike the forward cache, there is no control how many seconds are actually
|
||||
@@ -2900,8 +2930,7 @@ Demuxer
|
||||
|
||||
Keep in mind that some events can flush the cache or force a low level
|
||||
seek anyway, such as switching tracks, or attempting to seek before the
|
||||
start or after the end of the file. This option is experimental - thus
|
||||
disabled, and bugs are to be expected.
|
||||
start or after the end of the file.
|
||||
|
||||
The special value ``auto`` means ``yes`` in the same situation as
|
||||
``--cache-secs`` is used (i.e. when the stream appears to be a network
|
||||
@@ -2909,9 +2938,12 @@ Demuxer
|
||||
|
||||
``--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
|
||||
playback, but on the other hand can add delays to seeking or track
|
||||
switching.
|
||||
of packets (default: yes). Having this enabled leads to smoother playback,
|
||||
enables features like prefetching, and prevents that stuck network freezes
|
||||
the player. On the other hand, it can add overhead, or the background
|
||||
prefetching can hog CPU resources.
|
||||
|
||||
Disabling this option is not recommended. Use it for debugging only.
|
||||
|
||||
``--demuxer-readahead-secs=<seconds>``
|
||||
If ``--demuxer-thread`` is enabled, this controls how much the demuxer
|
||||
@@ -3084,13 +3116,18 @@ OSD
|
||||
Whether to load the on-screen-controller (default: yes).
|
||||
|
||||
``--no-osd-bar``, ``--osd-bar``
|
||||
Disable display of the OSD bar. This will make some things (like seeking)
|
||||
use OSD text messages instead of the bar.
|
||||
Disable display of the OSD bar.
|
||||
|
||||
You can configure this on a per-command basis in input.conf using ``osd-``
|
||||
prefixes, see ``Input command prefixes``. If you want to disable the OSD
|
||||
prefixes, see ``Input Command Prefixes``. If you want to disable the OSD
|
||||
completely, use ``--osd-level=0``.
|
||||
|
||||
``--osd-on-seek=<no,bar,msg,msg-bar>``
|
||||
Set what is displayed on the OSD during seeks. The default is ``bar``.
|
||||
|
||||
You can configure this on a per-command basis in input.conf using ``osd-``
|
||||
prefixes, see ``Input Command Prefixes``.
|
||||
|
||||
``--osd-duration=<time>``
|
||||
Set the duration of the OSD messages in ms (default: 1000).
|
||||
|
||||
@@ -3109,30 +3146,31 @@ OSD
|
||||
|
||||
``--osd-msg1=<string>``
|
||||
Show this string as message on OSD with OSD level 1 (visible by default).
|
||||
The message will be visible by default, and as long no other message
|
||||
The message will be visible by default, and as long as no other message
|
||||
covers it, and the OSD level isn't changed (see ``--osd-level``).
|
||||
Expands properties; see `Property Expansion`_.
|
||||
|
||||
``--osd-msg2=<string>``
|
||||
Similar as ``--osd-msg1``, but for OSD level 2. If this is an empty string
|
||||
Similar to ``--osd-msg1``, but for OSD level 2. If this is an empty string
|
||||
(default), then the playback time is shown.
|
||||
|
||||
``--osd-msg3=<string>``
|
||||
Similar as ``--osd-msg1``, but for OSD level 3. If this is an empty string
|
||||
Similar to ``--osd-msg1``, but for OSD level 3. If this is an empty string
|
||||
(default), then the playback time, duration, and some more information is
|
||||
shown.
|
||||
|
||||
This is also used for the ``show-progress`` command (by default mapped to
|
||||
``P``), or in some non-default cases when seeking.
|
||||
This is used for the ``show-progress`` command (by default mapped to ``P``),
|
||||
and when seeking if enabled with ``--osd-on-seek`` or by ``osd-`` prefixes
|
||||
in input.conf (see ``Input Command Prefixes``).
|
||||
|
||||
``--osd-status-msg`` is a legacy equivalent (but with a minor difference).
|
||||
|
||||
``--osd-status-msg=<string>``
|
||||
Show a custom string during playback instead of the standard status text.
|
||||
This overrides the status text used for ``--osd-level=3``, when using the
|
||||
``show-progress`` command (by default mapped to ``P``), or in some
|
||||
non-default cases when seeking. Expands properties. See
|
||||
`Property Expansion`_.
|
||||
``show-progress`` command (by default mapped to ``P``), and when seeking if
|
||||
enabled with ``--osd-on-seek`` or ``osd-`` prefixes in input.conf (see
|
||||
``Input Command Prefixes``). Expands properties. See `Property Expansion`_.
|
||||
|
||||
This option has been replaced with ``--osd-msg3``. The only difference is
|
||||
that this option implicitly includes ``${osd-sym-cc}``. This option is
|
||||
@@ -3286,7 +3324,8 @@ Screenshot
|
||||
``--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.
|
||||
sometimes triggers writing 16 bit PNGs with huge file sizes. This will also
|
||||
include an unused alpha channel in the resulting files if 16 bit is used.
|
||||
|
||||
``--screenshot-template=<template>``
|
||||
Specify the filename template used to save screenshots. The template
|
||||
@@ -3438,6 +3477,53 @@ Software Scaler
|
||||
``--sws-cvs=<v>``
|
||||
Software scaler chroma vertical shifting. See ``--sws-scaler``.
|
||||
|
||||
Audio Resampler
|
||||
---------------
|
||||
|
||||
This controls the default options of any resampling done by mpv (but not within
|
||||
libavfilter, within the system audio API resampler, or any other places).
|
||||
|
||||
It also sets the defaults for the ``lavrresample`` audio filter.
|
||||
|
||||
``--audio-resample-filter-size=<length>``
|
||||
Length of the filter with respect to the lower sampling rate. (default:
|
||||
16)
|
||||
|
||||
``--audio-resample-phase-shift=<count>``
|
||||
Log2 of the number of polyphase entries. (..., 10->1024, 11->2048,
|
||||
12->4096, ...) (default: 10->1024)
|
||||
|
||||
``--audio-resample-cutoff=<cutoff>``
|
||||
Cutoff frequency (0.0-1.0), default set depending upon filter length.
|
||||
|
||||
``--audio-resample-linear=<yes|no>``
|
||||
If set then filters will be linearly interpolated between polyphase
|
||||
entries. (default: no)
|
||||
|
||||
``--audio-normalize-downmix=<yes|no>``
|
||||
Enable/disable normalization if surround audio is downmixed to stereo
|
||||
(default: no). If this is disabled, downmix can cause clipping. If it's
|
||||
enabled, the output might be too quiet. It depends on the source audio.
|
||||
|
||||
Technically, this changes the ``normalize`` suboption of the
|
||||
``lavrresample`` audio filter, which performs the downmixing.
|
||||
|
||||
If downmix happens outside of mpv for some reason, or in the decoder
|
||||
(decoder downmixing), or in the audio output (system mixer), this has no
|
||||
effect.
|
||||
|
||||
``--audio-resample-max-output-size=<length>``
|
||||
Limit maximum size of audio frames filtered at once, in ms (default: 40).
|
||||
The output size size is limited in order to make resample speed changes
|
||||
react faster. This is necessary especially if decoders or filters output
|
||||
very large frame sizes (like some lossless codecs or some DRC filters).
|
||||
This option does not affect the resampling algorithm in any way.
|
||||
|
||||
For testing/debugging only. Can be removed or changed any time.
|
||||
|
||||
``--audio-swresample-o=<string>``
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
Terminal
|
||||
--------
|
||||
@@ -3839,9 +3925,33 @@ Cache
|
||||
very high, so the actually achieved readahead will usually be limited by
|
||||
the value of the ``--demuxer-max-bytes`` option.
|
||||
|
||||
``--cache-pause``, ``--no-cache-pause``
|
||||
Whether the player should automatically pause when the cache runs low,
|
||||
and unpause once more data is available ("buffering").
|
||||
``--cache-pause=<yes|no>``
|
||||
Whether the player should automatically pause when the cache runs out of
|
||||
data and stalls decoding/playback (default: yes). If enabled, it will
|
||||
pause and unpause once more data is available, aka "buffering".
|
||||
|
||||
``--cache-pause-wait=<seconds>``
|
||||
Number of seconds the packet cache should have buffered before starting
|
||||
playback again if "buffering" was entered (default: 1). This can be used
|
||||
to control how long the player rebuffers if ``--cache-pause`` is enabled,
|
||||
and the demuxer underruns. If the given time is higher than the maximum
|
||||
set with ``--cache-secs`` or ``--demuxer-readahead-secs``, or prefetching
|
||||
ends before that for some other reason (like file end), playback resumes
|
||||
earlier.
|
||||
|
||||
``--cache-pause-initial=<yes|no>``
|
||||
Enter "buffering" mode before starting playback (default: no). This can be
|
||||
used to ensure playback starts smoothly, in exchange for waiting some time
|
||||
to prefetch network data (as controlled by ``--cache-pause-wait``). For
|
||||
example, some common behavior is that playback starts, but network caches
|
||||
immediately underrun when trying to decode more data as playback progresses.
|
||||
|
||||
Another thing that can happen is that the network prefetching is so CPU
|
||||
demanding (due to demuxing in the background) that playback drops frames
|
||||
at first. In these cases, it helps enabling this option, and setting
|
||||
``--cache-secs`` and ``--cache-pause-wait`` to roughly the same value.
|
||||
|
||||
This option also triggers when playback is restarted after seeking.
|
||||
|
||||
|
||||
Network
|
||||
@@ -3877,6 +3987,13 @@ Network
|
||||
Field2: value2
|
||||
Connection: close
|
||||
|
||||
``--http-proxy=<proxy>``
|
||||
URL of the HTTP/HTTPS proxy. If this is set, the ``http_proxy`` environment
|
||||
is ignored. The ``no_proxy`` environment variable is still respected. This
|
||||
option is silently ignored if it does not start with ``http://``. Proxies
|
||||
are not used for https URLs. Setting this option does not try to make the
|
||||
ytdl script use the proxy.
|
||||
|
||||
``--tls-ca-file=<filename>``
|
||||
Certificate authority database file for use with TLS. (Silently fails with
|
||||
older FFmpeg or Libav versions.)
|
||||
@@ -3900,6 +4017,16 @@ Network
|
||||
special value 0 (default) uses the FFmpeg/Libav defaults. If a protocol
|
||||
is used which does not support timeouts, this option is silently ignored.
|
||||
|
||||
.. warning::
|
||||
|
||||
This breaks the RTSP protocol, because of inconsistent FFmpeg API
|
||||
regarding its internal timeout option. Not only does the RTSP timeout
|
||||
option accept different units (seconds instead of microseconds, causing
|
||||
mpv to pass it huge values), it will also overflow FFmpeg internal
|
||||
calculations. The worst is that merely setting the option will put RTSP
|
||||
into listening mode, which breaks any client uses. Do not use this
|
||||
option with RTSP URLs.
|
||||
|
||||
``--rtsp-transport=<lavf|udp|tcp|http>``
|
||||
Select RTSP transport method (default: tcp). This selects the underlying
|
||||
network transport when playing ``rtsp://...`` URLs. The value ``lavf``
|
||||
@@ -3996,6 +4123,20 @@ ALSA audio output options
|
||||
or it will work only for files which use the layout implicit to your
|
||||
ALSA device).
|
||||
|
||||
``--alsa-buffer-time=<microseconds>``
|
||||
Set the requested buffer time in microseconds. A value of 0 skips requesting
|
||||
anything from the ALSA API. This and the ``--alsa-periods`` option uses the
|
||||
ALSA ``near`` functions to set the requested parameters. If doing so results
|
||||
in an empty configuration set, setting these parameters is skipped.
|
||||
|
||||
Both options control the buffer size. A low buffer size can lead to higher
|
||||
CPU usage and audio dropouts, while a high buffer size can lead to higher
|
||||
latency in volume changes and other filtering.
|
||||
|
||||
``--alsa-periods=<number>``
|
||||
Number of periods requested from the ALSA API. See ``--alsa-buffer-time``
|
||||
for further remarks.
|
||||
|
||||
|
||||
GPU renderer options
|
||||
-----------------------
|
||||
@@ -4295,11 +4436,25 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
Controls the number of VkQueues used for rendering (limited by how many
|
||||
your device supports). In theory, using more queues could enable some
|
||||
parallelism between frames (when using a ``--swapchain-depth`` higher than
|
||||
1). (Default: 1)
|
||||
1), but it can also slow things down on hardware where there's no true
|
||||
parallelism between queues. (Default: 1)
|
||||
|
||||
NOTE: Setting this to a value higher than 1 may cause graphical corruption,
|
||||
as mpv's vulkan implementation currently does not try and protect textures
|
||||
against concurrent access.
|
||||
``--vulkan-async-transfer``
|
||||
Enables the use of async transfer queues on supported vulkan devices. Using
|
||||
them allows transfer operations like texture uploads and blits to happen
|
||||
concurrently with the actual rendering, thus improving overall throughput
|
||||
and power consumption. Enabled by default, and should be relatively safe.
|
||||
|
||||
``--vulkan-async-compute``
|
||||
Enables the use of async compute queues on supported vulkan devices. Using
|
||||
this, in theory, allows out-of-order scheduling of compute shaders with
|
||||
graphics shaders, thus enabling the hardware to do more effective work while
|
||||
waiting for pipeline bubbles and memory operations. Not beneficial on all
|
||||
GPUs. It's worth noting that if async compute is enabled, and the device
|
||||
supports more compute queues than graphics queues (bound by the restrictions
|
||||
set by ``--vulkan-queue-count``), mpv will internally try and prefer the
|
||||
use of compute shaders over fragment shaders wherever possible. Not enabled
|
||||
by default, since it seems to cause issues with some drivers.
|
||||
|
||||
``--d3d11-warp=<yes|no|auto>``
|
||||
Use WARP (Windows Advanced Rasterization Platform) with the D3D11 GPU
|
||||
@@ -4589,7 +4744,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
|
||||
``--deband``
|
||||
Enable the debanding algorithm. This greatly reduces the amount of visible
|
||||
banding, blocking and other quantization artifacts, at the expensive of
|
||||
banding, blocking and other quantization artifacts, at the expense 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.
|
||||
@@ -4727,6 +4882,37 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
|
||||
OS X only.
|
||||
|
||||
``--macos-title-bar-style=<dark|ultradark|light|mediumlight|auto>``
|
||||
Sets the styling of the title bar (default: dark).
|
||||
OS X and cocoa-cb only
|
||||
|
||||
:dark: Dark title bar with vibrancy, a subtle blurring effect that
|
||||
dynamically blends the background (Video) into the title bar.
|
||||
:ultradark: Darker title bar with vibrancy (like QuickTime Player).
|
||||
:light: Bright title bar with vibrancy.
|
||||
:mediumlight: Less bright title bar with vibrancy.
|
||||
:auto: Detects the system settings and sets the title bar styling
|
||||
appropriately, either ultradark or mediumlight.
|
||||
|
||||
``--macos-fs-animation-duration=<default|0-1000>``
|
||||
Sets the fullscreen resize animation duration in ms (default: default).
|
||||
The default value is slightly less than the system's animation duration
|
||||
(500ms) to prevent some problems when the end of an async animation happens
|
||||
at the same time as the end of the system wide fullscreen animation. Setting
|
||||
anything higher than 500ms will only prematurely cancel the resize animation
|
||||
after the system wide animation ended. The upper limit is still set at
|
||||
1000ms since it's possible that Apple or the user changes the system
|
||||
defaults. Anything higher than 1000ms though seems too long and shouldn't be
|
||||
set anyway.
|
||||
OS X and cocoa-cb only
|
||||
|
||||
``--android-surface-size=<WxH>``
|
||||
Set dimensions of the rendering surface used by the Android gpu context.
|
||||
Needs to be set by the embedding application if the dimensions change during
|
||||
runtime (i.e. if the device is rotated), via the surfaceChanged callback.
|
||||
|
||||
Android with ``--gpu-context=android`` only.
|
||||
|
||||
``--swapchain-depth=<N>``
|
||||
Allow up to N in-flight frames. This essentially controls the frame
|
||||
latency. Increasing the swapchain depth can improve pipelining and prevent
|
||||
@@ -4746,7 +4932,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
auto
|
||||
auto-select (default)
|
||||
cocoa
|
||||
Cocoa/OS X
|
||||
Cocoa/OS X (deprecated, use --vo=opengl-cb instead)
|
||||
win
|
||||
Win32/WGL
|
||||
winvk
|
||||
@@ -4819,9 +5005,12 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
Selects the internal format of textures used for FBOs. The format can
|
||||
influence performance and quality of the video output. ``fmt`` can be one
|
||||
of: rgb8, rgb10, rgb10_a2, rgb16, rgb16f, rgb32f, rgba12, rgba16, rgba16f,
|
||||
rgba16hf, rgba32f. Default: ``auto``, which maps to rgba16 on desktop GL,
|
||||
and rgba16f or rgb10_a2 on GLES (e.g. ANGLE), unless GL_EXT_texture_norm16
|
||||
is available.
|
||||
rgba16hf, rgba32f.
|
||||
|
||||
Default: ``auto``, which first attempts to utilize 16bit float
|
||||
(rgba16f, rgba16hf), and falls back to rgba16 if those are not available.
|
||||
Finally, attempts to utilize rgb10_a2 or rgba8 if all of the previous formats
|
||||
are not available.
|
||||
|
||||
``--gamma-factor=<0.1..2.0>``
|
||||
Set an additional raw gamma factor (default: 1.0). If gamma is adjusted in
|
||||
@@ -4856,7 +5045,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
are:
|
||||
|
||||
auto
|
||||
Disable any adaptation (default)
|
||||
Disable any adaptation, except for atypical color spaces. Specifically,
|
||||
wide/unusual gamuts get automatically adapted to BT.709, while standard
|
||||
gamut (i.e. BT.601 and BT.709) content is not touched. (default)
|
||||
bt.470m
|
||||
ITU-R BT.470 M
|
||||
bt.601-525
|
||||
@@ -4888,7 +5079,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
Valid values are:
|
||||
|
||||
auto
|
||||
Disable any adaptation (default)
|
||||
Disable any adaptation, except for atypical transfers. Specifically,
|
||||
HDR or linear light source material gets automatically converted to
|
||||
gamma 2.2, while SDR content is not touched. (default)
|
||||
bt.1886
|
||||
ITU-R BT.1886 curve (assuming infinite contrast)
|
||||
srgb
|
||||
@@ -4922,6 +5115,39 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
The user should independently guarantee this before using these signal
|
||||
formats for display.
|
||||
|
||||
``--target-peak=<nits>``
|
||||
Specifies the measured peak brightness of the output display, in cd/m^2
|
||||
(AKA nits). The interpretation of this brightness depends on the configured
|
||||
``--target-trc``. In all cases, it imposes a limit on the signal values
|
||||
that will be sent to the display. If the source exceeds this brightness
|
||||
level, a tone mapping filter will be inserted. For HLG, it has the
|
||||
additional effect of parametrizing the inverse OOTF, in order to get
|
||||
colorimetrically consistent results with the mastering display. For SDR, or
|
||||
when using an ICC (profile (``--icc-profile``), setting this to a value
|
||||
above 100 essentially causes the display to be treated as if it were an HDR
|
||||
display in disguise. (See the note below)
|
||||
|
||||
By default, the chosen peak defaults to an appropriate value based on the
|
||||
TRC in use. For SDR curves, it defaults to 100. For HDR curves, it
|
||||
defaults to 100 * the transfer function's nominal peak.
|
||||
|
||||
.. note::
|
||||
|
||||
When using an SDR transfer function, this is normally not needed, and
|
||||
setting it may lead to very unexpected results. The one time it *is*
|
||||
useful is if you want to calibrate a HDR display using traditional
|
||||
transfer functions and calibration equipment. In such cases, you can
|
||||
set your HDR display to a high brightness such as 800 cd/m^2, and then
|
||||
calibrate it to a standard curve like gamma2.8. Setting this value to
|
||||
800 would then instruct mpv to essentially treat it as an HDR display
|
||||
with the given peak. This may be a good alternative in environments
|
||||
where PQ or HLG input to the display is not possible, and makes it
|
||||
possible to use HDR displays with mpv regardless of operating system
|
||||
support for HDMI HDR metadata.
|
||||
|
||||
In such a configuration, we highly recommend setting ``--tone-mapping``
|
||||
to ``mobius`` or even ``clip``.
|
||||
|
||||
``--tone-mapping=<value>``
|
||||
Specifies the algorithm used for tone-mapping images onto the target
|
||||
display. This is relevant for both HDR->SDR conversion as well as gamut
|
||||
@@ -4938,7 +5164,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
for in-range material as much as possible. Use this when you care about
|
||||
color accuracy more than detail preservation. This is somewhere in
|
||||
between ``clip`` and ``reinhard``, depending on the value of
|
||||
``--tone-mapping-param``. (default)
|
||||
``--tone-mapping-param``.
|
||||
reinhard
|
||||
Reinhard tone mapping algorithm. Very simple continuous curve.
|
||||
Preserves overall image brightness but uses nonlinear contrast, which
|
||||
@@ -4949,7 +5175,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
desaturating everything. Developed by John Hable for use in video
|
||||
games. Use this when you care about detail preservation more than
|
||||
color/brightness accuracy. This is roughly equivalent to
|
||||
``--hdr-tone-mapping=reinhard --tone-mapping-param=0.24``.
|
||||
``--hdr-tone-mapping=reinhard --tone-mapping-param=0.24``. If possible,
|
||||
you should also enable ``--hdr-compute-peak`` for the best results.
|
||||
(Default)
|
||||
gamma
|
||||
Fits a logarithmic transfer between the tone curves.
|
||||
linear
|
||||
@@ -4978,13 +5206,15 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
linear
|
||||
Specifies the scale factor to use while stretching. Defaults to 1.0.
|
||||
|
||||
``--hdr-compute-peak``
|
||||
Compute the HDR peak per-frame of relying on tagged metadata. These values
|
||||
are averaged over local regions as well as over several frames to prevent
|
||||
the value from jittering around too much. This option basically gives you
|
||||
dynamic, per-scene tone mapping. Requires compute shaders, which is a
|
||||
fairly recent OpenGL feature, and will probably also perform horribly on
|
||||
some drivers, so enable at your own risk.
|
||||
``--hdr-compute-peak=<auto|yes|no>``
|
||||
Compute the HDR peak and frame average brightness per-frame instead of
|
||||
relying on tagged metadata. These values are averaged over local regions as
|
||||
well as over several frames to prevent the value from jittering around too
|
||||
much. This option basically gives you dynamic, per-scene tone mapping.
|
||||
Requires compute shaders, which is a fairly recent OpenGL feature, and will
|
||||
probably also perform horribly on some drivers, so enable at your own risk.
|
||||
The special value ``auto`` (default) will enable HDR peak computation
|
||||
automatically if compute shaders and SSBOs are supported.
|
||||
|
||||
``--tone-mapping-desaturate=<value>``
|
||||
Apply desaturation for highlights. The parameter essentially controls the
|
||||
@@ -4994,8 +5224,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
into white instead. This makes images feel more natural, at the cost of
|
||||
reducing information about out-of-range colors.
|
||||
|
||||
The default of 1.0 provides a good balance that roughly matches the look
|
||||
and feel of the ACES ODT curves. A setting of 0.0 disables this option.
|
||||
The default of 0.5 provides a good balance. This value is weaker than the
|
||||
ACES ODT curves' recommendation, but works better for most content in
|
||||
practice. A setting of 0.0 disables this option.
|
||||
|
||||
``--gamut-warning``
|
||||
If enabled, mpv will mark all clipped/out-of-gamut pixels that exceed a
|
||||
@@ -5027,6 +5258,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
NOTE: On Windows, the default profile must be an ICC profile. WCS profiles
|
||||
are not supported.
|
||||
|
||||
Applications using libmpv with the render API need to provide the ICC
|
||||
profile via ``MPV_RENDER_PARAM_ICC_PROFILE``.
|
||||
|
||||
``--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
|
||||
@@ -5053,7 +5287,7 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
Size of the 3D LUT generated from the ICC profile in each dimension.
|
||||
Default is 64x64x64. Sizes may range from 2 to 512.
|
||||
|
||||
``--icc-contrast=<0-100000>``
|
||||
``--icc-contrast=<0-1000000>``
|
||||
Specifies an upper limit on the target device's contrast ratio. This is
|
||||
detected automatically from the profile if possible, but for some profiles
|
||||
it might be missing, causing the contrast to be assumed as infinite. As a
|
||||
@@ -5123,6 +5357,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
flipping GL front and backbuffers immediately (i.e. it doesn't call it
|
||||
in display-sync mode).
|
||||
|
||||
On OSX this is always deactivated because it only causes performance
|
||||
problems and other regressions.
|
||||
|
||||
``--gpu-dumb-mode=<yes|no|auto>``
|
||||
This mode is extremely restricted, and will disable most extended
|
||||
features. That includes high quality scalers and custom shaders!
|
||||
@@ -5147,16 +5384,6 @@ The following video options are currently all specific to ``--vo=gpu`` and
|
||||
NOTE: This is not cleaned automatically, so old, unused cache files may
|
||||
stick around indefinitely.
|
||||
|
||||
``--cuda-decode-device=<auto|0..>``
|
||||
Choose the GPU device used for decoding when using the ``cuda`` hwdec.
|
||||
|
||||
By default, the device that is being used to provide OpenGL output will
|
||||
also be used for decoding (and in the vast majority of cases, only one
|
||||
GPU will be present).
|
||||
|
||||
Note that when using the ``cuda-copy`` hwdec, a different option must be
|
||||
passed: ``--vd-lavc-o=gpu=<0..>``.
|
||||
|
||||
Miscellaneous
|
||||
-------------
|
||||
|
||||
@@ -5186,6 +5413,26 @@ 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-timing-offset=<seconds>``
|
||||
Control how long before video display target time the frame should be
|
||||
rendered (default: 0.050). If a video frame should be displayed at a
|
||||
certain time, the VO will start rendering the frame earlier, and then will
|
||||
perform a blocking wait until the display time, and only then "swap" the
|
||||
frame to display. The rendering cannot start before the previous frame is
|
||||
displayed, so this value is implicitly limited by the video framerate. With
|
||||
normal video frame rates, the default value will ensure that rendering is
|
||||
always immediately started after the previous frame was displayed. On the
|
||||
other hand, setting a too high value can reduce responsiveness with low
|
||||
FPS value.
|
||||
|
||||
For client API users using the render API (or the deprecated ``opengl-cb``
|
||||
API), this option is interesting, because you can stop the render API
|
||||
from limiting your FPS (see ``mpv_render_context_render()`` documentation).
|
||||
|
||||
This applies only to audio timing modes (e.g. ``--video-sync=audio``). In
|
||||
other modes (``--video-sync=display-...``), video timing relies on vsync
|
||||
blocking, and this option is not used.
|
||||
|
||||
``--video-sync=<audio|...>``
|
||||
How the player synchronizes audio and video.
|
||||
|
||||
@@ -5318,7 +5565,8 @@ Miscellaneous
|
||||
|
||||
Unlike ``--sub-files`` and ``--audio-files``, this includes all tracks, and
|
||||
does not cause default stream selection over the "proper" file. This makes
|
||||
it slightly less intrusive.
|
||||
it slightly less intrusive. (In mpv 0.28.0 and before, this was not quite
|
||||
strictly enforced.)
|
||||
|
||||
This is a list option. See `List Options`_ for details.
|
||||
|
||||
@@ -5399,6 +5647,8 @@ Miscellaneous
|
||||
both tracks need to have the same width, or filter initialization
|
||||
will fail (you can add ``scale`` filters before the ``vstack`` filter
|
||||
to fix the size).
|
||||
To load a video track from another file, you can use
|
||||
``--external-file=other.mkv``.
|
||||
- ``--lavfi-complex='[aid1] asplit [t1] [ao] ; [t1] showvolume [t2] ; [vid1] [t2] overlay [vo]'``
|
||||
Play audio track 1, and overlay the measured volume for each speaker
|
||||
over video track 1.
|
||||
|
||||
+15
-1
@@ -128,7 +128,7 @@ Configuration
|
||||
-------------
|
||||
|
||||
The OSC offers limited configuration through a config file
|
||||
``lua-settings/osc.conf`` placed in mpv's user dir and through the
|
||||
``script-opts/osc.conf`` placed in mpv's user dir and through the
|
||||
``--script-opts`` command-line option. Options provided through the command-line
|
||||
will override those from the config file.
|
||||
|
||||
@@ -171,6 +171,15 @@ Configurable Options
|
||||
marker with guide), or bar (fill).
|
||||
Default pre-0.21.0 was 'slider'.
|
||||
|
||||
``seekbarkeyframes``
|
||||
Default: yes
|
||||
|
||||
Controls the mode used to seek when dragging the seekbar. By default,
|
||||
keyframes are used. If set to false, exact seeking on mouse drags
|
||||
will be used instead. Keyframes are preferred, but exact seeks may be
|
||||
useful in cases where keyframes cannot be found. Note that using exact
|
||||
seeks can potentially make mouse dragging much slower.
|
||||
|
||||
``deadzonesize``
|
||||
Default: 0.5
|
||||
|
||||
@@ -272,6 +281,11 @@ Configurable Options
|
||||
|
||||
Display timecodes with milliseconds
|
||||
|
||||
``seekranges``
|
||||
Default: yes
|
||||
|
||||
Display seekable ranges on the seekbar
|
||||
|
||||
``visibility``
|
||||
Default: auto (auto hide/show on mouse move)
|
||||
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ monospaced digits is recommended. By default, the open source font
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
This script can be customized through a config file ``lua-settings/stats.conf``
|
||||
This script can be customized through a config file ``script-opts/stats.conf``
|
||||
placed in mpv's user directory and through the ``--script-opts`` command-line
|
||||
option. The configuration syntax is described in `ON SCREEN CONTROLLER`_.
|
||||
|
||||
|
||||
+41
-28
@@ -1,8 +1,11 @@
|
||||
VIDEO FILTERS
|
||||
=============
|
||||
|
||||
Video filters allow you to modify the video stream and its properties. The
|
||||
syntax is:
|
||||
Video filters allow you to modify the video stream and its properties. All of
|
||||
the information described in this section applies to audio filters as well
|
||||
(generally using the prefix ``--af`` instead of ``--vf``).
|
||||
|
||||
The exact syntax is:
|
||||
|
||||
``--vf=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Setup a chain of video filters. This consists on the filter name, and an
|
||||
@@ -41,11 +44,18 @@ syntax is:
|
||||
the ``lavfi`` filter, which uses a very similar syntax as mpv (MPlayer
|
||||
historically) to specify filters and their parameters.
|
||||
|
||||
Filters can be manipulated at run time. You can use ``@`` labels as described
|
||||
above in combination with the ``vf`` command (see `COMMAND INTERFACE`_) to get
|
||||
more control over this. Initially disabled filters with ``!`` are useful for
|
||||
this as well.
|
||||
|
||||
You can also set defaults for each filter. The defaults are applied before the
|
||||
normal filter parameters.
|
||||
normal filter parameters. This is deprecated and never worked for the
|
||||
libavfilter bridge.
|
||||
|
||||
``--vf-defaults=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Set defaults for each filter.
|
||||
Set defaults for each filter. (Deprecated. ``--af-defaults`` is deprecated
|
||||
as well.)
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -75,15 +85,20 @@ normal filter parameters.
|
||||
Video filters are managed in lists. There are a few commands to manage the
|
||||
filter list.
|
||||
|
||||
``--vf-add=<filter1[,filter2,...]>``
|
||||
Appends the filters given as arguments to the filter list.
|
||||
``--vf-add=filter``
|
||||
Appends the filter given as arguments to the filter list. (Passing multiple
|
||||
filters is currently still possible, but deprecated.)
|
||||
|
||||
``--vf-pre=<filter1[,filter2,...]>``
|
||||
Prepends the filters given as arguments to the filter list.
|
||||
``--vf-pre=filter``
|
||||
Prepends the filters given as arguments to the filter list. (Passing
|
||||
multiple filters is currently still possible, but deprecated.)
|
||||
|
||||
``--vf-del=<index1[,index2,...]>``
|
||||
Deletes the filters at the given indexes. Index numbers start at 0,
|
||||
negative numbers address the end of the list (-1 is the last).
|
||||
``--vf-del=filter``
|
||||
Deletes the filter. The filter can even given the way it was added (filter
|
||||
name and its full argument list), by label (prefixed with ``@``), or as
|
||||
index number. Index numbers start at 0, negative numbers address the end of
|
||||
the list (-1 is the last). (Passing multiple filters is currently still
|
||||
possible, but deprecated.)
|
||||
|
||||
``--vf-clr``
|
||||
Completely empties the filter list.
|
||||
@@ -94,10 +109,6 @@ With filters that support it, you can access parameters by their name.
|
||||
Prints the parameter names and parameter value ranges for a particular
|
||||
filter.
|
||||
|
||||
``--vf=<filter=named_parameter1=value1[:named_parameter2=value2:...]>``
|
||||
Sets a named parameter to the given value. Use on and off or yes and no to
|
||||
set flag parameters.
|
||||
|
||||
Available mpv-only filters are:
|
||||
|
||||
``format=fmt=<value>:colormatrix=<value>:...``
|
||||
@@ -110,9 +121,7 @@ Available mpv-only filters are:
|
||||
|
||||
``<fmt>``
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: don't change).
|
||||
``<outfmt>``
|
||||
Format name that should be substituted for the output. If they do not
|
||||
have the same bytes per pixel and chroma subsampling, it will fail.
|
||||
|
||||
``<colormatrix>``
|
||||
Controls the YUV to RGB color space conversion when playing video. There
|
||||
are various standards. Normally, BT.601 should be used for SD video, and
|
||||
@@ -120,7 +129,7 @@ Available mpv-only filters are:
|
||||
results in slightly under or over saturated and shifted colors.
|
||||
|
||||
These options are not always supported. Different video outputs provide
|
||||
varying degrees of support. The ``opengl`` and ``vdpau`` video output
|
||||
varying degrees of support. The ``gpu`` and ``vdpau`` video output
|
||||
drivers usually offer full support. The ``xv`` output can set the color
|
||||
space if the system video driver supports it, but not input and output
|
||||
levels. The ``scale`` video filter can configure color space and input
|
||||
@@ -163,7 +172,7 @@ Available mpv-only filters are:
|
||||
used to override the setting.
|
||||
|
||||
This option only affects video output drivers that perform color
|
||||
management, for example ``opengl`` with the ``target-prim`` or
|
||||
management, for example ``gpu`` with the ``target-prim`` or
|
||||
``icc-profile`` suboptions set.
|
||||
|
||||
If this option is set to ``auto`` (which is the default), the video's
|
||||
@@ -459,7 +468,7 @@ Available mpv-only filters are:
|
||||
other userdata type will result in hard crashes.
|
||||
|
||||
``vavpp``
|
||||
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=opengl``
|
||||
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=gpu``
|
||||
only. Currently deinterlaces. This filter is automatically inserted if
|
||||
deinterlacing is requested (either using the ``d`` key, by default mapped to
|
||||
the command ``cycle deinterlace``, or the ``--deinterlace`` option).
|
||||
@@ -469,18 +478,22 @@ Available mpv-only filters are:
|
||||
|
||||
no
|
||||
Don't perform deinterlacing.
|
||||
auto
|
||||
Select the best quality deinterlacing algorithm (default). This
|
||||
goes by the order of the options as documented, with
|
||||
``motion-compensated`` being considered best quality.
|
||||
first-field
|
||||
Show only first field.
|
||||
bob
|
||||
bob deinterlacing (default).
|
||||
bob deinterlacing.
|
||||
weave, motion-adaptive, motion-compensated
|
||||
Advanced deinterlacing algorithms. Whether these actually work
|
||||
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).
|
||||
:no: Deinterlace all frames (default).
|
||||
:yes: Only deinterlace frames marked as interlaced.
|
||||
|
||||
``reversal-bug=<yes|no>``
|
||||
:no: Use the API as it was interpreted by older Mesa drivers. While
|
||||
@@ -492,12 +505,12 @@ Available mpv-only filters are:
|
||||
algorithms.
|
||||
|
||||
``vdpaupp``
|
||||
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=opengl``
|
||||
VDPAU video post processing. Works with ``--vo=vdpau`` and ``--vo=gpu``
|
||||
only. This filter is automatically inserted if deinterlacing is requested
|
||||
(either using the ``d`` key, by default mapped to the command
|
||||
``cycle deinterlace``, or the ``--deinterlace`` option). When enabling
|
||||
deinterlacing, it is always preferred over software deinterlacer filters
|
||||
if the ``vdpau`` VO is used, and also if ``opengl`` is used and hardware
|
||||
if the ``vdpau`` VO is used, and also if ``gpu`` is used and hardware
|
||||
decoding was activated at least once (i.e. vdpau was loaded).
|
||||
|
||||
``sharpen=<-1-1>``
|
||||
@@ -535,7 +548,7 @@ Available mpv-only filters are:
|
||||
Try to apply inverse telecine, needs motion adaptive temporal
|
||||
deinterlacing.
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
If ``yes``, only deinterlace frames marked as interlaced (default: no).
|
||||
``hqscaling=<0-9>``
|
||||
0
|
||||
Use default VDPAU scaling (default).
|
||||
@@ -549,7 +562,7 @@ Available mpv-only filters are:
|
||||
``deint=<yes|no>``
|
||||
Whether deinterlacing is enabled (default: no).
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
If ``yes``, only deinterlace frames marked as interlaced (default: no).
|
||||
``mode=<blend|bob|adaptive|mocomp|ivctc|none>``
|
||||
Tries to select a video processor with the given processing capability.
|
||||
If a video processor supports multiple capabilities, it is not clear
|
||||
|
||||
+45
-32
@@ -324,7 +324,7 @@ Available video output drivers are:
|
||||
This is low quality, and has issues with OSD.
|
||||
|
||||
.. note:: This driver is for compatibility with crappy systems. You can
|
||||
use vaapi hardware decoding with ``--vo=opengl`` too.
|
||||
use vaapi hardware decoding with ``--vo=gpu`` too.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -428,37 +428,21 @@ Available video output drivers are:
|
||||
``--vo-image-outdir=<dirname>``
|
||||
Specify the directory to save the image files to (default: ``./``).
|
||||
|
||||
``wayland`` (Wayland only)
|
||||
Wayland shared memory video output as fallback for ``opengl``.
|
||||
``libmpv``
|
||||
For use with libmpv direct embedding. As a special case, on OS X it
|
||||
is used like a normal VO within mpv (cocoa-cb). Otherwise useless in any
|
||||
other contexts.
|
||||
(See ``<mpv/render.h>``.)
|
||||
|
||||
.. note:: This driver is for compatibility with systems that don't provide
|
||||
working OpenGL drivers.
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
``--vo-wayland-alpha``
|
||||
Use a buffer format that supports videos and images with alpha
|
||||
information
|
||||
``--vo-wayland-rgb565``
|
||||
Use RGB565 as buffer format. This format is implemented on most
|
||||
platforms, especially on embedded where it is far more efficient then
|
||||
RGB8888.
|
||||
``--vo-wayland-triple-buffering``
|
||||
Use 3 buffers instead of 2. This can lead to more fluid playback, but
|
||||
uses more memory.
|
||||
|
||||
``opengl-cb``
|
||||
For use with libmpv direct OpenGL embedding; useless in any other contexts.
|
||||
(See ``<mpv/opengl_cb.h>``.)
|
||||
|
||||
This also supports many of the options the ``opengl`` VO has.
|
||||
This also supports many of the options the ``gpu`` VO has, depending on the
|
||||
backend.
|
||||
|
||||
``rpi`` (Raspberry Pi)
|
||||
Native video output on the Raspberry Pi using the MMAL API.
|
||||
|
||||
This is deprecated. Use ``--vo=opengl`` instead, which is the default and
|
||||
This is deprecated. Use ``--vo=gpu`` instead, which is the default and
|
||||
provides the same functionality. The ``rpi`` VO will be removed in
|
||||
mpv 0.23.0. Its functionality was folded into --vo=opengl, which now uses
|
||||
mpv 0.23.0. Its functionality was folded into --vo=gpu, which now uses
|
||||
RPI hardware decoding by treating it as a hardware overlay (without applying
|
||||
GL filtering). Also to be changed in 0.23.0: the --fs flag will be reset to
|
||||
"no" by default (like on the other platforms).
|
||||
@@ -488,7 +472,7 @@ Available video output drivers are:
|
||||
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
|
||||
Should be used when one doesn't want to install full-blown graphical
|
||||
environment (e.g. no X). Does not support hardware acceleration (if you
|
||||
need this, check the ``drm`` backend for ``opengl`` VO).
|
||||
need this, check the ``drm`` backend for ``gpu`` VO).
|
||||
|
||||
The following global options are supported by this video output:
|
||||
|
||||
@@ -501,13 +485,42 @@ Available video output drivers are:
|
||||
(default: empty)
|
||||
|
||||
``--drm-mode=<number>``
|
||||
Mode ID to use (resolution, bit depth and frame rate).
|
||||
Mode ID to use (resolution and frame rate).
|
||||
(default: 0)
|
||||
|
||||
``--drm-overlay=<number>``
|
||||
Select the DRM overlay index to use.
|
||||
Overlay index is zero based, and related to crtc.
|
||||
(default: 0)
|
||||
``--drm-osd-plane-id=<number>``
|
||||
Select the DRM plane index to use for OSD (or OSD and video).
|
||||
Index is zero based, and related to crtc.
|
||||
When using this option with the drm_prime renderer, it will only affect
|
||||
the OSD contents. Otherwise it will set OSD & video plane.
|
||||
(default: primary plane)
|
||||
|
||||
``--drm-video-plane-id=<number>``
|
||||
Select the DRM plane index to use for video layer.
|
||||
Index is zero based, and related to crtc.
|
||||
This option only has effect when using the drm_prime renderer (which
|
||||
supports several layers) together with ``vo=gpu`` and ``gpu-context=drm``.
|
||||
(default: first overlay plane)
|
||||
|
||||
``--drm-format=<xrgb8888,xrgb2101010>``
|
||||
Select the DRM format to use (default: xrgb8888). This allows you to
|
||||
choose the bit depth of the DRM mode. xrgb8888 is your usual 24 bit per
|
||||
pixel/8 bits per channel packed RGB format with 8 bits of padding.
|
||||
xrgb2101010 is a packed 30 bits per pixel/10 bits per channel packed RGB
|
||||
format with 2 bits of padding.
|
||||
|
||||
Unless you have an intel graphics card, a recent kernel and a recent
|
||||
version of mesa (>=18) xrgb2101010 is unlikely to work for you.
|
||||
|
||||
This currently only has an effect when used together with the ``drm``
|
||||
backend for the ``gpu`` VO. The ``drm`` VO always uses xrgb8888.
|
||||
|
||||
``--drm-osd-size=<[WxH]>``
|
||||
Sets the OSD OpenGL size to the specified size. OSD will then be upscaled
|
||||
to the current screen resolution. This option can be useful when using
|
||||
several layers in high resolutions with a GPU which cannot handle it.
|
||||
Note : this option is only available with DRM atomic support.
|
||||
(default: display resolution)
|
||||
|
||||
``mediacodec_embed`` (Android)
|
||||
Renders ``IMGFMT_MEDIACODEC`` frames directly to an ``android.view.Surface``.
|
||||
|
||||
@@ -177,13 +177,13 @@ video/out/:
|
||||
Video output. They also create GUI windows and handle user input. In most
|
||||
cases, the windowing code is shared among VOs, like x11_common.c for X11 and
|
||||
w32_common.c for Windows. The VOs stand between frontend and windowing code.
|
||||
vo_opengl can pick a windowing system at runtime, e.g. the same binary can
|
||||
vo_gpu can pick a windowing system at runtime, e.g. the same binary can
|
||||
provide both X11 and Cocoa support on OSX.
|
||||
|
||||
VOs can be reconfigured at runtime. A vo_reconfig() call can change the video
|
||||
resolution and format, without destroying the window.
|
||||
|
||||
vo_opengl should be taken as reference.
|
||||
vo_gpu should be taken as reference.
|
||||
|
||||
audio/:
|
||||
format.h/format.c define the uncompressed audio formats. (As well as some
|
||||
|
||||
@@ -132,7 +132,11 @@ FFmpeg dependencies:
|
||||
- OpenSSL or GnuTLS (have to be explicitly enabled when compiling FFmpeg)
|
||||
- libx264/libmp3lame/libfdk-aac if you want to use encoding (have to be
|
||||
explicitly enabled when compiling FFmpeg)
|
||||
- Libav also works, but some features will not work. (See section below.)
|
||||
- For native DASH playback, FFmpeg needs to be built with --enable-libxml2
|
||||
(although there are security implications).
|
||||
- For good nvidia support on Linux, make sure nv-codec-headers is installed
|
||||
and can be found by configure.
|
||||
- Libav support is broken. (See section below.)
|
||||
|
||||
Most of the above libraries are available in suitable versions on normal
|
||||
Linux distributions. For ease of compiling the latest git master of everything,
|
||||
@@ -149,8 +153,8 @@ or cross-compile from Linux with MinGW. See
|
||||
|
||||
|
||||
Generally, mpv should work with the latest release as well as the git version
|
||||
of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work
|
||||
with FFmpeg only (subtitle formats in particular).
|
||||
of FFmpeg. Libav support is currently broken, because they did not add certain
|
||||
FFmpeg API changes which mpv relies on.
|
||||
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
+94
-124
@@ -1,27 +1,8 @@
|
||||
Release 0.28.1
|
||||
Release 0.29.0
|
||||
==============
|
||||
|
||||
This release fixes CVE-2018-6360.
|
||||
This release requires FFmpeg 4.0 or newer.
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
- ytdl_hook: whitelist protocols from urls retrieved from youtube-dl (#5456)
|
||||
|
||||
Release 0.28.0
|
||||
==============
|
||||
|
||||
This release needs recent FFmpeg (newer than 3.4) due to major refactoring.
|
||||
Required library versions:
|
||||
|
||||
* libavutil >= 56.6.100
|
||||
* libavcodec >= 58.7.100
|
||||
* libavformat >= 58.0.102
|
||||
* libswscale >= 5.0.101
|
||||
* libavfilter >= 7.0.101
|
||||
* libswresample >= 3.0.100
|
||||
|
||||
The LGPL 2.1+ relicensing process is now mostly complete. The remaining GPL-only
|
||||
code (see Copyright file) can be disabled at build time by using --enable-lgpl.
|
||||
|
||||
Features
|
||||
--------
|
||||
@@ -29,33 +10,40 @@ Features
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Add DRM_PRIME Format Handling and Display for RockChip MPP decoders
|
||||
- csputils: Add support for Display P3 primaries
|
||||
- demux: support multiple seekable cached ranges, display cache ranges on OSC
|
||||
- demux_playlist: support .url files (#5107)
|
||||
- dvb: Add multiple frontends support (up to 8)
|
||||
- dvb: implement parsing of modulation for VDR-style channels config
|
||||
- hwdec: add mediacodec hardware decoder for IMGFMT_MEDIACODEC frames,
|
||||
rename mediacodec to mediacodec-copy
|
||||
- lua: integrate stats.lua script (bound to i/I by default)
|
||||
- vd_lavc: add support for nvdec hwaccel
|
||||
- vo_gpu: add android opengl backend
|
||||
- vo_gpu: initial d3d11 support
|
||||
- vo_gpu: vulkan support
|
||||
- ao/openal: Add better sample format and channel layout selection
|
||||
- ao/openal: Add support for direct channels output
|
||||
- cocoa-cb: initial implementation via opengl-cb API
|
||||
- context_drm_egl: Introduce 30bpp support (requires Mesa 18.0.0_rc4 or later)
|
||||
- osx: add some more menu bar items as suggested by Apples's HIG
|
||||
- vo_gpu: make screenshots use the GL renderer (#5498, #5240)
|
||||
- x11: support Shift+TAB as an input key bind (#5849)
|
||||
- ytdl_hook: support native dash demuxer, if present
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- ao_alsa, ao_pulse: reduce requested buffer size
|
||||
- audio: change format negotiation, remove channel remix fudging
|
||||
- client API: deprecate opengl-cb API and introduce a replacement API
|
||||
- demux: lower demuxer cache default sizes from 400MB/400MB to 50MB/150MB
|
||||
- osx: always deactivate the early opengl flush on macOS
|
||||
- scripting: change when/how player waits for scripts being loaded
|
||||
- vo_gpu: various improvements to HDR peak detection
|
||||
- vo_gpu: various improvements to HDR tone mapping
|
||||
- wayland_common: update to stable xdg-shell
|
||||
- ytdl_hook: parse youtube playlist urls to set start index if `--ytdl-raw-option=yes-playlist=` is specified
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- af_lavrresample: deprecate this filter
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- af: remove deprecated audio filters (channels, equalizer, pan, volume;
|
||||
replacements in lavfi)
|
||||
- vf: remove most GPL video filters (crop, dsize, expand, flip, gradfun, mirror,
|
||||
noformat, pullup, rotate, scale, stereo3d, yadif; replacements in lavfi)
|
||||
- vf_buffer: remove this filter
|
||||
- video: remove automatic stereo3d filter insertion
|
||||
- vo_gpu: remove hwdec_vaglx interop
|
||||
- vo_opengl: refactor into vo_gpu
|
||||
- vo_wayland: remove
|
||||
- build: drop support for SDL1 (only applied to ao_sdl)
|
||||
|
||||
|
||||
Options and Commands
|
||||
@@ -64,106 +52,88 @@ Options and Commands
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- demux: add option to create CC tracks eagerly (--sub-create-cc-track)
|
||||
- options: add --start=none to reset previously set start time
|
||||
- options: add --vlang switch
|
||||
- add a number of --audio-resample-* options, which should from now on be used instead of --af-defaults=lavrresample:
|
||||
- ao/openal: --openal-direct-channels
|
||||
- command: add --osd-on-seek option defaulting to bar
|
||||
- command: add a change-list command (#5435)
|
||||
- options: add --http-proxy
|
||||
- options: add a builtin low-latency profile
|
||||
- vaapi: add option to select a non-default device path with --vaapi-device (#4320)
|
||||
- video: add option to reduce latency by 1 or 2 frames
|
||||
- vo_gpu: introduce --target-peak (#5521)
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- cache: lower default size to 2*10MB
|
||||
- demux: bump the demuxer cache readahead duration to 10 hours
|
||||
- demux: use seekable cache for network by default, bump prefetch limit
|
||||
- msg: make --msg-level affect --log-file too
|
||||
- player/misc.c: allow both --length and --end to control play endpoint
|
||||
- player: match subtitles with language tags with --sub-auto=exact
|
||||
- rename --opengl-hwdec-interop to --gpu-hwdec-interop (now mostly useless)
|
||||
- vd_lavc: prefer nvdec over vdpau with --hwdec=auto (better codec and surface
|
||||
format support)
|
||||
- vd_lavc: rename --hwdec=rpi to --hwdec=mmal
|
||||
- command: change cycle-value command behavior (#5772)
|
||||
- config: replace config dir lua-settings/ with dir script-opts/
|
||||
- options: --lavfi-complex now requires uniquely named filter pads.
|
||||
- options: --ytdl is now always enabled, even for libmpv
|
||||
- options: make --deinterlace=yes always deinterlace (#5219)
|
||||
- options: slightly improve filter help output for lavfi bridge
|
||||
- vo: rename --vo=opengl-cb to --vo=libmpv
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- encoding: deprecate a bunch of obscure options (--ovoffset, --oaoffset, --ovfirst, --oafirst)
|
||||
- options: deprecate --vf-defaults and --af-defaults
|
||||
- osx: --gpu-context=cocoa with --gpu-api=opengl is now deprecated in favour of --vo=libmpv
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- options: remove --heartbeat-cmd and --heartbeat-interval
|
||||
(incidentally fixes #4888)
|
||||
- command: remove an old compatibility hack that allowed CLI aliases to be set as property, deprecated in 0.26.0
|
||||
- input: remove legacy command handling for MPlayer commands that were deprecated in 2013
|
||||
- options: drop --opensles-sample-rate, as --audio-samplerate should be used if desired
|
||||
- options: drop deprecated --videotoolbox-format, --ff-aid, --ff-vid, --ff-sid, --ad-spdif-dtshd, --softvol options
|
||||
- options: remove --video-stereo-mode
|
||||
- options: remove deprecated ao/vo auto profiles
|
||||
- options: remove the following encoding options: --ocopyts (now the default, old timestamp handling is gone), --oneverdrop (now default), --oharddup (you need to use --vf=fps=VALUE), --ofps, --oautofps, --omaxfps
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- TOOLS/autoload.lua: add ogm, ogg and opus extensions
|
||||
- Use /dev/tty instead of stdin for terminal input (#4190)
|
||||
- audio: add audio softvol processing to AO (replaces previously GPL’d code)
|
||||
- audio: fix channel conversion with NA channels (e.g. with ALSA)
|
||||
- audio: fix missing volume update on init and reinit
|
||||
- csputils: Fix DCI P3 primaries white point
|
||||
- demux: don't allow subtitles to mess up buffered time display
|
||||
- demux: fix .cue files with audio files that contain attached pictures
|
||||
- demux: fix accounting for seekable ranges on track switches (fixes missing
|
||||
audio when cycling through audio tracks with e.g. EDL, --merge-files, ordered
|
||||
chapters and youtube-dl pseudo DASH)
|
||||
- demux: fix crash with cue/ordered chapter files (#5027)
|
||||
- demux: speed up cache seeking with a coarse index
|
||||
- demux_lavf: always give libavformat the filename when probing
|
||||
(helps with mp3 files)
|
||||
- demux_mkv: add V_SNOW tag to mkv_video_tags
|
||||
- dvb: Fix long channel switching: next/prev channel
|
||||
- dvb: fixes for ATSC tuning
|
||||
- lavc_conv: clamp timestamps to positive (#5047)
|
||||
- macOS: fix bundle on macOS High Sierra (10.13) (#4926, #4866)
|
||||
- mp_image: always copy color attributes on hw download (#4804)
|
||||
- mp_image: select an explicit fallback for chroma location (#4804)
|
||||
- msg: bump log level of --log-file to -v -v
|
||||
- msg: reinterpret a bunch of message levels
|
||||
- osc: fix rare stack overflow when changing visibility mode
|
||||
- osdep/io: add android-related bullshit to fix files >2 GiB
|
||||
- player/playloop.c: respect playback start time when using --loop-file
|
||||
- player: allow seeking in cached parts of unseekable streams
|
||||
- player: make track language matching case insensitive (#5272)
|
||||
- player: use start timestamp for ab-looping if --ab-loop-a is absent
|
||||
- player: when loading external file, always add all track types (#5132)
|
||||
- restore-old-bindings.conf: add old macOS/Wayland AXIS bindings
|
||||
- screenshot: create directories from template
|
||||
- scripting: report dlerror() output
|
||||
- sd_ass: accept RFC8081 font media types
|
||||
- sd_ass: accept otc as fallback OpenType collection file extension
|
||||
- stream_libarchive: work around various types of locale braindeath
|
||||
(https://git.io/vbiFJ)
|
||||
- subprocess-win: don't change the mouse cursor when creating processes
|
||||
- video: add a hack to avoid missing subtitles with vf_sub (#5194)
|
||||
- video: fix alpha handling (#4983)
|
||||
- video: fix memory leaks (roughly 1 KB per decoded frame) with hwdec copy modes
|
||||
- video: fix rotation and deinterlace auto filters
|
||||
- video: properly pass through ICC data
|
||||
- vo: add support for externally driven renderloop and make wayland use it
|
||||
(partially fixes display-sync under wayland; disables rendering when window
|
||||
is invisible)
|
||||
- vo_gpu: change --tone-mapping-desaturate algorithm
|
||||
- vo_gpu: enable 3DLUTs in dumb mode
|
||||
- vo_gpu: fix gamma scale
|
||||
- vo_gpu: fix mobius tone mapping compatibility to GLSL 120 (#5069)
|
||||
- vo_gpu: fix video sometimes not being rerendered on equalizer change
|
||||
- vo_gpu: kill off FBOTEX_FUZZY (#1814)
|
||||
- vo_gpu: opengl: fix possible screenshot window crash (#4905)
|
||||
- vo_gpu: opengl: use GLX_MESA_swap_control where available
|
||||
- vo_gpu: reduce the --alpha=blend-tiles checkerboard intensity
|
||||
- vo_gpu: win: remove exclusive-fullscreen detection hack
|
||||
- vo_lavc: remove messy delayed subtitle rendering logic (#4689)
|
||||
- wayland_common: implement output tracking, many cleanups and bugfixes
|
||||
- Windows: skip window snapping if Windows handled it
|
||||
- Windows: add more-POSIXy versions of open() and fstat() (#4711)
|
||||
- ytdl_hook: don't prepend ytdl:// to non-youtube links in playlists (#5003)
|
||||
- HIDRemote: fix volume buttons on macOS 10.13 (#5721)
|
||||
- TOOLS/autoload: Fix broken "disabled" option
|
||||
- TOOLS/autoload: be more robust with slow directory listings (#5618)
|
||||
- ao_sdl: fix default buffer size
|
||||
- build: add static libraries to libmpv.pc
|
||||
- build: bump waf to 2.0.9 (fixes Python 3.7 compatibility)
|
||||
- build: manually add standard library search paths for linking (#5791)
|
||||
- common: round all integer times to milliseconds
|
||||
- demux, player: fix playback of sparse video streams (w/ still images)
|
||||
- demux: support for some kinds of timed metadata (e.g. ICY)
|
||||
- demux_mkv: add V_AV1 identifier for AV1
|
||||
- demux_mkv: support Meridian Lossless Packing in Matroska (#5923)
|
||||
- display additional metadata tags during video playback
|
||||
- drm_atomic: Fix memory leaks in drm_atomic_create
|
||||
- enable cache for FUSE filesystems on OpenBSD and FreeBSD
|
||||
- gpu: prefer 16bit floating point FBO formats to 16bit integer ones
|
||||
- hwdec_vaegl: Fix VAAPI EGL interop used with gpu-context=drm
|
||||
- input.conf: use exact value for [ binding, which does the inverse of ]
|
||||
- input: add a keybinding to toggle hardware decoding
|
||||
- input: minor additions to default key bindings (#973)
|
||||
- osc: fix accidentally skipping files when seeking with slider
|
||||
- player: fix strange behavior on edition switching
|
||||
- player: fix track autoselection of external files yet again
|
||||
- terminal-unix: stop trying to read when terminal disappears (#5842)
|
||||
- video: actually wait for last frame being rendered on EOF
|
||||
- video: fix --video-rotate in some cases
|
||||
- video: fix crash with vdpau when reinitializing rendering (#5447)
|
||||
- vo_gpu: fix anamorphic screenshots (#5619)
|
||||
- vo_vdpau: fix resizing and rotation problems
|
||||
|
||||
|
||||
This listing is not complete. Check DOCS/client-api-changes.rst for a history
|
||||
of changes to the client API, and DOCS/interface-changes.rst for a history
|
||||
of changes to other user-visible interfaces.
|
||||
|
||||
A complete changelog can be seen by running `git log v0.27.0..v0.28.0`
|
||||
A complete changelog can be seen by running `git log v0.28.2..v0.29.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.27.0...v0.28.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.27.0..v0.28.0
|
||||
|
||||
https://github.com/mpv-player/mpv/compare/v0.28.2...v0.29.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.28.2..v0.29.0
|
||||
@@ -24,8 +24,8 @@ def is_user_lib(objfile, libname):
|
||||
|
||||
def otool(objfile):
|
||||
command = "otool -L %s | grep -e '\t' | awk '{ print $1 }'" % objfile
|
||||
output = subprocess.check_output(command, shell = True)
|
||||
return filter(partial(is_user_lib, objfile), output.split())
|
||||
output = subprocess.check_output(command, shell = True, universal_newlines=True)
|
||||
return set(filter(partial(is_user_lib, objfile), output.split()))
|
||||
|
||||
def install_name_tool_change(old, new, objfile):
|
||||
subprocess.call(["install_name_tool", "-change", old, new, objfile])
|
||||
@@ -35,7 +35,7 @@ def install_name_tool_id(name, objfile):
|
||||
|
||||
def libraries(objfile, result = dict()):
|
||||
libs_list = otool(objfile)
|
||||
result[objfile] = set(libs_list)
|
||||
result[objfile] = libs_list
|
||||
|
||||
for lib in libs_list:
|
||||
if lib not in result:
|
||||
@@ -80,15 +80,17 @@ def process_libraries(libs_dict, binary, processed = []):
|
||||
if p in libs_dict[src]:
|
||||
install_name_tool_change(p, lib_name(p), dst)
|
||||
|
||||
process_libraries(libs_dict, binary, ls)
|
||||
return ls
|
||||
|
||||
def main():
|
||||
binary = os.path.abspath(sys.argv[1])
|
||||
if not os.path.exists(lib_path(binary)):
|
||||
os.makedirs(lib_path(binary))
|
||||
libs = libraries(binary)
|
||||
print(libs)
|
||||
process_libraries(libs, binary)
|
||||
|
||||
libs_processed = process_libraries(libs, binary)
|
||||
while libs_processed is not None:
|
||||
libs_processed = process_libraries(libs, binary, libs_processed)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+25
-11
@@ -7,7 +7,9 @@
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
MAXENTRIES = 5000
|
||||
|
||||
local msg = require 'mp.msg'
|
||||
local options = require 'mp.options'
|
||||
local utils = require 'mp.utils'
|
||||
|
||||
o = {
|
||||
disabled = false
|
||||
@@ -25,8 +27,6 @@ EXTENSIONS = Set {
|
||||
'mp3', 'wav', 'ogm', 'flac', 'm4a', 'wma', 'ogg', 'opus',
|
||||
}
|
||||
|
||||
mputils = require 'mp.utils'
|
||||
|
||||
function add_files_at(index, files)
|
||||
index = index - 1
|
||||
local oldcount = mp.get_property_number("playlist-count", 1)
|
||||
@@ -84,21 +84,35 @@ function alnumcomp(x, y)
|
||||
return #xt < #yt
|
||||
end
|
||||
|
||||
local autoloaded = nil
|
||||
|
||||
function find_and_add_entries()
|
||||
local path = mp.get_property("path", "")
|
||||
local dir, filename = mputils.split_path(path)
|
||||
if o.disabled or #dir == 0 then
|
||||
local dir, filename = utils.split_path(path)
|
||||
msg.trace(("dir: %s, filename: %s"):format(dir, filename))
|
||||
if o.disabled then
|
||||
msg.verbose("stopping: autoload disabled")
|
||||
return
|
||||
elseif #dir == 0 then
|
||||
msg.verbose("stopping: not a local path")
|
||||
return
|
||||
end
|
||||
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
if (pl_count > 1 and autoload == nil) or
|
||||
-- check if this is a manually made playlist
|
||||
if (pl_count > 1 and autoloaded == nil) or
|
||||
(pl_count == 1 and EXTENSIONS[string.lower(get_extension(filename))] == nil) then
|
||||
return
|
||||
else
|
||||
autoload = true
|
||||
autoloaded = true
|
||||
end
|
||||
|
||||
local files = mputils.readdir(dir, "files")
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
local pl_current = mp.get_property_number("playlist-pos-1", 1)
|
||||
msg.trace(("playlist-pos-1: %s, playlist: %s"):format(pl_current,
|
||||
utils.to_string(pl)))
|
||||
|
||||
local files = utils.readdir(dir, "files")
|
||||
if files == nil then
|
||||
return
|
||||
end
|
||||
@@ -118,8 +132,6 @@ function find_and_add_entries()
|
||||
dir = ""
|
||||
end
|
||||
|
||||
local pl = mp.get_property_native("playlist", {})
|
||||
local pl_current = mp.get_property_number("playlist-pos", 0) + 1
|
||||
-- Find the current pl entry (dir+"/"+filename) in the sorted dir list
|
||||
local current
|
||||
for i = 1, #files do
|
||||
@@ -131,6 +143,7 @@ function find_and_add_entries()
|
||||
if current == nil then
|
||||
return
|
||||
end
|
||||
msg.trace("current file position in files: "..current)
|
||||
|
||||
local append = {[-1] = {}, [1] = {}}
|
||||
for direction = -1, 1, 2 do -- 2 iterations, with direction = -1 and +1
|
||||
@@ -144,6 +157,7 @@ function find_and_add_entries()
|
||||
local filepath = dir .. file
|
||||
if pl_e then
|
||||
-- If there's a playlist entry, and it's the same file, stop.
|
||||
msg.trace(pl_e.filename.." == "..filepath.." ?")
|
||||
if pl_e.filename == filepath then
|
||||
break
|
||||
end
|
||||
@@ -151,11 +165,11 @@ function find_and_add_entries()
|
||||
|
||||
if direction == -1 then
|
||||
if pl_current == 1 then -- never add additional entries in the middle
|
||||
mp.msg.info("Prepending " .. file)
|
||||
msg.info("Prepending " .. file)
|
||||
table.insert(append[-1], 1, filepath)
|
||||
end
|
||||
else
|
||||
mp.msg.info("Adding " .. file)
|
||||
msg.info("Adding " .. file)
|
||||
table.insert(append[1], filepath)
|
||||
end
|
||||
end
|
||||
|
||||
+3
-1
@@ -213,6 +213,7 @@ elements_matroska = (
|
||||
'Tag*, 7373, sub', (
|
||||
'Targets, 63c0, sub', (
|
||||
'TargetTypeValue, 68ca, uint',
|
||||
'TargetType, 63ca, str',
|
||||
'TargetTrackUID, 63c5, uint',
|
||||
'TargetEditionUID, 63c9, uint',
|
||||
'TargetChapterUID, 63c4, uint',
|
||||
@@ -221,7 +222,8 @@ elements_matroska = (
|
||||
'SimpleTag*, 67c8, sub', (
|
||||
'TagName, 45a3, str',
|
||||
'TagLanguage, 447a, str',
|
||||
'TagString, 4487, str'
|
||||
'TagString, 4487, str',
|
||||
'TagDefault, 4484, uint',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+4
-4
@@ -29,11 +29,11 @@ my @opts = parse_main_opts('--list-options', '^ (\-\-[^\s\*]*)\*?\s*(.*)');
|
||||
|
||||
die "Couldn't find any options" unless (@opts);
|
||||
|
||||
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*(.*)');
|
||||
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*(.*)');
|
||||
|
||||
my @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @af = parse_opts('--af=help', '^ ([^\s\:]*)\s*(.*)');
|
||||
my @vf = parse_opts('--vf=help', '^ ([^\s\:]*)\s*(.*)');
|
||||
|
||||
my @protos = parse_opts('--list-protocols', '^ ([^\s]*)');
|
||||
|
||||
|
||||
+4
-3
@@ -2,6 +2,7 @@ branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
- ci
|
||||
|
||||
environment:
|
||||
global:
|
||||
@@ -15,11 +16,11 @@ test: off
|
||||
|
||||
install:
|
||||
# Update core packages
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm --noprogressbar --ask=20
|
||||
# Update non-core packages
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm --noprogressbar --ask=20
|
||||
# Install required MSYS2 packages
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --needed base-devel perl python pkg-config
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --noprogressbar --needed base-devel perl python pkg-config
|
||||
# Now MSYS2 is up to date, do the rest of the install from a bash script
|
||||
- C:\msys64\usr\bin\bash -lc "cd && exec \"$APPVEYOR_BUILD_FOLDER\"/TOOLS/appveyor-install.sh"
|
||||
build_script:
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "chmap.h"
|
||||
|
||||
struct mp_aconverter;
|
||||
struct mp_aframe;
|
||||
struct mpv_global;
|
||||
struct mp_log;
|
||||
|
||||
struct mp_resample_opts {
|
||||
int filter_size;
|
||||
int phase_shift;
|
||||
int linear;
|
||||
double cutoff;
|
||||
int normalize;
|
||||
int allow_passthrough;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
#define MP_RESAMPLE_OPTS_DEF { \
|
||||
.filter_size = 16, \
|
||||
.cutoff = 0.0, \
|
||||
.phase_shift = 10, \
|
||||
.normalize = -1, \
|
||||
}
|
||||
|
||||
struct mp_aconverter *mp_aconverter_create(struct mpv_global *global,
|
||||
struct mp_log *log,
|
||||
const struct mp_resample_opts *opts);
|
||||
bool mp_aconverter_reconfig(struct mp_aconverter *p,
|
||||
int in_rate, int in_format, struct mp_chmap in_channels,
|
||||
int out_rate, int out_format, struct mp_chmap out_channels);
|
||||
void mp_aconverter_flush(struct mp_aconverter *p);
|
||||
void mp_aconverter_set_speed(struct mp_aconverter *p, double speed);
|
||||
bool mp_aconverter_write_input(struct mp_aconverter *p, struct mp_aframe *in);
|
||||
struct mp_aframe *mp_aconverter_read_output(struct mp_aconverter *p, bool *eof);
|
||||
double mp_aconverter_get_latency(struct mp_aconverter *p);
|
||||
+124
-9
@@ -32,6 +32,11 @@ struct mp_aframe {
|
||||
// We support spdif formats, which are allocated as AV_SAMPLE_FMT_S16.
|
||||
int format;
|
||||
double pts;
|
||||
double speed;
|
||||
};
|
||||
|
||||
struct avframe_opaque {
|
||||
double speed;
|
||||
};
|
||||
|
||||
static void free_frame(void *ptr)
|
||||
@@ -43,11 +48,11 @@ static void free_frame(void *ptr)
|
||||
struct mp_aframe *mp_aframe_create(void)
|
||||
{
|
||||
struct mp_aframe *frame = talloc_zero(NULL, struct mp_aframe);
|
||||
frame->pts = MP_NOPTS_VALUE;
|
||||
frame->av_frame = av_frame_alloc();
|
||||
if (!frame->av_frame)
|
||||
abort();
|
||||
talloc_set_destructor(frame, free_frame);
|
||||
mp_aframe_reset(frame);
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -61,6 +66,7 @@ struct mp_aframe *mp_aframe_new_ref(struct mp_aframe *frame)
|
||||
dst->chmap = frame->chmap;
|
||||
dst->format = frame->format;
|
||||
dst->pts = frame->pts;
|
||||
dst->speed = frame->speed;
|
||||
|
||||
if (mp_aframe_is_allocated(frame)) {
|
||||
if (av_frame_ref(dst->av_frame, frame->av_frame) < 0)
|
||||
@@ -80,6 +86,7 @@ void mp_aframe_reset(struct mp_aframe *frame)
|
||||
frame->chmap.num = 0;
|
||||
frame->format = 0;
|
||||
frame->pts = MP_NOPTS_VALUE;
|
||||
frame->speed = 1.0;
|
||||
}
|
||||
|
||||
// Remove all actual audio data and leave only the metadata.
|
||||
@@ -120,6 +127,11 @@ struct mp_aframe *mp_aframe_from_avframe(struct AVFrame *av_frame)
|
||||
mp_chmap_from_channels(&frame->chmap, av_frame->channels);
|
||||
#endif
|
||||
|
||||
if (av_frame->opaque_ref) {
|
||||
struct avframe_opaque *op = (void *)av_frame->opaque_ref->data;
|
||||
frame->speed = op->speed;
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
@@ -137,6 +149,16 @@ struct AVFrame *mp_aframe_to_avframe(struct mp_aframe *frame)
|
||||
if (!mp_chmap_is_lavc(&frame->chmap))
|
||||
return NULL;
|
||||
|
||||
if (!frame->av_frame->opaque_ref && frame->speed != 1.0) {
|
||||
frame->av_frame->opaque_ref =
|
||||
av_buffer_alloc(sizeof(struct avframe_opaque));
|
||||
if (!frame->av_frame->opaque_ref)
|
||||
return NULL;
|
||||
|
||||
struct avframe_opaque *op = (void *)frame->av_frame->opaque_ref->data;
|
||||
op->speed = frame->speed;
|
||||
}
|
||||
|
||||
return av_frame_clone(frame->av_frame);
|
||||
}
|
||||
|
||||
@@ -183,6 +205,7 @@ void mp_aframe_config_copy(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
void mp_aframe_copy_attributes(struct mp_aframe *dst, struct mp_aframe *src)
|
||||
{
|
||||
dst->pts = src->pts;
|
||||
dst->speed = src->speed;
|
||||
|
||||
int rate = dst->av_frame->sample_rate;
|
||||
|
||||
@@ -316,6 +339,37 @@ void mp_aframe_set_pts(struct mp_aframe *frame, double pts)
|
||||
frame->pts = pts;
|
||||
}
|
||||
|
||||
// Set a speed factor. This is multiplied with the sample rate to get the
|
||||
// "effective" samplerate (mp_aframe_get_effective_rate()), which will be used
|
||||
// to do PTS calculations. If speed!=1.0, the PTS values always refer to the
|
||||
// original PTS (before changing speed), and if you want reasonably continuous
|
||||
// PTS between frames, you need to use the effective samplerate.
|
||||
void mp_aframe_set_speed(struct mp_aframe *frame, double factor)
|
||||
{
|
||||
frame->speed = factor;
|
||||
}
|
||||
|
||||
// Adjust current speed factor.
|
||||
void mp_aframe_mul_speed(struct mp_aframe *frame, double factor)
|
||||
{
|
||||
frame->speed *= factor;
|
||||
}
|
||||
|
||||
double mp_aframe_get_speed(struct mp_aframe *frame)
|
||||
{
|
||||
return frame->speed;
|
||||
}
|
||||
|
||||
// Matters for speed changed frames (such as a frame which has been resampled
|
||||
// to play at a different speed).
|
||||
// Return the sample rate at which the frame would have to be played to result
|
||||
// in the same duration as the original frame before the speed change.
|
||||
// This is used for A/V sync.
|
||||
double mp_aframe_get_effective_rate(struct mp_aframe *frame)
|
||||
{
|
||||
return mp_aframe_get_rate(frame) / frame->speed;
|
||||
}
|
||||
|
||||
// Return number of data pointers.
|
||||
int mp_aframe_get_planes(struct mp_aframe *frame)
|
||||
{
|
||||
@@ -339,6 +393,18 @@ int mp_aframe_get_total_plane_samples(struct mp_aframe *frame)
|
||||
? 1 : mp_aframe_get_channels(frame));
|
||||
}
|
||||
|
||||
char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt)
|
||||
{
|
||||
char ch[128];
|
||||
mp_chmap_to_str_buf(ch, sizeof(ch), &fmt->chmap);
|
||||
char *hr_ch = mp_chmap_to_str_hr(&fmt->chmap);
|
||||
if (strcmp(hr_ch, ch) != 0)
|
||||
mp_snprintf_cat(ch, sizeof(ch), " (%s)", hr_ch);
|
||||
snprintf(buf, buf_size, "%dHz %s %dch %s", fmt->av_frame->sample_rate,
|
||||
ch, fmt->chmap.num, af_fmt_to_str(fmt->format));
|
||||
return buf;
|
||||
}
|
||||
|
||||
// Set data to the audio after the given number of samples (i.e. slice it).
|
||||
void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
|
||||
{
|
||||
@@ -352,35 +418,38 @@ void mp_aframe_skip_samples(struct mp_aframe *f, int samples)
|
||||
f->av_frame->nb_samples -= samples;
|
||||
|
||||
if (f->pts != MP_NOPTS_VALUE)
|
||||
f->pts += samples / (double)mp_aframe_get_rate(f);
|
||||
f->pts += samples / mp_aframe_get_effective_rate(f);
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_aframe_end_pts(struct mp_aframe *f)
|
||||
{
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (f->pts == MP_NOPTS_VALUE || rate < 1)
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
if (f->pts == MP_NOPTS_VALUE || rate <= 0)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->av_frame->nb_samples / (double)rate;
|
||||
return f->pts + f->av_frame->nb_samples / rate;
|
||||
}
|
||||
|
||||
// Return the duration in seconds of the frame (0 if invalid).
|
||||
double mp_aframe_duration(struct mp_aframe *f)
|
||||
{
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
if (rate < 1)
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
if (rate <= 0)
|
||||
return 0;
|
||||
return f->av_frame->nb_samples / (double)rate;
|
||||
return f->av_frame->nb_samples / rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
// Refuses to change spdif frames.
|
||||
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
{
|
||||
double f_end = mp_aframe_end_pts(f);
|
||||
int rate = mp_aframe_get_rate(f);
|
||||
double rate = mp_aframe_get_effective_rate(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
if (af_fmt_is_spdif(mp_aframe_get_format(f)))
|
||||
return;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
@@ -405,6 +474,52 @@ void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end)
|
||||
}
|
||||
}
|
||||
|
||||
bool mp_aframe_copy_samples(struct mp_aframe *dst, int dst_offset,
|
||||
struct mp_aframe *src, int src_offset,
|
||||
int samples)
|
||||
{
|
||||
if (!mp_aframe_config_equals(dst, src))
|
||||
return false;
|
||||
|
||||
if (mp_aframe_get_size(dst) < dst_offset + samples ||
|
||||
mp_aframe_get_size(src) < src_offset + samples)
|
||||
return false;
|
||||
|
||||
uint8_t **s = mp_aframe_get_data_ro(src);
|
||||
uint8_t **d = mp_aframe_get_data_rw(dst);
|
||||
if (!s || !d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(dst);
|
||||
size_t sstride = mp_aframe_get_sstride(dst);
|
||||
|
||||
for (int n = 0; n < planes; n++) {
|
||||
memcpy(d[n] + dst_offset * sstride, s[n] + src_offset * sstride,
|
||||
samples * sstride);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples)
|
||||
{
|
||||
if (mp_aframe_get_size(f) < offset + samples)
|
||||
return false;
|
||||
|
||||
int format = mp_aframe_get_format(f);
|
||||
uint8_t **d = mp_aframe_get_data_rw(f);
|
||||
if (!d)
|
||||
return false;
|
||||
|
||||
int planes = mp_aframe_get_planes(f);
|
||||
size_t sstride = mp_aframe_get_sstride(f);
|
||||
|
||||
for (int n = 0; n < planes; n++)
|
||||
af_fill_silence(d[n] + offset * sstride, samples * sstride, format);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct mp_aframe_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mp_aframe;
|
||||
struct AVFrame;
|
||||
@@ -34,21 +36,32 @@ int mp_aframe_get_channels(struct mp_aframe *frame);
|
||||
int mp_aframe_get_rate(struct mp_aframe *frame);
|
||||
int mp_aframe_get_size(struct mp_aframe *frame);
|
||||
double mp_aframe_get_pts(struct mp_aframe *frame);
|
||||
double mp_aframe_get_speed(struct mp_aframe *frame);
|
||||
double mp_aframe_get_effective_rate(struct mp_aframe *frame);
|
||||
|
||||
bool mp_aframe_set_format(struct mp_aframe *frame, int format);
|
||||
bool mp_aframe_set_chmap(struct mp_aframe *frame, struct mp_chmap *in);
|
||||
bool mp_aframe_set_rate(struct mp_aframe *frame, int rate);
|
||||
bool mp_aframe_set_size(struct mp_aframe *frame, int samples);
|
||||
void mp_aframe_set_pts(struct mp_aframe *frame, double pts);
|
||||
void mp_aframe_set_speed(struct mp_aframe *frame, double factor);
|
||||
void mp_aframe_mul_speed(struct mp_aframe *frame, double factor);
|
||||
|
||||
int mp_aframe_get_planes(struct mp_aframe *frame);
|
||||
int mp_aframe_get_total_plane_samples(struct mp_aframe *frame);
|
||||
size_t mp_aframe_get_sstride(struct mp_aframe *frame);
|
||||
|
||||
char *mp_aframe_format_str_buf(char *buf, size_t buf_size, struct mp_aframe *fmt);
|
||||
#define mp_aframe_format_str(fmt) mp_aframe_format_str_buf((char[32]){0}, 32, (fmt))
|
||||
|
||||
void mp_aframe_skip_samples(struct mp_aframe *f, int samples);
|
||||
double mp_aframe_end_pts(struct mp_aframe *f);
|
||||
double mp_aframe_duration(struct mp_aframe *f);
|
||||
void mp_aframe_clip_timestamps(struct mp_aframe *f, double start, double end);
|
||||
bool mp_aframe_copy_samples(struct mp_aframe *dst, int dst_offset,
|
||||
struct mp_aframe *src, int src_offset,
|
||||
int samples);
|
||||
bool mp_aframe_set_silence(struct mp_aframe *f, int offset, int samples);
|
||||
|
||||
struct mp_aframe_pool;
|
||||
struct mp_aframe_pool *mp_aframe_pool_create(void *ta_parent);
|
||||
|
||||
-625
@@ -1,625 +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 <stdint.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/buffer.h>
|
||||
#include <libavutil/frame.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/version.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/common.h"
|
||||
#include "fmt-conversion.h"
|
||||
#include "audio.h"
|
||||
#include "aframe.h"
|
||||
|
||||
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_fmt_to_bytes(mpa->format);
|
||||
if (af_fmt_is_planar(mpa->format)) {
|
||||
mpa->spf = 1;
|
||||
mpa->num_planes = mpa->nch;
|
||||
mpa->sstride = mpa->bps;
|
||||
} else {
|
||||
mpa->spf = mpa->nch;
|
||||
mpa->num_planes = 1;
|
||||
mpa->sstride = mpa->bps * mpa->nch;
|
||||
}
|
||||
}
|
||||
|
||||
void mp_audio_set_format(struct mp_audio *mpa, int format)
|
||||
{
|
||||
mpa->format = format;
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels)
|
||||
{
|
||||
mp_chmap_from_channels(&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;
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src)
|
||||
{
|
||||
dst->format = src->format;
|
||||
dst->channels = src->channels;
|
||||
dst->rate = src->rate;
|
||||
update_redundant_info(dst);
|
||||
}
|
||||
|
||||
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b)
|
||||
{
|
||||
return a->format == b->format && a->rate == b->rate &&
|
||||
mp_chmap_equals(&a->channels, &b->channels);
|
||||
}
|
||||
|
||||
bool mp_audio_config_valid(const struct mp_audio *mpa)
|
||||
{
|
||||
return mp_chmap_is_valid(&mpa->channels) && af_fmt_is_valid(mpa->format)
|
||||
&& mpa->rate >= 1 && mpa->rate < 10000000;
|
||||
}
|
||||
|
||||
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,
|
||||
ch, mpa->channels.num, af_fmt_to_str(mpa->format));
|
||||
return buf;
|
||||
}
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
|
||||
{
|
||||
if (af_fmt_is_planar(mpa->format))
|
||||
mp_audio_set_format(mpa, af_fmt_from_planar(mpa->format));
|
||||
}
|
||||
|
||||
// Return used size of a plane. (The size is the same for all planes.)
|
||||
int mp_audio_psize(struct mp_audio *mpa)
|
||||
{
|
||||
return mpa->samples * mpa->sstride;
|
||||
}
|
||||
|
||||
void mp_audio_set_null_data(struct mp_audio *mpa)
|
||||
{
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
mpa->planes[n] = NULL;
|
||||
mpa->allocated[n] = NULL;
|
||||
}
|
||||
mpa->samples = 0;
|
||||
}
|
||||
|
||||
static int get_plane_size(const struct mp_audio *mpa, int samples)
|
||||
{
|
||||
if (samples < 0 || !mp_audio_config_valid(mpa))
|
||||
return -1;
|
||||
if (samples >= INT_MAX / mpa->sstride)
|
||||
return -1;
|
||||
return MPMAX(samples * mpa->sstride, 1);
|
||||
}
|
||||
|
||||
static void mp_audio_destructor(void *ptr)
|
||||
{
|
||||
struct mp_audio *mpa = ptr;
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
}
|
||||
|
||||
/* Reallocate the data stored in mpa->planes[n] so that enough samples are
|
||||
* available on every plane. The previous data is kept (for the smallest
|
||||
* common number of samples before/after resize).
|
||||
*
|
||||
* This also makes sure the resulting buffer is writable (even in the case
|
||||
* the buffer has the correct size).
|
||||
*
|
||||
* mpa->samples is not set or used.
|
||||
*
|
||||
* This function is flexible enough to handle format and channel layout
|
||||
* changes. In these cases, all planes are reallocated as needed. Unused
|
||||
* planes are freed.
|
||||
*
|
||||
* mp_audio_realloc(mpa, 0) will still yield non-NULL for mpa->data[n].
|
||||
*
|
||||
* Allocated data is implicitly freed on talloc_free(mpa).
|
||||
*/
|
||||
void mp_audio_realloc(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
int size = get_plane_size(mpa, samples);
|
||||
if (size < 0)
|
||||
abort(); // oom or invalid parameters
|
||||
if (!mp_audio_is_writeable(mpa)) {
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
mpa->planes[n] = NULL;
|
||||
}
|
||||
}
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
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;
|
||||
}
|
||||
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
|
||||
av_buffer_unref(&mpa->allocated[n]);
|
||||
mpa->planes[n] = NULL;
|
||||
}
|
||||
talloc_set_destructor(mpa, mp_audio_destructor);
|
||||
}
|
||||
|
||||
// Like mp_audio_realloc(), but only reallocate if the audio grows in size.
|
||||
// If the buffer is reallocated, also preallocate.
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
if (samples > mp_audio_get_allocated_size(mpa) || !mp_audio_is_writeable(mpa)) {
|
||||
size_t alloc = ta_calc_prealloc_elems(samples);
|
||||
if (alloc > INT_MAX)
|
||||
abort(); // oom
|
||||
mp_audio_realloc(mpa, alloc);
|
||||
}
|
||||
}
|
||||
|
||||
/* Get the size allocated for the data, in number of samples. If the allocated
|
||||
* size isn't on sample boundaries (e.g. after format changes), the returned
|
||||
* sample number is a rounded down value.
|
||||
*
|
||||
* Note that this only works in situations where mp_audio_realloc() also works!
|
||||
*/
|
||||
int mp_audio_get_allocated_size(struct mp_audio *mpa)
|
||||
{
|
||||
int size = 0;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Clear the samples [start, start + length) with silence.
|
||||
void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length)
|
||||
{
|
||||
assert(start >= 0 && length >= 0 && start + length <= mpa->samples);
|
||||
int offset = start * mpa->sstride;
|
||||
int size = length * mpa->sstride;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
if (n > 0 && mpa->planes[n] == mpa->planes[0])
|
||||
continue; // silly optimization for special cases
|
||||
af_fill_silence((char *)mpa->planes[n] + offset, size, mpa->format);
|
||||
}
|
||||
}
|
||||
|
||||
// All integer parameters are in samples.
|
||||
// dst and src can overlap.
|
||||
void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length)
|
||||
{
|
||||
assert(mp_audio_config_equals(dst, src));
|
||||
assert(length >= 0);
|
||||
assert(dst_offset >= 0 && dst_offset + length <= dst->samples);
|
||||
assert(src_offset >= 0 && src_offset + length <= src->samples);
|
||||
|
||||
for (int n = 0; n < dst->num_planes; n++) {
|
||||
memmove((char *)dst->planes[n] + dst_offset * dst->sstride,
|
||||
(char *)src->planes[n] + src_offset * src->sstride,
|
||||
length * dst->sstride);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy fields that describe characteristics of the audio frame, but which are
|
||||
// not part of the core format (format/channels/rate), and not part of the
|
||||
// data (samples).
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
// nothing yet
|
||||
}
|
||||
|
||||
// Set data to the audio after the given number of samples (i.e. slice it).
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= data->samples);
|
||||
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
data->planes[n] = (uint8_t *)data->planes[n] + samples * data->sstride;
|
||||
|
||||
data->samples -= samples;
|
||||
|
||||
if (data->pts != MP_NOPTS_VALUE)
|
||||
data->pts += samples / (double)data->rate;
|
||||
}
|
||||
|
||||
// Return the timestamp of the sample just after the end of this frame.
|
||||
double mp_audio_end_pts(struct mp_audio *f)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
return MP_NOPTS_VALUE;
|
||||
return f->pts + f->samples / (double)f->rate;
|
||||
}
|
||||
|
||||
// Clip the given frame to the given timestamp range. Adjusts the frame size
|
||||
// and timestamp.
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end)
|
||||
{
|
||||
double f_end = mp_audio_end_pts(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
f->samples = 0;
|
||||
} else {
|
||||
int new = (end - f->pts) * f->rate;
|
||||
f->samples = MPCLAMP(new, 0, f->samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (start != MP_NOPTS_VALUE) {
|
||||
if (f->pts < start) {
|
||||
if (f_end <= start) {
|
||||
f->samples = 0;
|
||||
f->pts = f_end;
|
||||
} else {
|
||||
int skip = (start - f->pts) * f->rate;
|
||||
skip = MPCLAMP(skip, 0, f->samples);
|
||||
mp_audio_skip_samples(f, skip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Return false if the frame data is shared, true otherwise.
|
||||
// Will return true for non-refcounted frames.
|
||||
bool mp_audio_is_writeable(struct mp_audio *data)
|
||||
{
|
||||
bool ok = true;
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
if (data->allocated[n])
|
||||
ok &= av_buffer_is_writable(data->allocated[n]);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void mp_audio_steal_data(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
talloc_set_destructor(dst, mp_audio_destructor);
|
||||
mp_audio_destructor(dst);
|
||||
*dst = *src;
|
||||
talloc_set_destructor(src, NULL);
|
||||
talloc_free(src);
|
||||
}
|
||||
|
||||
// Make sure the frame owns the audio data, and if not, copy the data.
|
||||
// Return negative value on failure (which means it can't be made writeable).
|
||||
// Non-refcounted frames are always considered writeable.
|
||||
int mp_audio_make_writeable(struct mp_audio *data)
|
||||
{
|
||||
if (!mp_audio_is_writeable(data)) {
|
||||
struct mp_audio *new = talloc(NULL, struct mp_audio);
|
||||
*new = *data;
|
||||
mp_audio_set_null_data(new); // use format only
|
||||
mp_audio_realloc(new, data->samples);
|
||||
new->samples = data->samples;
|
||||
mp_audio_copy(new, 0, data, 0, data->samples);
|
||||
mp_audio_steal_data(data, new);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
|
||||
{
|
||||
AVFrame *tmp = NULL;
|
||||
struct mp_audio *new = talloc_zero(NULL, struct mp_audio);
|
||||
talloc_set_destructor(new, mp_audio_destructor);
|
||||
|
||||
mp_audio_set_format(new, af_from_avformat(avframe->format));
|
||||
|
||||
struct mp_chmap lavc_chmap;
|
||||
mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being stupid POS again
|
||||
if (lavc_chmap.num != avframe->channels)
|
||||
mp_chmap_from_channels(&lavc_chmap, avframe->channels);
|
||||
#endif
|
||||
|
||||
new->rate = avframe->sample_rate;
|
||||
|
||||
mp_audio_set_channels(new, &lavc_chmap);
|
||||
|
||||
// Force refcounted frame.
|
||||
if (!avframe->buf[0]) {
|
||||
tmp = av_frame_alloc();
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
if (av_frame_ref(tmp, avframe) < 0)
|
||||
goto fail;
|
||||
avframe = tmp;
|
||||
}
|
||||
|
||||
// If we can't handle the format (e.g. too many channels), bail out.
|
||||
if (!mp_audio_config_valid(new))
|
||||
goto fail;
|
||||
|
||||
for (int n = 0; n < AV_NUM_DATA_POINTERS + avframe->nb_extended_buf; n++) {
|
||||
AVBufferRef *buf = n < AV_NUM_DATA_POINTERS ? avframe->buf[n]
|
||||
: avframe->extended_buf[n - AV_NUM_DATA_POINTERS];
|
||||
if (!buf)
|
||||
break;
|
||||
if (n >= MP_NUM_CHANNELS)
|
||||
goto fail;
|
||||
new->allocated[n] = av_buffer_ref(buf);
|
||||
if (!new->allocated[n])
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (int n = 0; n < new->num_planes; n++)
|
||||
new->planes[n] = avframe->extended_data[n];
|
||||
new->samples = avframe->nb_samples;
|
||||
|
||||
return new;
|
||||
|
||||
fail:
|
||||
talloc_free(new);
|
||||
av_frame_free(&tmp);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe)
|
||||
{
|
||||
if (!aframe)
|
||||
return NULL;
|
||||
|
||||
struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
|
||||
struct mp_audio *res = mp_audio_from_avframe(av);
|
||||
if (!res)
|
||||
return NULL;
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(aframe, &chmap);
|
||||
mp_audio_set_channels(res, &chmap);
|
||||
mp_audio_set_format(res, mp_aframe_get_format(aframe));
|
||||
res->pts = mp_aframe_get_pts(aframe);
|
||||
return res;
|
||||
}
|
||||
|
||||
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src)
|
||||
{
|
||||
*dst = (struct mp_audio){0};
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(src, &chmap);
|
||||
mp_audio_set_channels(dst, &chmap);
|
||||
mp_audio_set_format(dst, mp_aframe_get_format(src));
|
||||
dst->rate = mp_aframe_get_rate(src);
|
||||
}
|
||||
|
||||
struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa)
|
||||
{
|
||||
if (!mpa)
|
||||
return NULL;
|
||||
|
||||
struct mp_aframe *aframe = mp_aframe_create();
|
||||
struct AVFrame *av = mp_aframe_get_raw_avframe(aframe);
|
||||
mp_aframe_set_format(aframe, mpa->format);
|
||||
mp_aframe_set_chmap(aframe, &mpa->channels);
|
||||
mp_aframe_set_rate(aframe, mpa->rate);
|
||||
|
||||
// bullshit it into ffmpeg-compatible parameters
|
||||
struct mp_audio mpb = *mpa;
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_set_unknown(&chmap, mpb.channels.num);
|
||||
mp_audio_set_channels(&mpb, &chmap);
|
||||
if (af_fmt_is_spdif(mpb.format))
|
||||
mp_audio_set_format(&mpb, AF_FORMAT_S16);
|
||||
|
||||
// put the reference into av, which magically puts it into aframe
|
||||
// aframe keeps its parameters, so the bullshit doesn't matter
|
||||
if (mp_audio_to_avframe(&mpb, av) < 0) {
|
||||
talloc_free(aframe);
|
||||
return NULL;
|
||||
}
|
||||
return aframe;
|
||||
}
|
||||
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe)
|
||||
{
|
||||
av_frame_unref(avframe);
|
||||
|
||||
avframe->nb_samples = frame->samples;
|
||||
avframe->format = af_to_avformat(frame->format);
|
||||
if (avframe->format == AV_SAMPLE_FMT_NONE)
|
||||
goto fail;
|
||||
|
||||
avframe->channel_layout = mp_chmap_to_lavc(&frame->channels);
|
||||
if (!avframe->channel_layout)
|
||||
goto fail;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS again
|
||||
avframe->channels = frame->channels.num;
|
||||
#endif
|
||||
avframe->sample_rate = frame->rate;
|
||||
|
||||
if (frame->num_planes > AV_NUM_DATA_POINTERS) {
|
||||
avframe->extended_data =
|
||||
av_mallocz_array(frame->num_planes, sizeof(avframe->extended_data[0]));
|
||||
int extbufs = frame->num_planes - AV_NUM_DATA_POINTERS;
|
||||
avframe->extended_buf =
|
||||
av_mallocz_array(extbufs, sizeof(avframe->extended_buf[0]));
|
||||
if (!avframe->extended_data || !avframe->extended_buf)
|
||||
goto fail;
|
||||
avframe->nb_extended_buf = extbufs;
|
||||
}
|
||||
|
||||
for (int p = 0; p < frame->num_planes; p++)
|
||||
avframe->extended_data[p] = frame->planes[p];
|
||||
avframe->linesize[0] = frame->samples * frame->sstride;
|
||||
|
||||
for (int p = 0; p < AV_NUM_DATA_POINTERS; p++)
|
||||
avframe->data[p] = avframe->extended_data[p];
|
||||
|
||||
for (int p = 0; p < frame->num_planes; p++) {
|
||||
if (!frame->allocated[p])
|
||||
break;
|
||||
AVBufferRef *nref = av_buffer_ref(frame->allocated[p]);
|
||||
if (!nref)
|
||||
goto fail;
|
||||
if (p < AV_NUM_DATA_POINTERS) {
|
||||
avframe->buf[p] = nref;
|
||||
} else {
|
||||
avframe->extended_buf[p - AV_NUM_DATA_POINTERS] = nref;
|
||||
}
|
||||
}
|
||||
|
||||
// Force refcounted frame.
|
||||
if (!avframe->buf[0]) {
|
||||
AVFrame *tmp = av_frame_alloc();
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
if (av_frame_ref(tmp, avframe) < 0)
|
||||
goto fail;
|
||||
av_frame_free(&avframe);
|
||||
avframe = tmp;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
av_frame_unref(avframe);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Returns NULL on failure. The input is always unreffed.
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
{
|
||||
struct AVFrame *avframe = av_frame_alloc();
|
||||
if (!avframe)
|
||||
goto fail;
|
||||
|
||||
if (mp_audio_to_avframe(frame, avframe) < 0)
|
||||
goto fail;
|
||||
|
||||
talloc_free(frame);
|
||||
return avframe;
|
||||
|
||||
fail:
|
||||
av_frame_free(&avframe);
|
||||
talloc_free(frame);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mp_audio_pool {
|
||||
AVBufferPool *avpool;
|
||||
int element_size;
|
||||
};
|
||||
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent)
|
||||
{
|
||||
return talloc_zero(ta_parent, struct mp_audio_pool);
|
||||
}
|
||||
|
||||
static void mp_audio_pool_destructor(void *p)
|
||||
{
|
||||
struct mp_audio_pool *pool = p;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
}
|
||||
|
||||
// Allocate data using the given format and number of samples.
|
||||
// Returns NULL on error.
|
||||
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
|
||||
const struct mp_audio *fmt, int samples)
|
||||
{
|
||||
int size = get_plane_size(fmt, samples);
|
||||
if (size < 0)
|
||||
return NULL;
|
||||
if (!pool->avpool || size > pool->element_size) {
|
||||
size_t alloc = ta_calc_prealloc_elems(size);
|
||||
if (alloc >= INT_MAX)
|
||||
return NULL;
|
||||
av_buffer_pool_uninit(&pool->avpool);
|
||||
pool->element_size = alloc;
|
||||
pool->avpool = av_buffer_pool_init(pool->element_size, NULL);
|
||||
if (!pool->avpool)
|
||||
return NULL;
|
||||
talloc_set_destructor(pool, mp_audio_pool_destructor);
|
||||
}
|
||||
struct mp_audio *new = talloc_ptrtype(NULL, new);
|
||||
talloc_set_destructor(new, mp_audio_destructor);
|
||||
*new = *fmt;
|
||||
mp_audio_set_null_data(new);
|
||||
new->samples = samples;
|
||||
for (int n = 0; n < new->num_planes; n++) {
|
||||
new->allocated[n] = av_buffer_pool_get(pool->avpool);
|
||||
if (!new->allocated[n]) {
|
||||
talloc_free(new);
|
||||
return NULL;
|
||||
}
|
||||
new->planes[n] = new->allocated[n]->data;
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
// Return a copy of the given frame.
|
||||
// Returns NULL on error.
|
||||
struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame)
|
||||
{
|
||||
struct mp_audio *new = mp_audio_pool_get(pool, frame, frame->samples);
|
||||
if (new) {
|
||||
mp_audio_copy(new, 0, frame, 0, new->samples);
|
||||
mp_audio_copy_attributes(new, frame);
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
// Exactly like mp_audio_make_writeable(), but get the data from the pool.
|
||||
int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
|
||||
struct mp_audio *data)
|
||||
{
|
||||
if (mp_audio_is_writeable(data))
|
||||
return 0;
|
||||
struct mp_audio *new = mp_audio_pool_get(pool, data, data->samples);
|
||||
if (!new)
|
||||
return -1;
|
||||
mp_audio_copy(new, 0, data, 0, data->samples);
|
||||
mp_audio_copy_attributes(new, data);
|
||||
mp_audio_steal_data(data, new);
|
||||
return 0;
|
||||
}
|
||||
-102
@@ -1,102 +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 MP_AUDIO_H
|
||||
#define MP_AUDIO_H
|
||||
|
||||
#include "format.h"
|
||||
#include "chmap.h"
|
||||
|
||||
// Audio data chunk
|
||||
struct mp_audio {
|
||||
int samples; // number of samples in data (per channel)
|
||||
void *planes[MP_NUM_CHANNELS]; // data buffer (one per plane)
|
||||
int rate; // sample rate
|
||||
struct mp_chmap channels; // channel layout, use mp_audio_set_*() to set
|
||||
int format; // format (AF_FORMAT_...), use mp_audio_set_format() to set
|
||||
// Redundant fields, for convenience
|
||||
int sstride; // distance between 2 samples in bytes on a plane
|
||||
// interleaved: bps * nch
|
||||
// planar: bps
|
||||
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_fmt_to_bytes(format))
|
||||
|
||||
double pts; // currently invalid within the filter chain
|
||||
|
||||
// --- 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(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);
|
||||
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);
|
||||
#define mp_audio_config_to_str(m) mp_audio_config_to_str_buf((char[64]){0}, 64, (m))
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa);
|
||||
|
||||
int mp_audio_psize(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_set_null_data(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_realloc(struct mp_audio *mpa, int samples);
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples);
|
||||
int mp_audio_get_allocated_size(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length);
|
||||
|
||||
void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length);
|
||||
void mp_audio_copy_attributes(struct mp_audio *dst, struct mp_audio *src);
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples);
|
||||
void mp_audio_clip_timestamps(struct mp_audio *f, double start, double end);
|
||||
double mp_audio_end_pts(struct mp_audio *data);
|
||||
|
||||
bool mp_audio_is_writeable(struct mp_audio *data);
|
||||
int mp_audio_make_writeable(struct mp_audio *data);
|
||||
|
||||
struct AVFrame;
|
||||
struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe);
|
||||
struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame);
|
||||
int mp_audio_to_avframe(struct mp_audio *frame, struct AVFrame *avframe);
|
||||
|
||||
struct mp_aframe;
|
||||
struct mp_audio *mp_audio_from_aframe(struct mp_aframe *aframe);
|
||||
void mp_audio_config_from_aframe(struct mp_audio *dst, struct mp_aframe *src);
|
||||
struct mp_aframe *mp_audio_to_aframe(struct mp_audio *mpa);
|
||||
|
||||
struct mp_audio_pool;
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
|
||||
struct mp_audio *mp_audio_pool_get(struct mp_audio_pool *pool,
|
||||
const struct mp_audio *fmt, int samples);
|
||||
struct mp_audio *mp_audio_pool_new_copy(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame);
|
||||
int mp_audio_pool_make_writeable(struct mp_audio_pool *pool,
|
||||
struct mp_audio *frame);
|
||||
|
||||
#include "filter/af.h"
|
||||
|
||||
#endif
|
||||
@@ -1,49 +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 Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_AD_H
|
||||
#define MPLAYER_AD_H
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "dec_audio.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
/* interface of audio decoder drivers */
|
||||
struct ad_functions {
|
||||
const char *name;
|
||||
void (*add_decoders)(struct mp_decoder_list *list);
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
// Return whether or not the packet has been consumed.
|
||||
bool (*send_packet)(struct dec_audio *da, struct demux_packet *pkt);
|
||||
// Return whether decoding is still going on (false if EOF was reached).
|
||||
// Never returns false & *out set, but can return true with !*out.
|
||||
bool (*receive_frame)(struct dec_audio *da, struct mp_aframe **out);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
ADCTRL_RESET = 1, // flush and reset state, e.g. after seeking
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_AD_H */
|
||||
+79
-51
@@ -27,27 +27,30 @@
|
||||
#include <libavutil/intreadwrite.h>
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "options/options.h"
|
||||
|
||||
#include "ad.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
struct priv {
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *avframe;
|
||||
bool force_channel_map;
|
||||
struct mp_chmap force_channel_map;
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
AVRational codec_timebase;
|
||||
};
|
||||
bool eof_returned;
|
||||
|
||||
static void uninit(struct dec_audio *da);
|
||||
struct mp_decoder public;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct ad_lavc_params
|
||||
struct ad_lavc_params {
|
||||
@@ -73,26 +76,24 @@ const struct m_sub_options ad_lavc_conf = {
|
||||
},
|
||||
};
|
||||
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
static bool init(struct mp_filter *da, struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct MPOpts *mpopts = da->opts;
|
||||
struct priv *ctx = da->priv;
|
||||
struct MPOpts *mpopts = da->global->opts;
|
||||
struct ad_lavc_params *opts = mpopts->ad_lavc_params;
|
||||
AVCodecContext *lavc_context;
|
||||
AVCodec *lavc_codec;
|
||||
struct mp_codec_params *c = da->codec;
|
||||
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
ctx->codec_timebase = mp_get_codec_timebase(codec);
|
||||
|
||||
ctx->codec_timebase = mp_get_codec_timebase(da->codec);
|
||||
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
if (codec->force_channels)
|
||||
ctx->force_channel_map = codec->channels;
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
if (!lavc_codec) {
|
||||
MP_ERR(da, "Cannot find codec '%s' in libavcodec...\n", decoder);
|
||||
uninit(da);
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
lavc_context = avcodec_alloc_context3(lavc_codec);
|
||||
@@ -121,10 +122,9 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
|
||||
mp_set_avopts(da->log, lavc_context, opts->avopts);
|
||||
|
||||
if (mp_set_avctx_codec_headers(lavc_context, c) < 0) {
|
||||
if (mp_set_avctx_codec_headers(lavc_context, codec) < 0) {
|
||||
MP_ERR(da, "Could not set decoder parameters.\n");
|
||||
uninit(da);
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
mp_set_avcodec_threads(da->log, lavc_context, opts->threads);
|
||||
@@ -132,41 +132,35 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
/* open it */
|
||||
if (avcodec_open2(lavc_context, lavc_codec, NULL) < 0) {
|
||||
MP_ERR(da, "Could not open codec.\n");
|
||||
uninit(da);
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void uninit(struct dec_audio *da)
|
||||
static void destroy(struct mp_filter *da)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
if (!ctx)
|
||||
return;
|
||||
|
||||
avcodec_free_context(&ctx->avctx);
|
||||
av_frame_free(&ctx->avframe);
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
static void reset(struct mp_filter *da)
|
||||
{
|
||||
struct priv *ctx = da->priv;
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->skip_samples = 0;
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
ctx->eof_returned = false;
|
||||
}
|
||||
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
static bool send_packet(struct mp_filter *da, struct demux_packet *mpkt)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -190,7 +184,7 @@ static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
static bool receive_frame(struct mp_filter *da, struct mp_frame *out)
|
||||
{
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
@@ -200,7 +194,8 @@ static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
if (ret == AVERROR_EOF) {
|
||||
// If flushing was initialized earlier and has ended now, make it start
|
||||
// over in case we get new packets at some point in the future.
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
// (Dont' reset the filter itself, we want to keep other state.)
|
||||
avcodec_flush_buffers(priv->avctx);
|
||||
return false;
|
||||
} else if (ret < 0 && ret != AVERROR(EAGAIN)) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
@@ -220,8 +215,8 @@ static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
if (!mpframe)
|
||||
return true;
|
||||
|
||||
if (priv->force_channel_map)
|
||||
mp_aframe_set_chmap(mpframe, &da->codec->channels);
|
||||
if (priv->force_channel_map.num)
|
||||
mp_aframe_set_chmap(mpframe, &priv->force_channel_map);
|
||||
|
||||
if (out_pts == MP_NOPTS_VALUE)
|
||||
out_pts = priv->next_pts;
|
||||
@@ -257,24 +252,57 @@ static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
priv->trim_samples -= trim;
|
||||
}
|
||||
|
||||
*out = mpframe;
|
||||
*out = MAKE_FRAME(MP_FRAME_AUDIO, mpframe);
|
||||
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *ad)
|
||||
{
|
||||
struct priv *priv = ad->priv;
|
||||
|
||||
lavc_process(ad, &priv->eof_returned, send_packet, receive_frame);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info ad_lavc_filter = {
|
||||
.name = "ad_lavc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_lavc_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct priv *priv = da->priv;
|
||||
priv->public.f = da;
|
||||
|
||||
if (!init(da, codec, decoder)) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &priv->public;
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
{
|
||||
mp_add_lavc_decoders(list, AVMEDIA_TYPE_AUDIO);
|
||||
}
|
||||
|
||||
const struct ad_functions ad_lavc = {
|
||||
.name = "lavc",
|
||||
const struct mp_decoder_fns ad_lavc = {
|
||||
.create = create,
|
||||
.add_decoders = add_decoders,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
};
|
||||
+85
-76
@@ -24,11 +24,16 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "filters/f_decoder_wrapper.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "options/options.h"
|
||||
#include "ad.h"
|
||||
|
||||
#define OUTBUF_SIZE 65536
|
||||
|
||||
@@ -43,8 +48,8 @@ struct spdifContext {
|
||||
struct mp_aframe *fmt;
|
||||
int sstride;
|
||||
struct mp_aframe_pool *pool;
|
||||
bool got_eof;
|
||||
struct demux_packet *queued_packet;
|
||||
|
||||
struct mp_decoder public;
|
||||
};
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
@@ -62,7 +67,8 @@ static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
static void uninit(struct dec_audio *da)
|
||||
// (called on both filter destruction _and_ if lavf fails to init)
|
||||
static void destroy(struct mp_filter *da)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
@@ -74,27 +80,11 @@ static void uninit(struct dec_audio *da)
|
||||
av_freep(&lavf_ctx->pb->buffer);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
avformat_free_context(lavf_ctx);
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->lavf_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(da->codec->codec);
|
||||
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
static void determine_codec_params(struct dec_audio *da, AVPacket *pkt,
|
||||
static void determine_codec_params(struct mp_filter *da, AVPacket *pkt,
|
||||
int *out_profile, int *out_rate)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
@@ -157,7 +147,7 @@ done:
|
||||
MP_WARN(da, "Failed to parse codec profile.\n");
|
||||
}
|
||||
|
||||
static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
static int init_filter(struct mp_filter *da, AVPacket *pkt)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
@@ -271,39 +261,36 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
uninit(da);
|
||||
destroy(da);
|
||||
mp_filter_internal_mark_failed(da);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static bool send_packet(struct dec_audio *da, struct demux_packet *mpkt)
|
||||
static void process(struct mp_filter *da)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (spdif_ctx->queued_packet || spdif_ctx->got_eof)
|
||||
return false;
|
||||
if (!mp_pin_can_transfer_data(da->ppins[1], da->ppins[0]))
|
||||
return;
|
||||
|
||||
spdif_ctx->queued_packet = mpkt ? demux_copy_packet(mpkt) : NULL;
|
||||
spdif_ctx->got_eof = !mpkt;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
|
||||
if (spdif_ctx->got_eof) {
|
||||
spdif_ctx->got_eof = false;
|
||||
return false;
|
||||
struct mp_frame inframe = mp_pin_out_read(da->ppins[0]);
|
||||
if (inframe.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(da->ppins[1], inframe);
|
||||
return;
|
||||
} else if (inframe.type != MP_FRAME_PACKET) {
|
||||
if (inframe.type) {
|
||||
MP_ERR(da, "unknown frame type\n");
|
||||
mp_filter_internal_mark_failed(da);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!spdif_ctx->queued_packet)
|
||||
return true;
|
||||
|
||||
double pts = spdif_ctx->queued_packet->pts;
|
||||
struct demux_packet *mpkt = inframe.data;
|
||||
struct mp_aframe *out = NULL;
|
||||
double pts = mpkt->pts;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, spdif_ctx->queued_packet, NULL);
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
if (!spdif_ctx->lavf_ctx) {
|
||||
if (init_filter(da, &pkt) < 0)
|
||||
@@ -317,39 +304,29 @@ static bool receive_frame(struct dec_audio *da, struct mp_aframe **out)
|
||||
goto done;
|
||||
}
|
||||
|
||||
*out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
out = mp_aframe_new_ref(spdif_ctx->fmt);
|
||||
int samples = spdif_ctx->out_buffer_len / spdif_ctx->sstride;
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, *out, samples) < 0) {
|
||||
TA_FREEP(out);
|
||||
if (mp_aframe_pool_allocate(spdif_ctx->pool, out, samples) < 0) {
|
||||
TA_FREEP(&out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
uint8_t **data = mp_aframe_get_data_rw(*out);
|
||||
uint8_t **data = mp_aframe_get_data_rw(out);
|
||||
if (!data) {
|
||||
TA_FREEP(out);
|
||||
TA_FREEP(&out);
|
||||
goto done;
|
||||
}
|
||||
|
||||
memcpy(data[0], spdif_ctx->out_buffer, spdif_ctx->out_buffer_len);
|
||||
mp_aframe_set_pts(*out, pts);
|
||||
mp_aframe_set_pts(out, pts);
|
||||
|
||||
done:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESET:
|
||||
talloc_free(spdif_ctx->queued_packet);
|
||||
spdif_ctx->queued_packet = NULL;
|
||||
spdif_ctx->got_eof = false;
|
||||
return CONTROL_TRUE;
|
||||
talloc_free(mpkt);
|
||||
if (out) {
|
||||
mp_pin_in_write(da->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
} else {
|
||||
mp_filter_internal_mark_failed(da);
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static const int codecs[] = {
|
||||
@@ -401,17 +378,49 @@ struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref)
|
||||
const char *suffix_name = dts_hd_allowed ? "dts_hd" : codec;
|
||||
char name[80];
|
||||
snprintf(name, sizeof(name), "spdif_%s", suffix_name);
|
||||
mp_add_decoder(list, "spdif", codec, name,
|
||||
mp_add_decoder(list, codec, name,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
return list;
|
||||
}
|
||||
|
||||
const struct ad_functions ad_spdif = {
|
||||
.name = "spdif",
|
||||
.add_decoders = NULL,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
.send_packet = send_packet,
|
||||
.receive_frame = receive_frame,
|
||||
static const struct mp_filter_info ad_spdif_filter = {
|
||||
.name = "ad_spdif",
|
||||
.priv_size = sizeof(struct spdifContext),
|
||||
.process = process,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_decoder *create(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder)
|
||||
{
|
||||
struct mp_filter *da = mp_filter_create(parent, &ad_spdif_filter);
|
||||
if (!da)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(da, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(da, MP_PIN_OUT, "out");
|
||||
|
||||
da->log = mp_log_new(da, parent->log, NULL);
|
||||
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
spdif_ctx->log = da->log;
|
||||
spdif_ctx->pool = mp_aframe_pool_create(spdif_ctx);
|
||||
spdif_ctx->public.f = da;
|
||||
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(codec->codec);
|
||||
|
||||
|
||||
if (spdif_ctx->codec_id == AV_CODEC_ID_NONE) {
|
||||
talloc_free(da);
|
||||
return NULL;
|
||||
}
|
||||
return &spdif_ctx->public;
|
||||
}
|
||||
|
||||
const struct mp_decoder_fns ad_spdif = {
|
||||
.create = create,
|
||||
};
|
||||
@@ -1,290 +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 Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "demux/codec_tags.h"
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/recorder.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "options/options.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "demux/stheader.h"
|
||||
|
||||
#include "dec_audio.h"
|
||||
#include "ad.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
extern const struct ad_functions ad_lavc;
|
||||
|
||||
// Not a real codec - specially treated.
|
||||
extern const struct ad_functions ad_spdif;
|
||||
|
||||
static const struct ad_functions * const ad_drivers[] = {
|
||||
&ad_lavc,
|
||||
NULL
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
d_audio->ad_driver = NULL;
|
||||
talloc_free(d_audio->priv);
|
||||
d_audio->priv = NULL;
|
||||
}
|
||||
|
||||
static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
|
||||
{
|
||||
if (!d_audio->ad_driver->init(d_audio, decoder)) {
|
||||
MP_VERBOSE(d_audio, "Audio decoder init failed.\n");
|
||||
d_audio->ad_driver = NULL;
|
||||
uninit_decoder(d_audio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
for (int i = 0; ad_drivers[i] != NULL; i++)
|
||||
ad_drivers[i]->add_decoders(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
static struct mp_decoder_list *audio_select_decoders(struct dec_audio *d_audio)
|
||||
{
|
||||
struct MPOpts *opts = d_audio->opts;
|
||||
const char *codec = d_audio->codec->codec;
|
||||
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new =
|
||||
mp_select_decoders(d_audio->log, list, codec, opts->audio_decoders);
|
||||
if (d_audio->try_spdif && codec) {
|
||||
struct mp_decoder_list *spdif =
|
||||
select_spdif_codec(codec, opts->audio_spdif);
|
||||
mp_append_decoders(spdif, new);
|
||||
talloc_free(new);
|
||||
new = spdif;
|
||||
}
|
||||
talloc_free(list);
|
||||
return new;
|
||||
}
|
||||
|
||||
static const struct ad_functions *find_driver(const char *name)
|
||||
{
|
||||
for (int i = 0; ad_drivers[i] != NULL; i++) {
|
||||
if (strcmp(ad_drivers[i]->name, name) == 0)
|
||||
return ad_drivers[i];
|
||||
}
|
||||
if (strcmp(name, "spdif") == 0)
|
||||
return &ad_spdif;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int audio_init_best_codec(struct dec_audio *d_audio)
|
||||
{
|
||||
uninit_decoder(d_audio);
|
||||
assert(!d_audio->ad_driver);
|
||||
|
||||
struct mp_decoder_entry *decoder = NULL;
|
||||
struct mp_decoder_list *list = audio_select_decoders(d_audio);
|
||||
|
||||
mp_print_decoders(d_audio->log, MSGL_V, "Codec list:", list);
|
||||
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *sel = &list->entries[n];
|
||||
const struct ad_functions *driver = find_driver(sel->family);
|
||||
if (!driver)
|
||||
continue;
|
||||
MP_VERBOSE(d_audio, "Opening audio decoder %s\n", sel->decoder);
|
||||
d_audio->ad_driver = driver;
|
||||
if (init_audio_codec(d_audio, sel->decoder)) {
|
||||
decoder = sel;
|
||||
break;
|
||||
}
|
||||
MP_WARN(d_audio, "Audio decoder init failed for %s\n", sel->decoder);
|
||||
}
|
||||
|
||||
if (d_audio->ad_driver) {
|
||||
d_audio->decoder_desc =
|
||||
talloc_asprintf(d_audio, "%s (%s)", decoder->decoder, decoder->desc);
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
d_audio->codec->codec);
|
||||
}
|
||||
|
||||
talloc_free(list);
|
||||
return !!d_audio->ad_driver;
|
||||
}
|
||||
|
||||
void audio_uninit(struct dec_audio *d_audio)
|
||||
{
|
||||
if (!d_audio)
|
||||
return;
|
||||
uninit_decoder(d_audio);
|
||||
talloc_free(d_audio);
|
||||
}
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio)
|
||||
{
|
||||
if (d_audio->ad_driver)
|
||||
d_audio->ad_driver->control(d_audio, ADCTRL_RESET, NULL);
|
||||
d_audio->pts = MP_NOPTS_VALUE;
|
||||
talloc_free(d_audio->current_frame);
|
||||
d_audio->current_frame = NULL;
|
||||
talloc_free(d_audio->packet);
|
||||
d_audio->packet = NULL;
|
||||
talloc_free(d_audio->new_segment);
|
||||
d_audio->new_segment = NULL;
|
||||
d_audio->start = d_audio->end = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
static void fix_audio_pts(struct dec_audio *da)
|
||||
{
|
||||
if (!da->current_frame)
|
||||
return;
|
||||
|
||||
double frame_pts = mp_aframe_get_pts(da->current_frame);
|
||||
if (frame_pts != MP_NOPTS_VALUE) {
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
MP_STATS(da, "value %f audio-pts-err", da->pts - frame_pts);
|
||||
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (da->pts == MP_NOPTS_VALUE || fabs(da->pts - frame_pts) > 0.001)
|
||||
da->pts = frame_pts;
|
||||
}
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
da->pts = 0;
|
||||
|
||||
mp_aframe_set_pts(da->current_frame, da->pts);
|
||||
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
da->pts += mp_aframe_duration(da->current_frame);
|
||||
}
|
||||
|
||||
static bool is_new_segment(struct dec_audio *da, struct demux_packet *p)
|
||||
{
|
||||
return p->segmented &&
|
||||
(p->start != da->start || p->end != da->end || p->codec != da->codec);
|
||||
}
|
||||
|
||||
void audio_work(struct dec_audio *da)
|
||||
{
|
||||
if (da->current_frame || !da->ad_driver)
|
||||
return;
|
||||
|
||||
if (!da->packet && !da->new_segment &&
|
||||
demux_read_packet_async(da->header, &da->packet) == 0)
|
||||
{
|
||||
da->current_state = DATA_WAIT;
|
||||
return;
|
||||
}
|
||||
|
||||
if (da->packet && is_new_segment(da, da->packet)) {
|
||||
assert(!da->new_segment);
|
||||
da->new_segment = da->packet;
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
if (da->ad_driver->send_packet(da, da->packet)) {
|
||||
if (da->recorder_sink)
|
||||
mp_recorder_feed_packet(da->recorder_sink, da->packet);
|
||||
|
||||
talloc_free(da->packet);
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool progress = da->ad_driver->receive_frame(da, &da->current_frame);
|
||||
|
||||
da->current_state = da->current_frame ? DATA_OK : DATA_AGAIN;
|
||||
if (!progress)
|
||||
da->current_state = DATA_EOF;
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = da->current_state == DATA_EOF;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_aframe_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
double frame_pts = mp_aframe_get_pts(da->current_frame);
|
||||
if (frame_pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = frame_pts >= da->end;
|
||||
if (mp_aframe_get_size(da->current_frame) == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a new segment, start it as soon as we're drained/finished.
|
||||
if (segment_end && da->new_segment) {
|
||||
struct demux_packet *new_segment = da->new_segment;
|
||||
da->new_segment = NULL;
|
||||
|
||||
if (da->codec == new_segment->codec) {
|
||||
audio_reset_decoding(da);
|
||||
} else {
|
||||
da->codec = new_segment->codec;
|
||||
da->ad_driver->uninit(da);
|
||||
da->ad_driver = NULL;
|
||||
audio_init_best_codec(da);
|
||||
}
|
||||
|
||||
da->start = new_segment->start;
|
||||
da->end = new_segment->end;
|
||||
|
||||
da->packet = new_segment;
|
||||
da->current_state = DATA_AGAIN;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch an audio frame decoded with audio_work(). Returns one of:
|
||||
// DATA_OK: *out_frame is set to a new image
|
||||
// DATA_WAIT: waiting for demuxer; will receive a wakeup signal
|
||||
// DATA_EOF: end of file, no more frames to be expected
|
||||
// DATA_AGAIN: dropped frame or something similar
|
||||
int audio_get_frame(struct dec_audio *da, struct mp_aframe **out_frame)
|
||||
{
|
||||
*out_frame = NULL;
|
||||
if (da->current_frame) {
|
||||
*out_frame = da->current_frame;
|
||||
da->current_frame = NULL;
|
||||
return DATA_OK;
|
||||
}
|
||||
if (da->current_state == DATA_OK)
|
||||
return DATA_AGAIN;
|
||||
return da->current_state;
|
||||
}
|
||||
@@ -1,66 +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 Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_DEC_AUDIO_H
|
||||
#define MPLAYER_DEC_AUDIO_H
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "demux/demux.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct mp_codec_params *codec;
|
||||
char *decoder_desc;
|
||||
|
||||
bool try_spdif;
|
||||
|
||||
struct mp_recorder_sink *recorder_sink;
|
||||
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
|
||||
// Strictly internal (dec_audio.c).
|
||||
|
||||
double pts; // endpts of previous frame
|
||||
double start, end;
|
||||
struct demux_packet *packet;
|
||||
struct demux_packet *new_segment;
|
||||
struct mp_aframe *current_frame;
|
||||
int current_state;
|
||||
};
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
void audio_work(struct dec_audio *d_audio);
|
||||
int audio_get_frame(struct dec_audio *d_audio, struct mp_aframe **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
|
||||
#endif /* MPLAYER_DEC_AUDIO_H */
|
||||
@@ -1,821 +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 "config.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "options/m_config.h"
|
||||
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "af.h"
|
||||
|
||||
// Static list of filters
|
||||
extern const struct af_info af_info_format;
|
||||
extern const struct af_info af_info_lavcac3enc;
|
||||
extern const struct af_info af_info_lavrresample;
|
||||
extern const struct af_info af_info_scaletempo;
|
||||
extern const struct af_info af_info_lavfi;
|
||||
extern const struct af_info af_info_lavfi_bridge;
|
||||
extern const struct af_info af_info_rubberband;
|
||||
|
||||
static const struct af_info *const filter_list[] = {
|
||||
&af_info_format,
|
||||
&af_info_lavcac3enc,
|
||||
&af_info_lavrresample,
|
||||
#if HAVE_RUBBERBAND
|
||||
&af_info_rubberband,
|
||||
#endif
|
||||
&af_info_scaletempo,
|
||||
&af_info_lavfi,
|
||||
&af_info_lavfi_bridge,
|
||||
NULL
|
||||
};
|
||||
|
||||
static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
{
|
||||
if (index >= MP_ARRAY_SIZE(filter_list) - 1)
|
||||
return false;
|
||||
const struct af_info *af = filter_list[index];
|
||||
*dst = (struct m_obj_desc) {
|
||||
.name = af->name,
|
||||
.description = af->info,
|
||||
.priv_size = af->priv_size,
|
||||
.priv_defaults = af->priv_defaults,
|
||||
.options = af->options,
|
||||
.p = af,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
const struct m_obj_list af_obj_list = {
|
||||
.get_desc = get_desc,
|
||||
.description = "audio filters",
|
||||
.allow_disable_entries = true,
|
||||
.allow_unknown_entries = true,
|
||||
.aliases = {
|
||||
{"force", "format"},
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
static void af_forget_frames(struct af_instance *af)
|
||||
{
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
talloc_free(af->out_queued[n]);
|
||||
af->num_out_queued = 0;
|
||||
}
|
||||
|
||||
static void af_chain_forget_frames(struct af_stream *s)
|
||||
{
|
||||
for (struct af_instance *cur = s->first; cur; cur = cur->next)
|
||||
af_forget_frames(cur);
|
||||
}
|
||||
|
||||
static void af_copy_unset_fields(struct mp_audio *dst, struct mp_audio *src)
|
||||
{
|
||||
if (dst->format == AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(dst, src->format);
|
||||
if (dst->nch == 0)
|
||||
mp_audio_set_channels(dst, &src->channels);
|
||||
if (dst->rate == 0)
|
||||
dst->rate = src->rate;
|
||||
}
|
||||
|
||||
static int input_control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
assert(arg == &((struct af_stream *)af->priv)->input);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int output_control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
struct af_stream *s = af->priv;
|
||||
struct mp_audio *output = &s->output;
|
||||
struct mp_audio *filter_output = &s->filter_output;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
*filter_output = *output;
|
||||
af_copy_unset_fields(filter_output, in);
|
||||
*in = *filter_output;
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int dummy_filter(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
af_add_output_frame(af, frame);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Function for creating a new filter of type name.The name may
|
||||
contain the commandline parameters for the filter */
|
||||
static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
char **args)
|
||||
{
|
||||
const char *lavfi_name = NULL;
|
||||
char **lavfi_args = NULL;
|
||||
struct m_obj_desc desc;
|
||||
if (!m_obj_list_find(&desc, &af_obj_list, bstr0(name))) {
|
||||
if (!m_obj_list_find(&desc, &af_obj_list, bstr0("lavfi-bridge"))) {
|
||||
MP_ERR(s, "Couldn't find audio filter '%s'.\n", name);
|
||||
return NULL;
|
||||
}
|
||||
lavfi_name = name;
|
||||
lavfi_args = args;
|
||||
args = NULL;
|
||||
if (strncmp(lavfi_name, "lavfi-", 6) == 0)
|
||||
lavfi_name += 6;
|
||||
}
|
||||
MP_VERBOSE(s, "Adding filter %s \n", name);
|
||||
|
||||
struct af_instance *af = talloc_zero(NULL, struct af_instance);
|
||||
*af = (struct af_instance) {
|
||||
.full_name = talloc_strdup(af, name),
|
||||
.info = desc.p,
|
||||
.data = talloc_zero(af, struct mp_audio),
|
||||
.log = mp_log_new(af, s->log, name),
|
||||
.opts = s->opts,
|
||||
.global = s->global,
|
||||
.out_pool = mp_audio_pool_create(af),
|
||||
};
|
||||
struct m_config *config =
|
||||
m_config_from_obj_desc_and_args(af, s->log, NULL, &desc,
|
||||
name, s->opts->af_defs, args);
|
||||
if (!config)
|
||||
goto error;
|
||||
if (lavfi_name) {
|
||||
// Pass the filter arguments as proper sub-options to the bridge filter.
|
||||
struct m_config_option *name_opt = m_config_get_co(config, bstr0("name"));
|
||||
assert(name_opt);
|
||||
assert(name_opt->opt->type == &m_option_type_string);
|
||||
if (m_config_set_option_raw(config, name_opt, &lavfi_name, 0) < 0)
|
||||
goto error;
|
||||
struct m_config_option *opts = m_config_get_co(config, bstr0("opts"));
|
||||
assert(opts);
|
||||
assert(opts->opt->type == &m_option_type_keyvalue_list);
|
||||
if (m_config_set_option_raw(config, opts, &lavfi_args, 0) < 0)
|
||||
goto error;
|
||||
af->full_name = talloc_asprintf(af, "%s (lavfi)", af->full_name);
|
||||
}
|
||||
af->priv = config->optstruct;
|
||||
|
||||
// Initialize the new filter
|
||||
if (af->info->open(af) != AF_OK)
|
||||
goto error;
|
||||
|
||||
return af;
|
||||
|
||||
error:
|
||||
MP_ERR(s, "Couldn't create or open audio filter '%s'\n", name);
|
||||
talloc_free(af);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create and insert a new filter of type name before the filter in the
|
||||
argument. This function can be called during runtime, the return
|
||||
value is the new filter */
|
||||
static struct af_instance *af_prepend(struct af_stream *s,
|
||||
struct af_instance *af,
|
||||
char *name, char **args)
|
||||
{
|
||||
if (!af)
|
||||
af = s->last;
|
||||
if (af == s->first)
|
||||
af = s->first->next;
|
||||
// Create the new filter and make sure it is OK
|
||||
struct af_instance *new = af_create(s, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
// Update pointers
|
||||
new->next = af;
|
||||
new->prev = af->prev;
|
||||
af->prev = new;
|
||||
new->prev->next = new;
|
||||
return new;
|
||||
}
|
||||
|
||||
// Uninit and remove the filter "af"
|
||||
static void af_remove(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
if (!af)
|
||||
return;
|
||||
|
||||
if (af == s->first || af == s->last)
|
||||
return;
|
||||
|
||||
// Print friendly message
|
||||
MP_VERBOSE(s, "Removing filter %s \n", af->info->name);
|
||||
|
||||
// Detach pointers
|
||||
af->prev->next = af->next;
|
||||
af->next->prev = af->prev;
|
||||
|
||||
if (af->uninit)
|
||||
af->uninit(af);
|
||||
af_forget_frames(af);
|
||||
talloc_free(af);
|
||||
}
|
||||
|
||||
static void remove_auto_inserted_filters(struct af_stream *s)
|
||||
{
|
||||
repeat:
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af->auto_inserted) {
|
||||
af_remove(s, af);
|
||||
goto repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
int msg_level)
|
||||
{
|
||||
MP_MSG(s, msg_level, "Audio filter chain:\n");
|
||||
|
||||
struct af_instance *af = s->first;
|
||||
while (af) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->full_name);
|
||||
if (af->label)
|
||||
mp_snprintf_cat(b, sizeof(b), "\"%s\" ", af->label);
|
||||
if (af->data)
|
||||
mp_snprintf_cat(b, sizeof(b), "%s", mp_audio_config_to_str(af->data));
|
||||
if (af->auto_inserted)
|
||||
mp_snprintf_cat(b, sizeof(b), " [a]");
|
||||
if (af == at)
|
||||
mp_snprintf_cat(b, sizeof(b), " <-");
|
||||
MP_MSG(s, msg_level, "%s\n", b);
|
||||
|
||||
af = af->next;
|
||||
}
|
||||
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
}
|
||||
|
||||
static void reset_formats(struct af_stream *s)
|
||||
{
|
||||
struct mp_audio none = {0};
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af != s->first && af != s->last)
|
||||
mp_audio_copy_config(af->data, &none);
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_reinit(struct af_instance *af)
|
||||
{
|
||||
struct af_instance *prev = af->prev;
|
||||
assert(prev);
|
||||
|
||||
// Check if this is the first filter
|
||||
struct mp_audio in = *prev->data;
|
||||
// Reset just in case...
|
||||
mp_audio_set_null_data(&in);
|
||||
|
||||
if (!mp_audio_config_valid(&in))
|
||||
return AF_ERROR;
|
||||
|
||||
af->fmt_in = in;
|
||||
int rv = af->control(af, AF_CONTROL_REINIT, &in);
|
||||
if (rv == AF_OK && !mp_audio_config_equals(&in, prev->data))
|
||||
rv = AF_FALSE; // conversion filter needed
|
||||
if (rv == AF_FALSE)
|
||||
af->fmt_in = in;
|
||||
|
||||
if (rv == AF_OK) {
|
||||
if (!mp_audio_config_valid(af->data))
|
||||
return AF_ERROR;
|
||||
af->fmt_out = *af->data;
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int filter_reinit_with_conversion(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
int rv = filter_reinit(af);
|
||||
|
||||
// Conversion filter is needed
|
||||
if (rv == AF_FALSE) {
|
||||
// First try if we can change the output format of the previous
|
||||
// filter to the input format the current filter is expecting.
|
||||
struct mp_audio in = af->fmt_in;
|
||||
if (af->prev != s->first && !mp_audio_config_equals(af->prev->data, &in)) {
|
||||
// This should have been successful (because it succeeded
|
||||
// before), even if just reverting to the old output format.
|
||||
mp_audio_copy_config(af->prev->data, &in);
|
||||
rv = filter_reinit(af->prev);
|
||||
if (rv != AF_OK)
|
||||
return rv;
|
||||
}
|
||||
if (!mp_audio_config_equals(af->prev->data, &in)) {
|
||||
// Retry with conversion filter added.
|
||||
struct af_instance *new =
|
||||
af_prepend(s, af, "lavrresample", NULL);
|
||||
if (!new)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
mp_audio_copy_config(new->data, &in);
|
||||
rv = filter_reinit(new);
|
||||
if (rv != AF_OK)
|
||||
af_remove(s, new);
|
||||
}
|
||||
if (rv == AF_OK)
|
||||
rv = filter_reinit(af);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int af_find_output_conversion(struct af_stream *s, struct mp_audio *cfg)
|
||||
{
|
||||
assert(mp_audio_config_valid(&s->output));
|
||||
assert(s->initialized > 0);
|
||||
|
||||
if (mp_chmap_equals_reordered(&s->input.channels, &s->output.channels))
|
||||
return AF_ERROR;
|
||||
|
||||
// Heuristic to detect point of conversion. If it looks like something
|
||||
// more complicated is going on, better bail out.
|
||||
// We expect that the last filter converts channels.
|
||||
struct af_instance *conv = s->last->prev;
|
||||
if (!conv->auto_inserted)
|
||||
return AF_ERROR;
|
||||
if (!(mp_chmap_equals_reordered(&conv->fmt_in.channels, &s->input.channels) &&
|
||||
mp_chmap_equals_reordered(&conv->fmt_out.channels, &s->output.channels)))
|
||||
return AF_ERROR;
|
||||
// Also, should be the only one which does auto conversion.
|
||||
for (struct af_instance *af = s->first->next; af != s->last; af = af->next)
|
||||
{
|
||||
if (af != conv && af->auto_inserted &&
|
||||
!mp_chmap_equals_reordered(&af->fmt_in.channels, &af->fmt_out.channels))
|
||||
return AF_ERROR;
|
||||
}
|
||||
// And not if it's the only filter.
|
||||
if (conv->prev == s->first && conv->next == s->last)
|
||||
return AF_ERROR;
|
||||
|
||||
*cfg = s->output;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Return AF_OK on success or AF_ERROR on failure.
|
||||
static int af_do_reinit(struct af_stream *s, bool second_pass)
|
||||
{
|
||||
struct mp_audio convert_early = {0};
|
||||
if (second_pass) {
|
||||
// If a channel conversion happens, and it is done by an auto-inserted
|
||||
// filter, then insert a filter to convert it early. Otherwise, do
|
||||
// nothing and return immediately.
|
||||
if (af_find_output_conversion(s, &convert_early) != AF_OK)
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
remove_auto_inserted_filters(s);
|
||||
af_chain_forget_frames(s);
|
||||
reset_formats(s);
|
||||
s->first->fmt_in = s->first->fmt_out = s->input;
|
||||
|
||||
if (mp_audio_config_valid(&convert_early)) {
|
||||
struct af_instance *new = af_prepend(s, s->first, "lavrresample", NULL);
|
||||
if (!new)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
mp_audio_copy_config(new->data, &convert_early);
|
||||
int rv = filter_reinit(new);
|
||||
if (rv != AF_DETACH && rv != AF_OK)
|
||||
return AF_ERROR;
|
||||
MP_VERBOSE(s, "Moving up output conversion.\n");
|
||||
}
|
||||
|
||||
// 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;
|
||||
while (af) {
|
||||
int rv = filter_reinit_with_conversion(s, af);
|
||||
|
||||
switch (rv) {
|
||||
case AF_OK:
|
||||
af = af->next;
|
||||
break;
|
||||
case AF_FALSE: {
|
||||
// If the format conversion is (probably) caused by spdif, then
|
||||
// (as a feature) drop the filter, instead of failing hard.
|
||||
int fmt_in1 = af->prev->data->format;
|
||||
int fmt_in2 = af->fmt_in.format;
|
||||
if (af_fmt_is_valid(fmt_in1) && af_fmt_is_valid(fmt_in2)) {
|
||||
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);
|
||||
struct af_instance *aft = af->prev;
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
goto negotiate_error;
|
||||
}
|
||||
case AF_DETACH: { // Filter is redundant and wants to be unloaded
|
||||
struct af_instance *aft = af->prev; // never NULL
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
MP_ERR(s, "Reinitialization did not work, "
|
||||
"audio filter '%s' returned error code %i\n",
|
||||
af->info->name, rv);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set previously unset fields in s->output to those of the filter chain
|
||||
* output. This is used to make the output format fixed, and even if you
|
||||
* insert new filters or change the input format, the output format won't
|
||||
* change. (Audio outputs generally can't change format at runtime.) */
|
||||
af_copy_unset_fields(&s->output, &s->filter_output);
|
||||
if (mp_audio_config_equals(&s->output, &s->filter_output)) {
|
||||
s->initialized = 1;
|
||||
af_print_filter_chain(s, NULL, MSGL_V);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
goto error;
|
||||
|
||||
negotiate_error:
|
||||
MP_ERR(s, "Unable to convert audio input format to output format.\n");
|
||||
error:
|
||||
s->initialized = -1;
|
||||
af_print_filter_chain(s, af, MSGL_ERR);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
static int af_reinit(struct af_stream *s)
|
||||
{
|
||||
int r = af_do_reinit(s, false);
|
||||
if (r == AF_OK && mp_audio_config_valid(&s->output)) {
|
||||
r = af_do_reinit(s, true);
|
||||
if (r != AF_OK) {
|
||||
MP_ERR(s, "Failed second pass filter negotiation.\n");
|
||||
r = af_do_reinit(s, false);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
// Uninit and remove all filters
|
||||
void af_uninit(struct af_stream *s)
|
||||
{
|
||||
while (s->first->next && s->first->next != s->last)
|
||||
af_remove(s, s->first->next);
|
||||
af_chain_forget_frames(s);
|
||||
s->initialized = 0;
|
||||
}
|
||||
|
||||
struct af_stream *af_new(struct mpv_global *global)
|
||||
{
|
||||
struct af_stream *s = talloc_zero(NULL, struct af_stream);
|
||||
s->log = mp_log_new(s, global->log, "!af");
|
||||
|
||||
static const struct af_info in = { .name = "in" };
|
||||
s->first = talloc(s, struct af_instance);
|
||||
*s->first = (struct af_instance) {
|
||||
.full_name = "in",
|
||||
.info = &in,
|
||||
.log = s->log,
|
||||
.control = input_control,
|
||||
.filter_frame = dummy_filter,
|
||||
.priv = s,
|
||||
.data = &s->input,
|
||||
};
|
||||
|
||||
static const struct af_info out = { .name = "out" };
|
||||
s->last = talloc(s, struct af_instance);
|
||||
*s->last = (struct af_instance) {
|
||||
.full_name = "out",
|
||||
.info = &out,
|
||||
.log = s->log,
|
||||
.control = output_control,
|
||||
.filter_frame = dummy_filter,
|
||||
.priv = s,
|
||||
.data = &s->filter_output,
|
||||
};
|
||||
|
||||
s->first->next = s->last;
|
||||
s->last->prev = s->first;
|
||||
s->opts = global->opts;
|
||||
s->global = global;
|
||||
return s;
|
||||
}
|
||||
|
||||
void af_destroy(struct af_stream *s)
|
||||
{
|
||||
af_uninit(s);
|
||||
talloc_free(s);
|
||||
}
|
||||
|
||||
/* Initialize the stream "s". This function creates a new filter list
|
||||
if necessary according to the values set in input and output. Input
|
||||
and output should contain the format of the current movie and the
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the preferred output parameters is 0 the one that needs
|
||||
no conversion is used (i.e. the output format in the last filter).
|
||||
The return value is 0 if success and -1 if failure */
|
||||
int af_init(struct af_stream *s)
|
||||
{
|
||||
// Precaution in case caller is misbehaving
|
||||
mp_audio_set_null_data(&s->input);
|
||||
mp_audio_set_null_data(&s->output);
|
||||
|
||||
// Check if this is the first call
|
||||
if (s->first->next == s->last) {
|
||||
// Add all filters in the list (if there are any)
|
||||
struct m_obj_settings *list = s->opts->af_settings;
|
||||
for (int i = 0; list && list[i].name; i++) {
|
||||
if (!list[i].enabled)
|
||||
continue;
|
||||
struct af_instance *af =
|
||||
af_prepend(s, s->last, list[i].name, list[i].attribs);
|
||||
if (!af) {
|
||||
af_uninit(s);
|
||||
s->initialized = -1;
|
||||
return -1;
|
||||
}
|
||||
af->label = talloc_strdup(af, list[i].label);
|
||||
}
|
||||
}
|
||||
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
// Something is stuffed audio out will not work
|
||||
MP_ERR(s, "Could not create audio filter chain.\n");
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Add filter during execution. This function adds the filter "name"
|
||||
to the stream s. The filter will be inserted somewhere nice in the
|
||||
list of filters. The return value is a pointer to the new filter,
|
||||
If the filter couldn't be added the return value is NULL. */
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
char **args)
|
||||
{
|
||||
assert(label);
|
||||
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
|
||||
// Reinitialize the filter list
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_remove_by_label(s, label);
|
||||
return NULL;
|
||||
}
|
||||
return af_find_by_label(s, label);
|
||||
}
|
||||
|
||||
struct af_instance *af_find_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
if (af->label && strcmp(af->label, label) == 0)
|
||||
return af;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove the first filter that matches this name. Return number of filters
|
||||
* removed (0, 1), or a negative error code if reinit after removing failed.
|
||||
*/
|
||||
int af_remove_by_label(struct af_stream *s, char *label)
|
||||
{
|
||||
struct af_instance *af = af_find_by_label(s, label);
|
||||
if (!af)
|
||||
return 0;
|
||||
af_remove(s, af);
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
af_uninit(s);
|
||||
af_init(s);
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Calculate the total delay [seconds of output] caused by the filters */
|
||||
double af_calc_delay(struct af_stream *s)
|
||||
{
|
||||
struct af_instance *af = s->first;
|
||||
double delay = 0.0;
|
||||
while (af) {
|
||||
delay += af->delay;
|
||||
for (int n = 0; n < af->num_out_queued; n++)
|
||||
delay += af->out_queued[n]->samples / (double)af->data->rate;
|
||||
af = af->next;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/* Send control to all filters, starting with the last until one accepts the
|
||||
* command with AF_OK. Return the accepting filter. */
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
int res = AF_UNKNOWN;
|
||||
struct af_instance *filt = s->last;
|
||||
while (filt) {
|
||||
res = filt->control(filt, cmd, arg);
|
||||
if (res == AF_OK)
|
||||
return filt;
|
||||
filt = filt->prev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Send control to all filters. Never stop, even if a filter returns AF_OK. */
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg)
|
||||
{
|
||||
char *args[2] = {cmd, arg};
|
||||
if (strcmp(label, "all") == 0) {
|
||||
af_control_all(s, AF_CONTROL_COMMAND, args);
|
||||
return 0;
|
||||
} else {
|
||||
return af_control_by_label(s, AF_CONTROL_COMMAND, args, bstr0(label));
|
||||
}
|
||||
}
|
||||
|
||||
// Used by filters to add a filtered frame to the output queue.
|
||||
// Ownership of frame is transferred from caller to the filter chain.
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
if (frame) {
|
||||
assert(mp_audio_config_equals(&af->fmt_out, frame));
|
||||
MP_TARRAY_APPEND(af, af->out_queued, af->num_out_queued, frame);
|
||||
}
|
||||
}
|
||||
|
||||
static bool af_has_output_frame(struct af_instance *af)
|
||||
{
|
||||
if (!af->num_out_queued && af->filter_out) {
|
||||
if (af->filter_out(af) < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
}
|
||||
return af->num_out_queued > 0;
|
||||
}
|
||||
|
||||
static struct mp_audio *af_dequeue_output_frame(struct af_instance *af)
|
||||
{
|
||||
struct mp_audio *res = NULL;
|
||||
if (af_has_output_frame(af)) {
|
||||
res = af->out_queued[0];
|
||||
MP_TARRAY_REMOVE_AT(af->out_queued, af->num_out_queued, 0);
|
||||
}
|
||||
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)
|
||||
assert(mp_audio_config_equals(&af->fmt_in, frame));
|
||||
int r = af->filter_frame(af, frame);
|
||||
if (r < 0)
|
||||
MP_ERR(af, "Error filtering frame.\n");
|
||||
return r;
|
||||
}
|
||||
|
||||
// Input a frame into the filter chain. Ownership of frame is transferred.
|
||||
// Return >= 0 on success, < 0 on failure (even if output frames were produced)
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
assert(frame);
|
||||
if (s->initialized < 1) {
|
||||
talloc_free(frame);
|
||||
return -1;
|
||||
}
|
||||
return af_do_filter(s->first, frame);
|
||||
}
|
||||
|
||||
// Output the next queued frame (if any) from the full filter chain.
|
||||
// The frame can be retrieved with af_read_output_frame().
|
||||
// eof: if set, assume there's no more input i.e. af_filter_frame() will
|
||||
// not be called (until reset) - flush all internally delayed frames
|
||||
// returns: -1: error, 0: no output, 1: output available
|
||||
int af_output_frame(struct af_stream *s, bool eof)
|
||||
{
|
||||
if (s->last->num_out_queued)
|
||||
return 1;
|
||||
if (s->initialized < 1)
|
||||
return -1;
|
||||
while (1) {
|
||||
struct af_instance *last = NULL;
|
||||
for (struct af_instance * cur = s->first; cur; cur = cur->next) {
|
||||
// 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;
|
||||
}
|
||||
if (af_has_output_frame(cur))
|
||||
last = cur;
|
||||
}
|
||||
if (!last)
|
||||
return 0;
|
||||
if (!last->next)
|
||||
return 1;
|
||||
int r = af_do_filter(last->next, af_dequeue_output_frame(last));
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s)
|
||||
{
|
||||
if (!s->last->num_out_queued)
|
||||
af_output_frame(s, false);
|
||||
return af_dequeue_output_frame(s->last);
|
||||
}
|
||||
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame)
|
||||
{
|
||||
struct af_instance *af = s->last;
|
||||
MP_TARRAY_INSERT_AT(af, af->out_queued, af->num_out_queued, 0, frame);
|
||||
}
|
||||
|
||||
// Make sure the caller can change data referenced by the frame.
|
||||
// Return negative error code on failure (i.e. you can't write).
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame)
|
||||
{
|
||||
return mp_audio_pool_make_writeable(af->out_pool, frame);
|
||||
}
|
||||
|
||||
void af_seek_reset(struct af_stream *s)
|
||||
{
|
||||
af_control_all(s, AF_CONTROL_RESET, NULL);
|
||||
af_chain_forget_frames(s);
|
||||
}
|
||||
@@ -1,161 +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_H
|
||||
#define MPLAYER_AF_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "config.h"
|
||||
#if !(HAVE_LIBAF && HAVE_GPL)
|
||||
#error "libaf/GPL disabled"
|
||||
#endif
|
||||
|
||||
#include "options/options.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/audio.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/common.h"
|
||||
|
||||
struct af_instance;
|
||||
struct mpv_global;
|
||||
|
||||
// Number of channels
|
||||
#define AF_NCH MP_NUM_CHANNELS
|
||||
|
||||
// Flags for af->filter()
|
||||
#define AF_FILTER_FLAG_EOF 1
|
||||
|
||||
/* Audio filter information not specific for current instance, but for
|
||||
a specific filter */
|
||||
struct af_info {
|
||||
const char *info;
|
||||
const char *name;
|
||||
int (*open)(struct af_instance *vf);
|
||||
int priv_size;
|
||||
const void *priv_defaults;
|
||||
const struct m_option *options;
|
||||
};
|
||||
|
||||
// Linked list of audio filters
|
||||
struct af_instance {
|
||||
const struct af_info *info;
|
||||
char *full_name;
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
int (*control)(struct af_instance *af, int cmd, void *arg);
|
||||
void (*uninit)(struct af_instance *af);
|
||||
/* Feed a frame. The frame is NULL if EOF was reached, and the filter
|
||||
* should drain all remaining buffered data.
|
||||
* Use af_add_output_frame() to output data. The optional filter_out
|
||||
* callback can be set to produce output frames gradually.
|
||||
*/
|
||||
int (*filter_frame)(struct af_instance *af, struct mp_audio *frame);
|
||||
int (*filter_out)(struct af_instance *af);
|
||||
void *priv;
|
||||
struct mp_audio *data; // configuration and buffer for outgoing data stream
|
||||
|
||||
struct af_instance *next;
|
||||
struct af_instance *prev;
|
||||
double delay; /* Delay caused by the filter, in seconds of audio consumed
|
||||
* without corresponding output */
|
||||
bool auto_inserted; // inserted by af.c, such as conversion filters
|
||||
char *label;
|
||||
|
||||
struct mp_audio fmt_in, fmt_out;
|
||||
|
||||
struct mp_audio **out_queued;
|
||||
int num_out_queued;
|
||||
|
||||
struct mp_audio_pool *out_pool;
|
||||
};
|
||||
|
||||
// Current audio stream
|
||||
struct af_stream {
|
||||
int initialized; // 0: no, 1: yes, -1: attempted to, but failed
|
||||
|
||||
// The first and last filter in the list
|
||||
struct af_instance *first;
|
||||
struct af_instance *last;
|
||||
// The user sets the input format (what the decoder outputs), and sets some
|
||||
// or all fields in output to the output format the AO accepts.
|
||||
struct mp_audio input;
|
||||
struct mp_audio output;
|
||||
struct mp_audio filter_output;
|
||||
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
};
|
||||
|
||||
// Return values
|
||||
#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 {
|
||||
AF_CONTROL_REINIT = 1,
|
||||
AF_CONTROL_RESET,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE,
|
||||
AF_CONTROL_GET_METADATA,
|
||||
AF_CONTROL_COMMAND,
|
||||
};
|
||||
|
||||
// Argument for AF_CONTROL_SET_PAN_LEVEL
|
||||
typedef struct af_control_ext_s {
|
||||
void* arg; // Argument
|
||||
int ch; // Chanel number
|
||||
} af_control_ext_t;
|
||||
|
||||
struct af_stream *af_new(struct mpv_global *global);
|
||||
void af_destroy(struct af_stream *s);
|
||||
int af_init(struct af_stream *s);
|
||||
void af_uninit(struct af_stream *s);
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
char **args);
|
||||
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);
|
||||
int af_send_command(struct af_stream *s, char *label, char *cmd, char *arg);
|
||||
|
||||
void af_add_output_frame(struct af_instance *af, struct mp_audio *frame);
|
||||
int af_filter_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
int af_output_frame(struct af_stream *s, bool eof);
|
||||
struct mp_audio *af_read_output_frame(struct af_stream *s);
|
||||
void af_unread_output_frame(struct af_stream *s, struct mp_audio *frame);
|
||||
int af_make_writeable(struct af_instance *af, struct mp_audio *frame);
|
||||
|
||||
double af_calc_delay(struct af_stream *s);
|
||||
|
||||
int af_test_output(struct af_instance *af, struct mp_audio *out);
|
||||
|
||||
int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
|
||||
float af_softclip(float a);
|
||||
|
||||
#endif /* MPLAYER_AF_H */
|
||||
+90
-83
@@ -15,18 +15,14 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct m_config *config;
|
||||
|
||||
struct f_opts {
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct m_channels in_channels;
|
||||
@@ -37,98 +33,109 @@ struct priv {
|
||||
int fail;
|
||||
};
|
||||
|
||||
static void force_in_params(struct af_instance *af, struct mp_audio *in)
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
struct mp_pin *in_pin;
|
||||
};
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (!mp_pin_can_transfer_data(f->ppins[1], p->in_pin))
|
||||
return;
|
||||
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
|
||||
if (priv->in_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
|
||||
if (p->opts->fail) {
|
||||
MP_ERR(f, "Failing on purpose.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
}
|
||||
if (frame.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
}
|
||||
|
||||
static void force_out_params(struct af_instance *af, struct mp_audio *out)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
if (frame.type != MP_FRAME_AUDIO) {
|
||||
MP_ERR(f, "audio frame expected\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
struct mp_aframe *in = frame.data;
|
||||
|
||||
if (priv->out_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
|
||||
if (p->opts->out_channels.num_chmaps > 0) {
|
||||
if (!mp_aframe_set_chmap(in, &p->opts->out_channels.chmaps[0])) {
|
||||
MP_ERR(f, "could not force output channels\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
}
|
||||
if (p->opts->out_srate)
|
||||
mp_aframe_set_rate(in, p->opts->out_srate);
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
return;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
error:
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
|
||||
force_in_params(af, in);
|
||||
mp_audio_copy_config(out, in);
|
||||
force_out_params(af, out);
|
||||
static const struct mp_filter_info af_format_filter = {
|
||||
.name = "format",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
};
|
||||
|
||||
if (in->nch != out->nch || in->bps != out->bps) {
|
||||
MP_ERR(af, "Forced input/output formats are incompatible.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
static struct mp_filter *af_format_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_format_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (priv->fail) {
|
||||
MP_ERR(af, "Failing on purpose.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
if (data)
|
||||
mp_audio_copy_config(data, af->data);
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
}
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
if (p->opts->in_format)
|
||||
mp_autoconvert_add_afmt(conv, p->opts->in_format);
|
||||
if (p->opts->in_srate)
|
||||
mp_autoconvert_add_srate(conv, p->opts->in_srate);
|
||||
if (p->opts->in_channels.num_chmaps > 0)
|
||||
mp_autoconvert_add_chmap(conv, &p->opts->in_channels.chmaps[0]);
|
||||
|
||||
force_in_params(af, af->data);
|
||||
force_out_params(af, af->data);
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return AF_OK;
|
||||
return f;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_format = {
|
||||
.info = "Force audio format",
|
||||
.name = "format",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_AUDIOFORMAT("out-format", out_format, 0),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
|
||||
const struct mp_user_filter_entry af_format = {
|
||||
.desc = {
|
||||
.name = "format",
|
||||
.description = "Force audio format",
|
||||
.priv_size = sizeof(struct f_opts),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("channels", in_channels, 0, .min = 1),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
},
|
||||
.create = af_format_create,
|
||||
};
|
||||
+234
-280
@@ -31,14 +31,17 @@
|
||||
#include <libavutil/bswap.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/f_utils.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
|
||||
#define AC3_MAX_CHANNELS 6
|
||||
@@ -49,173 +52,89 @@ const uint16_t ac3_bitrate_tab[19] = {
|
||||
160, 192, 224, 256, 320, 384, 448, 512, 576, 640
|
||||
};
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_ac3enc_s {
|
||||
struct f_opts {
|
||||
int add_iec61937_header;
|
||||
int bit_rate;
|
||||
int min_channel_num;
|
||||
char *encoder;
|
||||
char **avopts;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe *in_frame;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
|
||||
struct AVCodec *lavc_acodec;
|
||||
struct AVCodecContext *lavc_actx;
|
||||
int bit_rate;
|
||||
struct mp_audio *input; // frame passed to libavcodec
|
||||
struct mp_audio *pending; // unconsumed input data
|
||||
int in_samples; // samples of input per AC3 frame
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
int64_t encoder_buffered;
|
||||
};
|
||||
|
||||
int cfg_add_iec61937_header;
|
||||
int cfg_bit_rate;
|
||||
int cfg_min_channel_num;
|
||||
char *cfg_encoder;
|
||||
char **cfg_avopts;
|
||||
} af_ac3enc_t;
|
||||
|
||||
// fmt carries the input format. Change it to the best next-possible format
|
||||
// the encoder likely accepts.
|
||||
static void select_encode_format(AVCodecContext *c, struct mp_audio *fmt)
|
||||
static bool reinit(struct mp_filter *f)
|
||||
{
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(fmt->format, formats);
|
||||
struct priv *s = f->priv;
|
||||
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
const enum AVSampleFormat *lf = c->codec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt && mpfmt == formats[n]) {
|
||||
mp_audio_set_format(fmt, mpfmt);
|
||||
goto done_fmt;
|
||||
}
|
||||
}
|
||||
}
|
||||
done_fmt: ;
|
||||
mp_aframe_reset(s->cur_format);
|
||||
|
||||
int rate =
|
||||
af_select_best_samplerate(fmt->rate, c->codec->supported_samplerates);
|
||||
if (rate > 0)
|
||||
fmt->rate = rate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
const uint64_t *lch = c->codec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
mp_chmap_sel_add_map(&sel, &chmap);
|
||||
}
|
||||
struct mp_chmap res = fmt->channels;
|
||||
mp_chmap_sel_adjust(&sel, &res);
|
||||
if (!mp_chmap_is_empty(&res))
|
||||
mp_audio_set_channels(fmt, &res);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \
|
||||
{0, 96000, 192000, 256000, 384000, 448000, 448000};
|
||||
|
||||
switch (cmd){
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
in->rate = 48000;
|
||||
select_encode_format(s->lavc_actx, in);
|
||||
|
||||
af->data->rate = in->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S_AC3);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
|
||||
if (!mp_audio_config_equals(in, &orig_in))
|
||||
return AF_FALSE;
|
||||
|
||||
if (s->cfg_add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE / af->data->sstride;
|
||||
}
|
||||
|
||||
mp_audio_copy_config(s->input, in);
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
|
||||
MP_DBG(af, "reinit: %d, %d, %d.\n", in->nch, in->rate, s->in_samples);
|
||||
|
||||
int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
|
||||
|
||||
if (s->lavc_actx->channels != in->nch ||
|
||||
s->lavc_actx->sample_rate != in->rate ||
|
||||
s->lavc_actx->bit_rate != bit_rate)
|
||||
{
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(in->format);
|
||||
s->lavc_actx->channels = in->nch;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
|
||||
s->lavc_actx->sample_rate = in->rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(af, "encoder didn't specify input frame size\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
s->in_samples = s->lavc_actx->frame_size;
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
if (s->opts->add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE /
|
||||
mp_aframe_get_sstride(s->in_frame);
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (avcodec_is_open(s->lavc_actx))
|
||||
avcodec_flush_buffers(s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
s->input->samples = 0;
|
||||
s->encoder_buffered = 0;
|
||||
return AF_OK;
|
||||
|
||||
int format = mp_aframe_get_format(s->in_frame);
|
||||
int rate = mp_aframe_get_rate(s->in_frame);
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(s->in_frame, &chmap);
|
||||
|
||||
int bit_rate = s->bit_rate;
|
||||
if (!bit_rate && chmap.num < AC3_MAX_CHANNELS + 1)
|
||||
bit_rate = default_bit_rate[chmap.num];
|
||||
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->sample_fmt = af_to_avformat(format);
|
||||
s->lavc_actx->channels = chmap.num;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&chmap);
|
||||
s->lavc_actx->sample_rate = rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
MP_ERR(f, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return false;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
|
||||
if (s->lavc_actx->frame_size < 1) {
|
||||
MP_ERR(f, "encoder didn't specify input frame size\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in_frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
struct priv *s = f->priv;
|
||||
|
||||
if (s) {
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
talloc_free(s->pending);
|
||||
}
|
||||
TA_FREEP(&s->in_frame);
|
||||
}
|
||||
|
||||
static void update_delay(struct af_instance *af)
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
|
||||
s->encoder_buffered) / (double)s->input->rate;
|
||||
}
|
||||
struct priv *s = f->priv;
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
// filter_output must have been called until no output was produced.
|
||||
if (s->pending && s->pending->samples)
|
||||
MP_ERR(af, "broken data flow\n");
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = audio;
|
||||
update_delay(af);
|
||||
return 0;
|
||||
reset(f);
|
||||
avcodec_free_context(&s->lavc_actx);
|
||||
}
|
||||
|
||||
static void swap_16(uint16_t *ptr, size_t size)
|
||||
@@ -224,105 +143,84 @@ static void swap_16(uint16_t *ptr, size_t size)
|
||||
ptr[n] = av_bswap16(ptr[n]);
|
||||
}
|
||||
|
||||
// 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 frame is complete.
|
||||
static bool fill_buffer(struct af_instance *af)
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
struct priv *s = f->priv;
|
||||
|
||||
af->delay = 0;
|
||||
|
||||
if (s->pending) {
|
||||
if (!mp_audio_is_writeable(s->input))
|
||||
assert(s->input->samples == 0); // we can't have sent a partial frame
|
||||
mp_audio_realloc_min(s->input, s->in_samples);
|
||||
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;
|
||||
}
|
||||
|
||||
// Return <0 on error, 0 on need more input, 1 on success (and *frame set).
|
||||
// To actually advance the read pointer, set s->input->samples=0 afterwards.
|
||||
static int read_input_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
if (!fill_buffer(af))
|
||||
return 0; // need more input
|
||||
|
||||
if (mp_audio_to_avframe(s->input, frame) < 0)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (!s->pending)
|
||||
return 0;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
MP_FATAL(af, "Could not allocate memory \n");
|
||||
return -1;
|
||||
}
|
||||
int err = -1;
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
bool err = true;
|
||||
struct mp_aframe *out = NULL;
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
// Send input as long as it wants.
|
||||
while (1) {
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
if (avcodec_is_open(s->lavc_actx)) {
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret >= 0)
|
||||
break;
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (receive).\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
AVFrame *frame = NULL;
|
||||
struct mp_frame input = mp_pin_out_read(s->in_pin);
|
||||
// The following code assumes no sample data buffering in the encoder.
|
||||
if (input.type == MP_FRAME_EOF) {
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
} else if (input.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&s->in_frame);
|
||||
s->in_frame = input.data;
|
||||
frame = mp_frame_to_av(input, NULL);
|
||||
if (!frame)
|
||||
goto done;
|
||||
if (mp_aframe_get_channels(s->in_frame) < s->opts->min_channel_num) {
|
||||
// Just pass it through.
|
||||
s->in_frame = NULL;
|
||||
mp_pin_in_write(f->ppins[1], input);
|
||||
return;
|
||||
}
|
||||
if (!mp_aframe_config_equals(s->in_frame, s->cur_format)) {
|
||||
if (!reinit(f))
|
||||
goto done;
|
||||
}
|
||||
} else if (input.type) {
|
||||
goto done;
|
||||
if (err == 0)
|
||||
break;
|
||||
err = -1;
|
||||
} else {
|
||||
return; // no data yet
|
||||
}
|
||||
int lavc_ret = avcodec_send_frame(s->lavc_actx, frame);
|
||||
// On EAGAIN, we're supposed to read remaining output.
|
||||
if (lavc_ret == AVERROR(EAGAIN))
|
||||
break;
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
av_frame_free(&frame);
|
||||
if (lavc_ret < 0 && lavc_ret != AVERROR(EAGAIN)) {
|
||||
MP_FATAL(f, "Encode failed (send).\n");
|
||||
goto done;
|
||||
}
|
||||
s->encoder_buffered += s->input->samples;
|
||||
s->input->samples = 0;
|
||||
}
|
||||
int lavc_ret = avcodec_receive_packet(s->lavc_actx, &pkt);
|
||||
if (lavc_ret == AVERROR(EAGAIN)) {
|
||||
// Need to buffer more input.
|
||||
err = 0;
|
||||
goto done;
|
||||
}
|
||||
if (lavc_ret < 0) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
pkt.size, s->pending->samples + s->input->samples);
|
||||
|
||||
s->encoder_buffered -= AC3_FRAME_SIZE;
|
||||
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, s->out_samples);
|
||||
if (!out)
|
||||
if (!s->in_frame)
|
||||
goto done;
|
||||
mp_audio_copy_attributes(out, s->pending);
|
||||
|
||||
out = mp_aframe_create();
|
||||
mp_aframe_set_format(out, AF_FORMAT_S_AC3);
|
||||
mp_aframe_set_chmap(out, &(struct mp_chmap)MP_CHMAP_INIT_STEREO);
|
||||
mp_aframe_set_rate(out, 48000);
|
||||
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, s->out_samples) < 0)
|
||||
goto done;
|
||||
|
||||
int sstride = mp_aframe_get_sstride(out);
|
||||
|
||||
mp_aframe_copy_attributes(out, s->in_frame);
|
||||
|
||||
int frame_size = pkt.size;
|
||||
int header_len = 0;
|
||||
char hdr[8];
|
||||
|
||||
if (s->cfg_add_iec61937_header && pkt.size > 5) {
|
||||
if (s->opts->add_iec61937_header && pkt.size > 5) {
|
||||
int bsmod = pkt.data[5] & 0x7;
|
||||
int len = frame_size;
|
||||
|
||||
@@ -336,48 +234,69 @@ static int filter_out(struct af_instance *af)
|
||||
AV_WL16(hdr + 6, len << 3); // number of bits in payload
|
||||
}
|
||||
|
||||
if (frame_size > out->samples * out->sstride)
|
||||
if (frame_size > s->out_samples * sstride)
|
||||
abort();
|
||||
|
||||
char *buf = (char *)out->planes[0];
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
if (!planes)
|
||||
goto done;
|
||||
char *buf = planes[0];
|
||||
memcpy(buf, hdr, header_len);
|
||||
memcpy(buf + header_len, pkt.data, pkt.size);
|
||||
memset(buf + header_len + pkt.size, 0,
|
||||
frame_size - (header_len + pkt.size));
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
mp_aframe_set_size(out, frame_size / sstride);
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
out = NULL;
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
av_packet_unref(&pkt);
|
||||
av_frame_free(&frame);
|
||||
update_delay(af);
|
||||
return err;
|
||||
talloc_free(out);
|
||||
if (err)
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance* af){
|
||||
static const struct mp_filter_info af_lavcac3enc_filter = {
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
static struct mp_filter *af_lavcac3enc_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_lavcac3enc_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->opts->encoder);
|
||||
if (!s->lavc_acodec) {
|
||||
MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
|
||||
return AF_ERROR;
|
||||
MP_ERR(f, "Couldn't find encoder %s.\n", s->opts->encoder);
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec);
|
||||
if (!s->lavc_actx) {
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
MP_ERR(f, "Audio LAVC, couldn't allocate context!\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
|
||||
return AF_ERROR;
|
||||
if (mp_set_avopts(f->log, s->lavc_actx, s->opts->avopts) < 0)
|
||||
goto error;
|
||||
|
||||
// For this one, we require the decoder to expert lists of all supported
|
||||
// parameters. (Not all decoders do that, but the ones we're interested
|
||||
@@ -385,50 +304,85 @@ static int af_open(struct af_instance* af){
|
||||
if (!s->lavc_acodec->sample_fmts ||
|
||||
!s->lavc_acodec->channel_layouts)
|
||||
{
|
||||
MP_ERR(af, "Audio encoder doesn't list supported parameters.\n");
|
||||
return AF_ERROR;
|
||||
MP_ERR(f, "Audio encoder doesn't list supported parameters.\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->input = talloc_zero(s, struct mp_audio);
|
||||
|
||||
if (s->cfg_bit_rate) {
|
||||
if (s->opts->bit_rate) {
|
||||
int i;
|
||||
for (i = 0; i < 19; i++) {
|
||||
if (ac3_bitrate_tab[i] == s->cfg_bit_rate) {
|
||||
if (ac3_bitrate_tab[i] == s->opts->bit_rate) {
|
||||
s->bit_rate = ac3_bitrate_tab[i] * 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= 19) {
|
||||
MP_WARN(af, "unable set unsupported bitrate %d, using default "
|
||||
MP_WARN(f, "unable set unsupported bitrate %d, using default "
|
||||
"bitrate (check manpage to see supported bitrates).\n",
|
||||
s->cfg_bit_rate);
|
||||
s->opts->bit_rate);
|
||||
}
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
const enum AVSampleFormat *lf = s->lavc_acodec->sample_fmts;
|
||||
for (int i = 0; lf && lf[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
int mpfmt = af_from_avformat(lf[i]);
|
||||
if (mpfmt)
|
||||
mp_autoconvert_add_afmt(conv, mpfmt);
|
||||
}
|
||||
|
||||
const uint64_t *lch = s->lavc_acodec->channel_layouts;
|
||||
for (int n = 0; lch && lch[n]; n++) {
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_chmap_from_lavc(&chmap, lch[n]);
|
||||
if (mp_chmap_is_valid(&chmap))
|
||||
mp_autoconvert_add_chmap(conv, &chmap);
|
||||
}
|
||||
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
mp_autoconvert_add_srate(conv, 48000);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
|
||||
struct mp_filter *fs = mp_fixed_aframe_size_create(f, AC3_FRAME_SIZE, true);
|
||||
if (!fs)
|
||||
abort();
|
||||
|
||||
mp_pin_connect(fs->pins[0], conv->f->pins[1]);
|
||||
s->in_pin = fs->pins[1];
|
||||
|
||||
return f;
|
||||
|
||||
error:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct af_ac3enc_s
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
|
||||
const struct af_info af_info_lavcac3enc = {
|
||||
.info = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_ac3enc_s),
|
||||
.priv_defaults = &(const struct af_ac3enc_s){
|
||||
.cfg_add_iec61937_header = 1,
|
||||
.cfg_bit_rate = 640,
|
||||
.cfg_min_channel_num = 3,
|
||||
.cfg_encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", cfg_encoder, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
const struct mp_user_filter_entry af_lavcac3enc = {
|
||||
.desc = {
|
||||
.description = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.add_iec61937_header = 1,
|
||||
.bit_rate = 640,
|
||||
.min_channel_num = 3,
|
||||
.encoder = "ac3",
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", bit_rate, 0, 32, 640,
|
||||
({"auto", 0}, {"default", 0})),
|
||||
OPT_INTRANGE("minch", min_channel_num, 0, 2, 6),
|
||||
OPT_STRING("encoder", encoder, 0),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
{0}
|
||||
},
|
||||
},
|
||||
.create = af_lavcac3enc_create,
|
||||
};
|
||||
@@ -1,413 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* Filter graph creation code taken from FFmpeg ffplay.c (LGPL 2.1 or later)
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/avstring.h>
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
#include <libavutil/rational.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include <libavutil/time.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#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"
|
||||
|
||||
// FFmpeg and Libav have slightly different APIs, just enough to cause us
|
||||
// unnecessary pain. <Expletive deleted.>
|
||||
#if LIBAVFILTER_VERSION_MICRO < 100
|
||||
#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
|
||||
avfilter_graph_parse(graph, filters, inputs, outputs, log_ctx)
|
||||
#define avfilter_graph_send_command(a, b, c, d, e, f, g) -1
|
||||
#else
|
||||
#define graph_parse(graph, filters, inputs, outputs, log_ctx) \
|
||||
avfilter_graph_parse_ptr(graph, filters, &(inputs), &(outputs), log_ctx)
|
||||
#endif
|
||||
|
||||
struct priv {
|
||||
// Single filter bridge, instead of a graph.
|
||||
bool is_bridge;
|
||||
|
||||
AVFilterGraph *graph;
|
||||
AVFilterContext *in;
|
||||
AVFilterContext *out;
|
||||
|
||||
int64_t samples_in;
|
||||
|
||||
AVRational timebase_out;
|
||||
|
||||
bool eof;
|
||||
|
||||
struct mp_tags *metadata;
|
||||
|
||||
// options
|
||||
char *cfg_graph;
|
||||
char **cfg_avopts;
|
||||
char *cfg_filter_name;
|
||||
char **cfg_filter_opts;
|
||||
};
|
||||
|
||||
static void destroy_graph(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
avfilter_graph_free(&p->graph);
|
||||
p->in = p->out = NULL;
|
||||
p->samples_in = 0;
|
||||
p->eof = false;
|
||||
}
|
||||
|
||||
static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
|
||||
{
|
||||
void *tmp = talloc_new(NULL);
|
||||
struct priv *p = af->priv;
|
||||
AVFilterContext *in = NULL, *out = NULL;
|
||||
bool ok = false;
|
||||
|
||||
if (!p->is_bridge && bstr0(p->cfg_graph).len == 0) {
|
||||
MP_FATAL(af, "lavfi: no filter graph set\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy_graph(af);
|
||||
|
||||
AVFilterGraph *graph = avfilter_graph_alloc();
|
||||
if (!graph)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(af->log, graph, p->cfg_avopts) < 0)
|
||||
goto error;
|
||||
|
||||
AVFilterInOut *outputs = avfilter_inout_alloc();
|
||||
AVFilterInOut *inputs = avfilter_inout_alloc();
|
||||
if (!outputs || !inputs)
|
||||
goto error;
|
||||
|
||||
char *src_args = talloc_asprintf(tmp,
|
||||
"sample_rate=%d:sample_fmt=%s:time_base=%d/%d:"
|
||||
"channel_layout=0x%"PRIx64, config->rate,
|
||||
av_get_sample_fmt_name(af_to_avformat(config->format)),
|
||||
1, config->rate, mp_chmap_to_lavc(&config->channels));
|
||||
|
||||
if (avfilter_graph_create_filter(&in, avfilter_get_by_name("abuffer"),
|
||||
"src", src_args, NULL, graph) < 0)
|
||||
goto error;
|
||||
|
||||
if (avfilter_graph_create_filter(&out, avfilter_get_by_name("abuffersink"),
|
||||
"out", NULL, NULL, graph) < 0)
|
||||
goto error;
|
||||
|
||||
if (p->is_bridge) {
|
||||
AVFilterContext *filter = avfilter_graph_alloc_filter(graph,
|
||||
avfilter_get_by_name(p->cfg_filter_name), "filter");
|
||||
if (!filter)
|
||||
goto error;
|
||||
|
||||
if (mp_set_avopts(af->log, filter->priv, p->cfg_filter_opts) < 0)
|
||||
goto error;
|
||||
|
||||
if (avfilter_init_str(filter, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
// Yep, we have to manually link those filters.
|
||||
if (filter->nb_inputs != 1 ||
|
||||
avfilter_pad_get_type(filter->input_pads, 0) != AVMEDIA_TYPE_AUDIO ||
|
||||
filter->nb_outputs != 1 ||
|
||||
avfilter_pad_get_type(filter->output_pads, 0) != AVMEDIA_TYPE_AUDIO)
|
||||
{
|
||||
MP_ERR(af, "The filter is required to have 1 audio input pad and "
|
||||
"1 audio output pad.\n");
|
||||
goto error;
|
||||
}
|
||||
if (avfilter_link(in, 0, filter, 0) < 0 ||
|
||||
avfilter_link(filter, 0, out, 0) < 0)
|
||||
{
|
||||
MP_ERR(af, "Failed to link filter.\n");
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
MP_VERBOSE(af, "lavfi: create graph: '%s'\n", p->cfg_graph);
|
||||
|
||||
outputs->name = av_strdup("in");
|
||||
outputs->filter_ctx = in;
|
||||
|
||||
inputs->name = av_strdup("out");
|
||||
inputs->filter_ctx = out;
|
||||
|
||||
if (graph_parse(graph, p->cfg_graph, inputs, outputs, NULL) < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (avfilter_graph_config(graph, NULL) < 0)
|
||||
goto error;
|
||||
|
||||
p->in = in;
|
||||
p->out = out;
|
||||
p->graph = graph;
|
||||
|
||||
assert(out->nb_inputs == 1);
|
||||
assert(in->nb_outputs == 1);
|
||||
|
||||
ok = true;
|
||||
error:
|
||||
|
||||
if (!ok) {
|
||||
MP_FATAL(af, "Can't configure libavfilter graph.\n");
|
||||
avfilter_graph_free(&graph);
|
||||
}
|
||||
avfilter_inout_free(&inputs);
|
||||
avfilter_inout_free(&outputs);
|
||||
talloc_free(tmp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void reset(struct af_instance *af)
|
||||
{
|
||||
if (!recreate_graph(af, &af->fmt_in))
|
||||
MP_FATAL(af, "Can't recreate libavfilter filter after a seek reset.\n");
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
|
||||
// Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
|
||||
if (in->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
if (!mp_chmap_is_lavc(&in->channels))
|
||||
mp_chmap_reorder_to_lavc(&in->channels); // will always work
|
||||
|
||||
if (!recreate_graph(af, in))
|
||||
return AF_ERROR;
|
||||
|
||||
AVFilterLink *l_out = p->out->inputs[0];
|
||||
|
||||
out->rate = l_out->sample_rate;
|
||||
|
||||
mp_audio_set_format(out, af_from_avformat(l_out->format));
|
||||
|
||||
struct mp_chmap out_cm;
|
||||
mp_chmap_from_lavc(&out_cm, l_out->channel_layout);
|
||||
mp_audio_set_channels(out, &out_cm);
|
||||
|
||||
if (!mp_audio_config_valid(out) || out->channels.num > AV_NUM_DATA_POINTERS)
|
||||
return AF_ERROR;
|
||||
|
||||
p->timebase_out = l_out->time_base;
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_COMMAND: {
|
||||
if (!p->graph)
|
||||
break;
|
||||
char **args = arg;
|
||||
return avfilter_graph_send_command(p->graph, "all",
|
||||
args[0], args[1], &(char){0}, 0, 0)
|
||||
>= 0 ? CONTROL_OK : CONTROL_ERROR;
|
||||
}
|
||||
case AF_CONTROL_GET_METADATA:
|
||||
if (p->metadata) {
|
||||
*(struct mp_tags *)arg = *p->metadata;
|
||||
return CONTROL_OK;
|
||||
}
|
||||
return CONTROL_NA;
|
||||
case AF_CONTROL_RESET:
|
||||
reset(af);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void get_metadata_from_av_frame(struct af_instance *af, AVFrame *frame)
|
||||
{
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
struct priv *p = af->priv;
|
||||
if (!p->metadata)
|
||||
p->metadata = talloc_zero(p, struct mp_tags);
|
||||
|
||||
mp_tags_copy_from_av_dictionary(p->metadata, frame->metadata);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
AVFrame *frame = NULL;
|
||||
|
||||
if (p->eof && data)
|
||||
reset(af);
|
||||
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
if (!data) {
|
||||
if (p->eof)
|
||||
return 0;
|
||||
p->eof = true;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
frame = mp_audio_to_avframe_and_unref(data);
|
||||
data = NULL;
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
// Timebase is 1/sample_rate
|
||||
frame->pts = p->samples_in;
|
||||
p->samples_in += frame->nb_samples;
|
||||
}
|
||||
|
||||
if (av_buffersrc_add_frame(p->in, frame) < 0)
|
||||
goto error;
|
||||
|
||||
av_frame_free(&frame);
|
||||
talloc_free(data);
|
||||
return 0;
|
||||
error:
|
||||
av_frame_free(&frame);
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
if (!p->graph)
|
||||
goto error;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
if (!frame)
|
||||
goto error;
|
||||
|
||||
int err = av_buffersink_get_frame(p->out, frame);
|
||||
if (err == AVERROR(EAGAIN) || err == AVERROR_EOF) {
|
||||
// Not an error situation - no more output buffers in queue.
|
||||
// AVERROR_EOF means we shouldn't even give the filter more
|
||||
// input, but we don't handle that completely correctly.
|
||||
av_frame_free(&frame);
|
||||
p->eof |= err == AVERROR_EOF;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct mp_audio *out = mp_audio_from_avframe(frame);
|
||||
if (!out)
|
||||
goto error;
|
||||
|
||||
mp_audio_copy_config(out, af->data);
|
||||
|
||||
if (frame->pts != AV_NOPTS_VALUE) {
|
||||
double in_time = p->samples_in / (double)af->fmt_in.rate;
|
||||
double out_time = frame->pts * av_q2d(p->timebase_out);
|
||||
// Need pts past the last output sample.
|
||||
out_time += out->samples / (double)out->rate;
|
||||
|
||||
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;
|
||||
error:
|
||||
av_frame_free(&frame);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
destroy_graph(af);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
if (p->is_bridge) {
|
||||
if (!p->cfg_filter_name) {
|
||||
MP_ERR(af, "Filter name not set!\n");
|
||||
return 0;
|
||||
}
|
||||
if (!avfilter_get_by_name(p->cfg_filter_name)) {
|
||||
MP_ERR(af, "libavfilter filter '%s' not found!\n", p->cfg_filter_name);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct af_info af_info_lavfi = {
|
||||
.info = "libavfilter bridge",
|
||||
.name = "lavfi",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
const struct af_info af_info_lavfi_bridge = {
|
||||
.info = "libavfilter bridge (explicit options)",
|
||||
.name = "lavfi-bridge",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv){
|
||||
.is_bridge = true,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("name", cfg_filter_name, M_OPT_MIN, .min = 1),
|
||||
OPT_KEYVALUELIST("opts", cfg_filter_opts, 0),
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
OPT_KEYVALUELIST("o", cfg_avopts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+58
-119
@@ -32,142 +32,81 @@
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/msg.h"
|
||||
#include "filters/f_swresample.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "audio/aconverter.h"
|
||||
#include "options/options.h"
|
||||
|
||||
struct af_resample {
|
||||
int allow_detach;
|
||||
double playback_speed;
|
||||
struct mp_resample_opts opts;
|
||||
struct mp_aconverter *converter;
|
||||
int global_normalize;
|
||||
};
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
static void set_defaults(struct mpv_global *global, void *p)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
struct af_resample *s = p;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio *out = af->data;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_resample_opts *opts = &s->opts;
|
||||
|
||||
if (((out->rate == in->rate) || (out->rate == 0)) &&
|
||||
(out->format == in->format) &&
|
||||
(mp_chmap_equals(&out->channels, &in->channels) || out->nch == 0) &&
|
||||
s->allow_detach && s->playback_speed == 1.0)
|
||||
return AF_DETACH;
|
||||
struct mp_resample_opts *src_opts =
|
||||
mp_get_config_group(s, global, &resample_conf);
|
||||
|
||||
if (out->rate == 0)
|
||||
out->rate = in->rate;
|
||||
s->global_normalize = src_opts->normalize;
|
||||
|
||||
if (mp_chmap_is_empty(&out->channels))
|
||||
mp_audio_set_channels(out, &in->channels);
|
||||
assert(!opts->avopts); // we don't set a default value, so it must be NULL
|
||||
|
||||
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)
|
||||
mp_audio_set_format(out, in->format);
|
||||
*opts = *src_opts;
|
||||
|
||||
int r = ((in->format == orig_in.format) &&
|
||||
mp_chmap_equals(&in->channels, &orig_in.channels))
|
||||
? AF_OK : AF_FALSE;
|
||||
|
||||
if (r == AF_OK) {
|
||||
if (!mp_aconverter_reconfig(s->converter,
|
||||
in->rate, in->format, in->channels,
|
||||
out->rate, out->format, out->channels))
|
||||
r = AF_ERROR;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
mp_aconverter_flush(s->converter);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
talloc_free(s->converter);
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
mp_aconverter_set_speed(s->converter, s->playback_speed);
|
||||
|
||||
af->filter_out(af);
|
||||
|
||||
struct mp_aframe *aframe = mp_audio_to_aframe(in);
|
||||
if (!aframe && in)
|
||||
return -1;
|
||||
talloc_free(in);
|
||||
bool ok = mp_aconverter_write_input(s->converter, aframe);
|
||||
if (!ok)
|
||||
talloc_free(aframe);
|
||||
|
||||
return ok ? 0 : -1;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
bool eof;
|
||||
struct mp_aframe *out = mp_aconverter_read_output(s->converter, &eof);
|
||||
if (out)
|
||||
af_add_output_frame(af, mp_audio_from_aframe(out));
|
||||
talloc_free(out);
|
||||
af->delay = mp_aconverter_get_latency(s->converter);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
s->converter = mp_aconverter_create(af->global, af->log, &s->opts);
|
||||
|
||||
return AF_OK;
|
||||
opts->avopts = NULL;
|
||||
struct m_option dummy = {.type = &m_option_type_keyvalue_list};
|
||||
m_option_copy(&dummy, &opts->avopts, &src_opts->avopts);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct af_resample
|
||||
|
||||
const struct af_info af_info_lavrresample = {
|
||||
.info = "Sample frequency conversion using libavresample",
|
||||
.name = "lavrresample",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_resample),
|
||||
.priv_defaults = &(const struct af_resample) {
|
||||
.opts = MP_RESAMPLE_OPTS_DEF,
|
||||
.playback_speed = 1.0,
|
||||
.allow_detach = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("filter-size", opts.filter_size, 0, 0, 32),
|
||||
OPT_INTRANGE("phase-shift", opts.phase_shift, 0, 0, 30),
|
||||
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_CHOICE("normalize", opts.normalize, 0,
|
||||
({"no", 0}, {"yes", 1}, {"auto", -1})),
|
||||
OPT_KEYVALUELIST("o", opts.avopts, 0),
|
||||
{0}
|
||||
static struct mp_filter *af_lavrresample_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct af_resample *s = options;
|
||||
|
||||
if (s->opts.normalize < 0)
|
||||
s->opts.normalize = s->global_normalize;
|
||||
|
||||
struct mp_swresample *swr = mp_swresample_create(parent, &s->opts);
|
||||
if (!swr)
|
||||
abort();
|
||||
|
||||
MP_WARN(swr->f, "This filter is deprecated! Use the --audio-resample- options"
|
||||
" to customize resampling, or the --af=aresample filter.\n");
|
||||
|
||||
talloc_free(s);
|
||||
return swr->f;
|
||||
}
|
||||
|
||||
const struct mp_user_filter_entry af_lavrresample = {
|
||||
.desc = {
|
||||
.description = "Sample frequency conversion using libavresample",
|
||||
.name = "lavrresample",
|
||||
.priv_size = sizeof(struct af_resample),
|
||||
.priv_defaults = &(const struct af_resample) {
|
||||
.opts = MP_RESAMPLE_OPTS_DEF,
|
||||
.allow_detach = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("filter-size", opts.filter_size, 0, 0, 32),
|
||||
OPT_INTRANGE("phase-shift", opts.phase_shift, 0, 0, 30),
|
||||
OPT_FLAG("linear", opts.linear, 0),
|
||||
OPT_DOUBLE("cutoff", opts.cutoff, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLAG("detach", allow_detach, 0), // does nothing
|
||||
OPT_CHOICE("normalize", opts.normalize, 0,
|
||||
({"no", 0}, {"yes", 1}, {"auto", -1})),
|
||||
OPT_KEYVALUELIST("o", opts.avopts, 0),
|
||||
{0}
|
||||
},
|
||||
.set_defaults = set_defaults,
|
||||
},
|
||||
.create = af_lavrresample_create,
|
||||
};
|
||||
+281
-165
@@ -20,232 +20,348 @@
|
||||
|
||||
#include <rubberband/rubberband-c.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
// command line options
|
||||
struct f_opts {
|
||||
int transients, detector, phase, window,
|
||||
smoothing, formant, pitch, channels;
|
||||
double scale;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
bool sent_final;
|
||||
RubberBandState rubber;
|
||||
double speed;
|
||||
double pitch;
|
||||
struct mp_audio *pending;
|
||||
bool needs_reset;
|
||||
struct mp_aframe *pending;
|
||||
// Estimate how much librubberband has buffered internally.
|
||||
// I could not find a way to do this with the librubberband API.
|
||||
double rubber_delay;
|
||||
// command line options
|
||||
int opt_transients, opt_detector, opt_phase, opt_window,
|
||||
opt_smoothing, opt_formant, opt_pitch, opt_channels;
|
||||
};
|
||||
|
||||
static void update_speed(struct af_instance *af, double new_speed)
|
||||
static void update_speed(struct priv *p, double new_speed)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->speed = new_speed;
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
if (p->rubber)
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
}
|
||||
|
||||
static void update_pitch(struct af_instance *af, double new_pitch)
|
||||
static bool update_pitch(struct priv *p, double new_pitch)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
if (new_pitch < 0.01 || new_pitch > 100.0)
|
||||
return false;
|
||||
|
||||
p->pitch = new_pitch;
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
if (p->rubber)
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
static bool init_rubberband(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
assert(!p->rubber);
|
||||
assert(p->pending);
|
||||
|
||||
in->format = AF_FORMAT_FLOATP;
|
||||
mp_audio_copy_config(out, in);
|
||||
int opts = p->opts->transients | p->opts->detector | p->opts->phase |
|
||||
p->opts->window | p->opts->smoothing | p->opts->formant |
|
||||
p->opts->pitch | p-> opts->channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
int rate = mp_aframe_get_rate(p->pending);
|
||||
int channels = mp_aframe_get_channels(p->pending);
|
||||
if (mp_aframe_get_format(p->pending) != AF_FORMAT_FLOATP)
|
||||
return false;
|
||||
|
||||
int opts = p->opt_transients | p->opt_detector | p->opt_phase |
|
||||
p->opt_window | p->opt_smoothing | p->opt_formant |
|
||||
p->opt_pitch | p-> opt_channels |
|
||||
RubberBandOptionProcessRealTime;
|
||||
|
||||
p->rubber = rubberband_new(in->rate, in->channels.num, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(af, "librubberband initialization failed.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
update_speed(af, p->speed);
|
||||
update_pitch(af, p->pitch);
|
||||
control(af, AF_CONTROL_RESET, NULL);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
p->rubber = rubberband_new(rate, channels, opts, 1.0, 1.0);
|
||||
if (!p->rubber) {
|
||||
MP_FATAL(f, "librubberband initialization failed.\n");
|
||||
return false;
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
update_speed(af, *(double *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
p->pending = NULL;
|
||||
p->rubber_delay = 0;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-pitch")) {
|
||||
char *endptr;
|
||||
double pitch = strtod(args[1], &endptr);
|
||||
if (*endptr || pitch < 0.01 || pitch > 100.0)
|
||||
return CONTROL_ERROR;
|
||||
update_pitch(af, pitch);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
|
||||
mp_aframe_config_copy(p->cur_format, p->pending);
|
||||
|
||||
update_speed(p, p->speed);
|
||||
update_pitch(p, p->pitch);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *data)
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
struct priv *p = f->priv;
|
||||
|
||||
talloc_free(p->pending);
|
||||
p->pending = data;
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
while (rubberband_available(p->rubber) <= 0) {
|
||||
while (!p->rubber || !p->pending || rubberband_available(p->rubber) <= 0) {
|
||||
const float *dummy[MP_NUM_CHANNELS] = {0};
|
||||
const float **in_data = dummy;
|
||||
size_t in_samples = 0;
|
||||
if (p->pending) {
|
||||
if (!p->pending->samples)
|
||||
break;
|
||||
|
||||
// recover from previous EOF
|
||||
if (p->needs_reset) {
|
||||
rubberband_reset(p->rubber);
|
||||
p->rubber_delay = 0;
|
||||
bool eof = false;
|
||||
if (!p->pending || !mp_aframe_get_size(p->pending)) {
|
||||
struct mp_frame frame = mp_pin_out_read(p->in_pin);
|
||||
if (frame.type == MP_FRAME_AUDIO) {
|
||||
TA_FREEP(&p->pending);
|
||||
p->pending = frame.data;
|
||||
} else if (frame.type == MP_FRAME_EOF) {
|
||||
eof = true;
|
||||
} else if (frame.type) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
} else {
|
||||
return; // no new data yet
|
||||
}
|
||||
p->needs_reset = false;
|
||||
}
|
||||
assert(p->pending || eof);
|
||||
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
in_data = (void *)&p->pending->planes;
|
||||
in_samples = MPMIN(p->pending->samples, needs);
|
||||
if (!p->rubber) {
|
||||
if (!p->pending) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
if (!init_rubberband(f))
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (p->needs_reset)
|
||||
break; // previous EOF
|
||||
p->needs_reset = !p->pending; // EOF
|
||||
bool format_change =
|
||||
p->pending && !mp_aframe_config_equals(p->pending, p->cur_format);
|
||||
|
||||
if (p->pending && !format_change) {
|
||||
size_t needs = rubberband_get_samples_required(p->rubber);
|
||||
uint8_t **planes = mp_aframe_get_data_ro(p->pending);
|
||||
int num_planes = mp_aframe_get_planes(p->pending);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
in_data[n] = (void *)planes[n];
|
||||
in_samples = MPMIN(mp_aframe_get_size(p->pending), needs);
|
||||
}
|
||||
|
||||
bool final = format_change || eof;
|
||||
if (!p->sent_final)
|
||||
rubberband_process(p->rubber, in_data, in_samples, final);
|
||||
p->sent_final |= final;
|
||||
|
||||
rubberband_process(p->rubber, in_data, in_samples, p->needs_reset);
|
||||
p->rubber_delay += in_samples;
|
||||
|
||||
if (!p->pending)
|
||||
break;
|
||||
mp_audio_skip_samples(p->pending, in_samples);
|
||||
if (p->pending && !format_change)
|
||||
mp_aframe_skip_samples(p->pending, in_samples);
|
||||
|
||||
if (rubberband_available(p->rubber) > 0) {
|
||||
if (eof)
|
||||
mp_pin_out_repeat_eof(p->in_pin); // drain more next time
|
||||
} else {
|
||||
if (eof) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
rubberband_reset(p->rubber);
|
||||
TA_FREEP(&p->pending);
|
||||
p->sent_final = false;
|
||||
return;
|
||||
} else if (format_change) {
|
||||
// go on with proper reinit on the next iteration
|
||||
rubberband_delete(p->rubber);
|
||||
p->sent_final = false;
|
||||
p->rubber = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(p->pending);
|
||||
|
||||
int out_samples = rubberband_available(p->rubber);
|
||||
if (out_samples > 0) {
|
||||
struct mp_audio *out =
|
||||
mp_audio_pool_get(af->out_pool, af->data, out_samples);
|
||||
if (!out)
|
||||
return -1;
|
||||
if (p->pending)
|
||||
mp_audio_copy_config(out, p->pending);
|
||||
struct mp_aframe *out = mp_aframe_new_ref(p->cur_format);
|
||||
if (mp_aframe_pool_allocate(p->out_pool, out, out_samples) < 0) {
|
||||
talloc_free(out);
|
||||
goto error;
|
||||
}
|
||||
|
||||
float **out_data = (void *)&out->planes;
|
||||
out->samples = rubberband_retrieve(p->rubber, out_data, out->samples);
|
||||
p->rubber_delay -= out->samples * p->speed;
|
||||
mp_aframe_copy_attributes(out, p->pending);
|
||||
|
||||
af_add_output_frame(af, out);
|
||||
float *out_data[MP_NUM_CHANNELS] = {0};
|
||||
uint8_t **planes = mp_aframe_get_data_rw(out);
|
||||
assert(planes);
|
||||
int num_planes = mp_aframe_get_planes(out);
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
out_data[n] = (void *)planes[n];
|
||||
|
||||
out_samples = rubberband_retrieve(p->rubber, out_data, out_samples);
|
||||
|
||||
if (!out_samples) {
|
||||
mp_filter_internal_mark_progress(f); // unexpected, just try again
|
||||
talloc_free(out);
|
||||
return;
|
||||
}
|
||||
|
||||
mp_aframe_set_size(out, out_samples);
|
||||
|
||||
p->rubber_delay -= out_samples * p->speed;
|
||||
|
||||
double pts = mp_aframe_get_pts(p->pending);
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
// Note: rubberband_get_latency() does not do what you'd expect.
|
||||
double delay = p->rubber_delay / mp_aframe_get_effective_rate(out);
|
||||
mp_aframe_set_pts(out, pts - delay);
|
||||
}
|
||||
|
||||
mp_aframe_mul_speed(out, p->speed);
|
||||
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
}
|
||||
|
||||
int delay_samples = p->rubber_delay;
|
||||
if (p->pending)
|
||||
delay_samples += p->pending->samples;
|
||||
af->delay = delay_samples / (af->data->rate * p->speed);
|
||||
|
||||
return 0;
|
||||
return;
|
||||
error:
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
struct priv *p = f->priv;
|
||||
|
||||
switch (cmd->type) {
|
||||
case MP_FILTER_COMMAND_TEXT: {
|
||||
char *endptr = NULL;
|
||||
double pitch = p->pitch;
|
||||
if (!strcmp(cmd->cmd, "set-pitch")) {
|
||||
pitch = strtod(cmd->arg, &endptr);
|
||||
if (*endptr)
|
||||
return false;
|
||||
return update_pitch(p, pitch);
|
||||
} else if (!strcmp(cmd->cmd, "multiply-pitch")) {
|
||||
double mult = strtod(cmd->arg, &endptr);
|
||||
if (*endptr || mult <= 0)
|
||||
return false;
|
||||
pitch *= mult;
|
||||
return update_pitch(p, pitch);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case MP_FILTER_COMMAND_SET_SPEED:
|
||||
update_speed(p, cmd->speed);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_reset(p->rubber);
|
||||
p->sent_final = false;
|
||||
TA_FREEP(&p->pending);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (p->rubber)
|
||||
rubberband_delete(p->rubber);
|
||||
talloc_free(p->pending);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
static const struct mp_filter_info af_rubberband_filter = {
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_rubberband_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
af->uninit = uninit;
|
||||
return AF_OK;
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_rubberband_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->opts = talloc_steal(p, options);
|
||||
p->speed = 1.0;
|
||||
p->pitch = p->opts->scale;
|
||||
p->cur_format = talloc_steal(p, mp_aframe_create());
|
||||
p->out_pool = mp_aframe_pool_create(p);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOATP);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
p->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
const struct af_info af_info_rubberband = {
|
||||
.info = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.speed = 1.0,
|
||||
.pitch = 1.0,
|
||||
.opt_pitch = RubberBandOptionPitchHighConsistency,
|
||||
.opt_transients = RubberBandOptionTransientsMixed,
|
||||
.opt_formant = RubberBandOptionFormantPreserved,
|
||||
.opt_channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", opt_transients, 0,
|
||||
({"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})),
|
||||
OPT_CHOICE("detector", opt_detector, 0,
|
||||
({"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})),
|
||||
OPT_CHOICE("phase", opt_phase, 0,
|
||||
({"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})),
|
||||
OPT_CHOICE("window", opt_window, 0,
|
||||
({"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})),
|
||||
OPT_CHOICE("smoothing", opt_smoothing, 0,
|
||||
({"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})),
|
||||
OPT_CHOICE("formant", opt_formant, 0,
|
||||
({"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})),
|
||||
OPT_CHOICE("pitch", opt_pitch, 0,
|
||||
({"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})),
|
||||
OPT_CHOICE("channels", opt_channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", pitch, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
|
||||
const struct mp_user_filter_entry af_rubberband = {
|
||||
.desc = {
|
||||
.description = "Pitch conversion with librubberband",
|
||||
.name = "rubberband",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.scale = 1.0,
|
||||
.pitch = RubberBandOptionPitchHighConsistency,
|
||||
.transients = RubberBandOptionTransientsMixed,
|
||||
.formant = RubberBandOptionFormantPreserved,
|
||||
.channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", transients, 0,
|
||||
({"crisp", RubberBandOptionTransientsCrisp},
|
||||
{"mixed", RubberBandOptionTransientsMixed},
|
||||
{"smooth", RubberBandOptionTransientsSmooth})),
|
||||
OPT_CHOICE("detector", detector, 0,
|
||||
({"compound", RubberBandOptionDetectorCompound},
|
||||
{"percussive", RubberBandOptionDetectorPercussive},
|
||||
{"soft", RubberBandOptionDetectorSoft})),
|
||||
OPT_CHOICE("phase", phase, 0,
|
||||
({"laminar", RubberBandOptionPhaseLaminar},
|
||||
{"independent", RubberBandOptionPhaseIndependent})),
|
||||
OPT_CHOICE("window", window, 0,
|
||||
({"standard", RubberBandOptionWindowStandard},
|
||||
{"short", RubberBandOptionWindowShort},
|
||||
{"long", RubberBandOptionWindowLong})),
|
||||
OPT_CHOICE("smoothing", smoothing, 0,
|
||||
({"off", RubberBandOptionSmoothingOff},
|
||||
{"on", RubberBandOptionSmoothingOn})),
|
||||
OPT_CHOICE("formant", formant, 0,
|
||||
({"shifted", RubberBandOptionFormantShifted},
|
||||
{"preserved", RubberBandOptionFormantPreserved})),
|
||||
OPT_CHOICE("pitch", pitch, 0,
|
||||
({"quality", RubberBandOptionPitchHighQuality},
|
||||
{"speed", RubberBandOptionPitchHighSpeed},
|
||||
{"consistency", RubberBandOptionPitchHighConsistency})),
|
||||
OPT_CHOICE("channels", channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", scale, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
},
|
||||
},
|
||||
.create = af_rubberband_create,
|
||||
};
|
||||
+379
-249
@@ -35,14 +35,33 @@
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include "af.h"
|
||||
#include "filters/f_autoconvert.h"
|
||||
#include "filters/filter_internal.h"
|
||||
#include "filters/user_filters.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_scaletempo_s
|
||||
{
|
||||
struct f_opts {
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float ms_search;
|
||||
float percent_overlap;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
struct f_opts *opts;
|
||||
|
||||
struct mp_pin *in_pin;
|
||||
struct mp_aframe *cur_format;
|
||||
struct mp_aframe_pool *out_pool;
|
||||
double current_pts;
|
||||
struct mp_aframe *in;
|
||||
|
||||
// stride
|
||||
float scale;
|
||||
float speed;
|
||||
@@ -62,29 +81,23 @@ typedef struct af_scaletempo_s
|
||||
int bytes_standing;
|
||||
void *buf_overlap;
|
||||
void *table_blend;
|
||||
void (*output_overlap)(struct af_scaletempo_s *s, void *out_buf,
|
||||
void (*output_overlap)(struct priv *s, void *out_buf,
|
||||
int bytes_off);
|
||||
// best overlap
|
||||
int frames_search;
|
||||
int num_channels;
|
||||
void *buf_pre_corr;
|
||||
void *table_window;
|
||||
int (*best_overlap_offset)(struct af_scaletempo_s *s);
|
||||
// command line
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float percent_overlap;
|
||||
float ms_search;
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
int speed_opt;
|
||||
} af_scaletempo_t;
|
||||
int (*best_overlap_offset)(struct priv *s);
|
||||
};
|
||||
|
||||
static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
|
||||
static bool reinit(struct mp_filter *f);
|
||||
|
||||
// Return whether it got enough data for filtering.
|
||||
static bool fill_queue(struct priv *s)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
int bytes_in = (data ? mp_audio_psize(data) : 0) - offset;
|
||||
int offset_unchanged = offset;
|
||||
int bytes_in = s->in ? mp_aframe_get_size(s->in) * s->bytes_per_frame : 0;
|
||||
int offset = 0;
|
||||
|
||||
if (s->bytes_to_slide > 0) {
|
||||
if (s->bytes_to_slide < s->bytes_queued) {
|
||||
@@ -103,21 +116,27 @@ static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_in > 0) {
|
||||
int bytes_copy = MPMIN(s->bytes_queue - s->bytes_queued, bytes_in);
|
||||
assert(bytes_copy >= 0);
|
||||
memcpy(s->buf_queue + s->bytes_queued,
|
||||
(int8_t *)data->planes[0] + offset, bytes_copy);
|
||||
int bytes_needed = s->bytes_queue - s->bytes_queued;
|
||||
assert(bytes_needed >= 0);
|
||||
|
||||
int bytes_copy = MPMIN(bytes_needed, bytes_in);
|
||||
if (bytes_copy > 0) {
|
||||
uint8_t **planes = mp_aframe_get_data_ro(s->in);
|
||||
memcpy(s->buf_queue + s->bytes_queued, planes[0] + offset, bytes_copy);
|
||||
s->bytes_queued += bytes_copy;
|
||||
offset += bytes_copy;
|
||||
bytes_needed -= bytes_copy;
|
||||
}
|
||||
|
||||
return offset - offset_unchanged;
|
||||
if (s->in)
|
||||
mp_aframe_skip_samples(s->in, offset / s->bytes_per_frame);
|
||||
|
||||
return bytes_needed == 0;
|
||||
}
|
||||
|
||||
#define UNROLL_PADDING (4 * 4)
|
||||
|
||||
static int best_overlap_offset_float(af_scaletempo_t *s)
|
||||
static int best_overlap_offset_float(struct priv *s)
|
||||
{
|
||||
float best_corr = INT_MIN;
|
||||
int best_off = 0;
|
||||
@@ -146,7 +165,7 @@ static int best_overlap_offset_float(af_scaletempo_t *s)
|
||||
return best_off * 4 * s->num_channels;
|
||||
}
|
||||
|
||||
static int best_overlap_offset_s16(af_scaletempo_t *s)
|
||||
static int best_overlap_offset_s16(struct priv *s)
|
||||
{
|
||||
int64_t best_corr = INT64_MIN;
|
||||
int best_off = 0;
|
||||
@@ -183,7 +202,7 @@ static int best_overlap_offset_s16(af_scaletempo_t *s)
|
||||
return best_off * 2 * s->num_channels;
|
||||
}
|
||||
|
||||
static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
|
||||
static void output_overlap_float(struct priv *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
float *pout = buf_out;
|
||||
@@ -196,7 +215,7 @@ static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
|
||||
static void output_overlap_s16(struct priv *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
int16_t *pout = buf_out;
|
||||
@@ -209,29 +228,77 @@ static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
|
||||
}
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
struct priv *s = f->priv;
|
||||
|
||||
if (s->scale == 1.0) {
|
||||
af->delay = 0;
|
||||
af_add_output_frame(af, data);
|
||||
return 0;
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
struct mp_aframe *out = NULL;
|
||||
|
||||
bool drain = false;
|
||||
bool is_eof = false;
|
||||
if (!s->in) {
|
||||
struct mp_frame frame = mp_pin_out_read(s->in_pin);
|
||||
if (!frame.type)
|
||||
return; // no input yet
|
||||
if (frame.type != MP_FRAME_AUDIO && frame.type != MP_FRAME_EOF) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
goto error;
|
||||
}
|
||||
|
||||
s->in = frame.type == MP_FRAME_AUDIO ? frame.data : NULL;
|
||||
is_eof = drain = !s->in;
|
||||
|
||||
// EOF before it was even initialized once.
|
||||
if (is_eof && !mp_aframe_config_is_valid(s->cur_format)) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->in && !mp_aframe_config_equals(s->in, s->cur_format)) {
|
||||
if (s->bytes_queued) {
|
||||
// Drain remaining data before executing the format change.
|
||||
MP_VERBOSE(f, "draining\n");
|
||||
mp_pin_out_unread(s->in_pin, frame);
|
||||
s->in = NULL;
|
||||
drain = true;
|
||||
} else {
|
||||
if (!reinit(f)) {
|
||||
MP_ERR(f, "initialization failed\n");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s->in)
|
||||
s->current_pts = mp_aframe_end_pts(s->in);
|
||||
}
|
||||
|
||||
int in_samples = data ? data->samples : 0;
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, af->data,
|
||||
((int)(in_samples / s->frames_stride_scaled) + 1) * s->frames_stride);
|
||||
if (!out) {
|
||||
talloc_free(data);
|
||||
return -1;
|
||||
if (!fill_queue(s) && !drain) {
|
||||
TA_FREEP(&s->in);
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
return;
|
||||
}
|
||||
if (data)
|
||||
mp_audio_copy_attributes(out, data);
|
||||
|
||||
int offset_in = fill_queue(af, data, 0);
|
||||
int8_t *pout = out->planes[0];
|
||||
while (s->bytes_queued >= s->bytes_queue) {
|
||||
int max_out_samples = s->bytes_stride / s->bytes_per_frame;
|
||||
if (drain)
|
||||
max_out_samples += s->bytes_queued;
|
||||
|
||||
out = mp_aframe_new_ref(s->cur_format);
|
||||
if (mp_aframe_pool_allocate(s->out_pool, out, max_out_samples) < 0)
|
||||
goto error;
|
||||
|
||||
if (s->in)
|
||||
mp_aframe_copy_attributes(out, s->in);
|
||||
|
||||
uint8_t **out_planes = mp_aframe_get_data_rw(out);
|
||||
if (!out_planes)
|
||||
goto error;
|
||||
int8_t *pout = out_planes[0];
|
||||
int out_offset = 0;
|
||||
if (s->bytes_queued >= s->bytes_queue) {
|
||||
int ti;
|
||||
float tf;
|
||||
int bytes_off = 0;
|
||||
@@ -240,12 +307,12 @@ static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
if (s->output_overlap) {
|
||||
if (s->best_overlap_offset)
|
||||
bytes_off = s->best_overlap_offset(s);
|
||||
s->output_overlap(s, pout, bytes_off);
|
||||
s->output_overlap(s, pout + out_offset, bytes_off);
|
||||
}
|
||||
memcpy(pout + s->bytes_overlap,
|
||||
memcpy(pout + out_offset + s->bytes_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_overlap,
|
||||
s->bytes_standing);
|
||||
pout += s->bytes_stride;
|
||||
out_offset += s->bytes_stride;
|
||||
|
||||
// input stride
|
||||
memcpy(s->buf_overlap,
|
||||
@@ -255,240 +322,303 @@ static int filter(struct af_instance *af, struct mp_audio *data)
|
||||
ti = (int)tf;
|
||||
s->frames_stride_error = tf - ti;
|
||||
s->bytes_to_slide = ti * s->bytes_per_frame;
|
||||
|
||||
offset_in += fill_queue(af, data, offset_in);
|
||||
}
|
||||
// Drain remaining buffered data.
|
||||
if (drain && s->bytes_queued) {
|
||||
memcpy(pout + out_offset, s->buf_queue, s->bytes_queued);
|
||||
out_offset += s->bytes_queued;
|
||||
s->bytes_queued = 0;
|
||||
}
|
||||
mp_aframe_set_size(out, out_offset / s->bytes_per_frame);
|
||||
|
||||
// This filter can have a negative delay when scale > 1:
|
||||
// output corresponding to some length of input can be decided and written
|
||||
// after receiving only a part of that input.
|
||||
af->delay = (s->bytes_queued - s->bytes_to_slide) / s->scale
|
||||
/ out->sstride / out->rate;
|
||||
float delay = (out_offset * s->speed + s->bytes_queued - s->bytes_to_slide) /
|
||||
s->bytes_per_frame / mp_aframe_get_effective_rate(out)
|
||||
+ (s->in ? mp_aframe_duration(s->in) : 0);
|
||||
|
||||
out->samples = (pout - (int8_t *)out->planes[0]) / out->sstride;
|
||||
talloc_free(data);
|
||||
if (out->samples) {
|
||||
af_add_output_frame(af, out);
|
||||
} else {
|
||||
talloc_free(out);
|
||||
if (s->current_pts != MP_NOPTS_VALUE)
|
||||
mp_aframe_set_pts(out, s->current_pts - delay);
|
||||
|
||||
mp_aframe_mul_speed(out, s->speed);
|
||||
|
||||
if (!mp_aframe_get_size(out))
|
||||
TA_FREEP(&out);
|
||||
|
||||
if (is_eof && out) {
|
||||
mp_pin_out_repeat_eof(s->in_pin);
|
||||
} else if (is_eof && !out) {
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
} else if (!is_eof && !out) {
|
||||
mp_pin_out_request_data_next(s->in_pin);
|
||||
}
|
||||
return 0;
|
||||
|
||||
if (out)
|
||||
mp_pin_in_write(f->ppins[1], MAKE_FRAME(MP_FRAME_AUDIO, out));
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
TA_FREEP(&s->in);
|
||||
talloc_free(out);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
|
||||
static void update_speed(struct af_instance *af, float speed)
|
||||
static void update_speed(struct priv *s, float speed)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
s->speed = speed;
|
||||
|
||||
double factor = (s->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->scale_nominal;
|
||||
double factor = (s->opts->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed;
|
||||
s->scale = factor * s->opts->scale_nominal;
|
||||
|
||||
s->frames_stride_scaled = s->scale * s->frames_stride;
|
||||
s->frames_stride_error = MPMIN(s->frames_stride_error, s->frames_stride_scaled);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
static bool reinit(struct mp_filter *f)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *data = (struct mp_audio *)arg;
|
||||
float srate = data->rate / 1000.0;
|
||||
int nch = data->nch;
|
||||
int use_int = 0;
|
||||
struct priv *s = f->priv;
|
||||
|
||||
mp_audio_force_interleaved_format(data);
|
||||
mp_audio_copy_config(af->data, data);
|
||||
mp_aframe_reset(s->cur_format);
|
||||
|
||||
if (data->format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
int bps = af->data->bps;
|
||||
float srate = mp_aframe_get_rate(s->in) / 1000.0;
|
||||
int nch = mp_aframe_get_channels(s->in);
|
||||
int format = mp_aframe_get_format(s->in);
|
||||
|
||||
s->frames_stride = srate * s->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
af->delay = 0;
|
||||
|
||||
update_speed(af, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(af, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
return af_test_output(af, (struct mp_audio *)arg);
|
||||
int use_int = 0;
|
||||
if (format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else if (format != AF_FORMAT_FLOAT) {
|
||||
return false;
|
||||
}
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
double speed = *(double *)arg;
|
||||
if (s->speed_opt & SCALE_TEMPO) {
|
||||
if (s->speed_opt & SCALE_PITCH)
|
||||
break;
|
||||
update_speed(af, speed);
|
||||
} else if (s->speed_opt & SCALE_PITCH) {
|
||||
update_speed(af, speed);
|
||||
break; // do not signal OK
|
||||
int bps = use_int ? 2 : 4;
|
||||
|
||||
s->frames_stride = srate * s->opts->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
|
||||
update_speed(s, s->speed);
|
||||
|
||||
int frames_overlap = s->frames_stride * s->opts->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->opts->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(f, "Out of memory\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(f, ""
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
mp_aframe_config_copy(s->cur_format, s->in);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance *af)
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
struct priv *s = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
|
||||
if (s->opts->speed_opt & SCALE_TEMPO) {
|
||||
if (s->opts->speed_opt & SCALE_PITCH)
|
||||
return false;
|
||||
update_speed(s, cmd->speed);
|
||||
return true;
|
||||
} else if (s->opts->speed_opt & SCALE_PITCH) {
|
||||
update_speed(s, cmd->speed);
|
||||
return false; // do not signal OK
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
|
||||
s->current_pts = MP_NOPTS_VALUE;
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
TA_FREEP(&s->in);
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *s = f->priv;
|
||||
free(s->buf_queue);
|
||||
free(s->buf_overlap);
|
||||
free(s->buf_pre_corr);
|
||||
free(s->table_blend);
|
||||
free(s->table_window);
|
||||
TA_FREEP(&s->in);
|
||||
mp_filter_free_children(f);
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter_frame = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_scaletempo_t
|
||||
|
||||
const struct af_info af_info_scaletempo = {
|
||||
.info = "Scale audio tempo while maintaining pitch",
|
||||
static const struct mp_filter_info af_scaletempo_filter = {
|
||||
.name = "scaletempo",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_scaletempo_t),
|
||||
.priv_defaults = &(const af_scaletempo_t) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.speed = 1.0,
|
||||
.scale_nominal = 1.0,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
static struct mp_filter *af_scaletempo_create(struct mp_filter *parent,
|
||||
void *options)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &af_scaletempo_filter);
|
||||
if (!f) {
|
||||
talloc_free(options);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *s = f->priv;
|
||||
s->opts = talloc_steal(s, options);
|
||||
s->speed = 1.0;
|
||||
s->cur_format = talloc_steal(s, mp_aframe_create());
|
||||
s->out_pool = mp_aframe_pool_create(s);
|
||||
|
||||
struct mp_autoconvert *conv = mp_autoconvert_create(f);
|
||||
if (!conv)
|
||||
abort();
|
||||
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_S16);
|
||||
mp_autoconvert_add_afmt(conv, AF_FORMAT_FLOAT);
|
||||
|
||||
mp_pin_connect(conv->f->pins[0], f->ppins[0]);
|
||||
s->in_pin = conv->f->pins[1];
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct f_opts
|
||||
|
||||
const struct mp_user_filter_entry af_scaletempo = {
|
||||
.desc = {
|
||||
.description = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.priv_size = sizeof(OPT_BASE_STRUCT),
|
||||
.priv_defaults = &(const OPT_BASE_STRUCT) {
|
||||
.ms_stride = 60,
|
||||
.percent_overlap = .20,
|
||||
.ms_search = 14,
|
||||
.speed_opt = SCALE_TEMPO,
|
||||
.scale_nominal = 1.0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("speed", speed_opt, 0,
|
||||
({"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})),
|
||||
{0}
|
||||
},
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("scale", scale_nominal, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("stride", ms_stride, M_OPT_MIN, .min = 0.01),
|
||||
OPT_FLOAT("overlap", percent_overlap, M_OPT_RANGE, .min = 0, .max = 1),
|
||||
OPT_FLOAT("search", ms_search, M_OPT_MIN, .min = 0),
|
||||
OPT_CHOICE("speed", speed_opt, 0,
|
||||
({"pitch", SCALE_PITCH},
|
||||
{"tempo", SCALE_TEMPO},
|
||||
{"none", 0},
|
||||
{"both", SCALE_TEMPO | SCALE_PITCH})),
|
||||
{0}
|
||||
},
|
||||
.create = af_scaletempo_create,
|
||||
};
|
||||
@@ -1,72 +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 <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
/* Convert from ms to sample time */
|
||||
int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
|
||||
{
|
||||
int i = 0;
|
||||
// Sanity check
|
||||
if(!in || !out)
|
||||
return AF_ERROR;
|
||||
|
||||
for(i=0;i<n;i++)
|
||||
out[i]=(int)((float)rate * MPCLAMP(in[i],mi,ma)/1000.0);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* test if output format matches
|
||||
* af: audio filter
|
||||
* out: needed format, will be overwritten by available
|
||||
* format if they do not match
|
||||
* returns: AF_FALSE if formats do not match, AF_OK if they match
|
||||
*
|
||||
* compares the format, rate and nch values of af->data with out
|
||||
* Note: logically, *out=*af->data always happens, because out contains the
|
||||
* format only, no actual audio data or memory allocations. *out always
|
||||
* contains the parameters from af->data after the function returns.
|
||||
*/
|
||||
int af_test_output(struct af_instance* af, struct mp_audio* out)
|
||||
{
|
||||
if((af->data->format != out->format) ||
|
||||
(af->data->bps != out->bps) ||
|
||||
(af->data->rate != out->rate) ||
|
||||
!mp_chmap_equals(&af->data->channels, &out->channels)){
|
||||
*out = *af->data;
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* Soft clipping, the sound of a dream, thanks to Jon Wattes
|
||||
post to Musicdsp.org */
|
||||
float af_softclip(float a)
|
||||
{
|
||||
if (a >= M_PI/2)
|
||||
return 1.0;
|
||||
else if (a <= -M_PI/2)
|
||||
return -1.0;
|
||||
else
|
||||
return sin(a);
|
||||
}
|
||||
+4
-3
@@ -154,7 +154,7 @@ void af_fill_silence(void *dst, size_t bytes, int format)
|
||||
// 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.
|
||||
static int af_format_conversion_score(int dst_format, int src_format)
|
||||
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;
|
||||
@@ -209,10 +209,11 @@ static int cmp_entry(const void *a, const void *b)
|
||||
// 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])
|
||||
// out_formats must be an int[AF_FORMAT_COUNT + 1] array.
|
||||
void af_get_best_sample_formats(int src_format, int *out_formats)
|
||||
{
|
||||
int num = 0;
|
||||
struct entry e[AF_FORMAT_COUNT];
|
||||
struct entry e[AF_FORMAT_COUNT + 1];
|
||||
for (int fmt = 1; fmt < AF_FORMAT_COUNT; fmt++) {
|
||||
int score = af_format_conversion_score(fmt, src_format);
|
||||
if (score > INT_MIN)
|
||||
|
||||
+2
-1
@@ -69,7 +69,8 @@ int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int sampler
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format);
|
||||
|
||||
void af_get_best_sample_formats(int src_format, int out_formats[AF_FORMAT_COUNT]);
|
||||
void af_get_best_sample_formats(int src_format, int *out_formats);
|
||||
int af_format_conversion_score(int dst_format, int src_format);
|
||||
int af_select_best_samplerate(int src_sampelrate, const int *available);
|
||||
|
||||
int af_format_sample_alignment(int format);
|
||||
|
||||
+3
-4
@@ -82,7 +82,7 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
#if HAVE_OPENSLES
|
||||
&audio_out_opensles,
|
||||
#endif
|
||||
#if HAVE_SDL1 || HAVE_SDL2
|
||||
#if HAVE_SDL2
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
#if HAVE_SNDIO
|
||||
@@ -93,9 +93,7 @@ static const struct ao_driver * const audio_out_drivers[] = {
|
||||
&audio_out_coreaudio_exclusive,
|
||||
#endif
|
||||
&audio_out_pcm,
|
||||
#if HAVE_ENCODING
|
||||
&audio_out_lavc,
|
||||
#endif
|
||||
#if HAVE_RSOUND
|
||||
&audio_out_rsound,
|
||||
#endif
|
||||
@@ -232,6 +230,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
if (ao->device_buffer)
|
||||
MP_VERBOSE(ao, "device buffer: %d samples.\n", ao->device_buffer);
|
||||
ao->buffer = MPMAX(ao->device_buffer, ao->def_buffer * ao->samplerate);
|
||||
ao->buffer = MPMAX(ao->buffer, 1);
|
||||
|
||||
int align = af_format_sample_alignment(ao->format);
|
||||
ao->buffer = (ao->buffer + align - 1) / align * align;
|
||||
@@ -421,7 +420,7 @@ int ao_query_and_reset_events(struct ao *ao, int events)
|
||||
return atomic_fetch_and(&ao->events_, ~(unsigned)events) & events;
|
||||
}
|
||||
|
||||
static void ao_add_events(struct ao *ao, int events)
|
||||
void ao_add_events(struct ao *ao, int events)
|
||||
{
|
||||
atomic_fetch_or(&ao->events_, events);
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
|
||||
@@ -48,6 +48,7 @@ enum aocontrol {
|
||||
enum {
|
||||
AO_EVENT_RELOAD = 1,
|
||||
AO_EVENT_HOTPLUG = 2,
|
||||
AO_EVENT_INITIAL_UNBLOCK = 4,
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -104,6 +105,8 @@ void ao_resume(struct ao *ao);
|
||||
void ao_drain(struct ao *ao);
|
||||
bool ao_eof_reached(struct ao *ao);
|
||||
int ao_query_and_reset_events(struct ao *ao, int events);
|
||||
void ao_add_events(struct ao *ao, int events);
|
||||
void ao_unblock(struct ao *ao);
|
||||
void ao_request_reload(struct ao *ao);
|
||||
void ao_hotplug_event(struct ao *ao);
|
||||
|
||||
|
||||
+47
-33
@@ -30,6 +30,7 @@
|
||||
#include <sys/time.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -42,8 +43,11 @@
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
#define HAVE_CHMAP_API \
|
||||
(defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16))
|
||||
#if defined(SND_CHMAP_API_VERSION) && SND_CHMAP_API_VERSION >= (1 << 16)
|
||||
#define HAVE_CHMAP_API 1
|
||||
#else
|
||||
#define HAVE_CHMAP_API 0
|
||||
#endif
|
||||
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
@@ -56,6 +60,8 @@ struct ao_alsa_opts {
|
||||
int resample;
|
||||
int ni;
|
||||
int ignore_chmap;
|
||||
int buffer_time;
|
||||
int frags;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct ao_alsa_opts
|
||||
@@ -67,6 +73,8 @@ static const struct m_sub_options ao_alsa_conf = {
|
||||
OPT_INTRANGE("alsa-mixer-index", mixer_index, 0, 0, 99),
|
||||
OPT_FLAG("alsa-non-interleaved", ni, 0),
|
||||
OPT_FLAG("alsa-ignore-chmap", ignore_chmap, 0),
|
||||
OPT_INTRANGE("alsa-buffer-time", buffer_time, 0, 0, INT_MAX),
|
||||
OPT_INTRANGE("alsa-periods", frags, 0, 0, INT_MAX),
|
||||
{0}
|
||||
},
|
||||
.defaults = &(const struct ao_alsa_opts) {
|
||||
@@ -74,6 +82,8 @@ static const struct m_sub_options ao_alsa_conf = {
|
||||
.mixer_name = "Master",
|
||||
.mixer_index = 0,
|
||||
.ni = 0,
|
||||
.buffer_time = 100000,
|
||||
.frags = 4,
|
||||
},
|
||||
.size = sizeof(struct ao_alsa_opts),
|
||||
};
|
||||
@@ -96,9 +106,6 @@ struct priv {
|
||||
struct ao_alsa_opts *opts;
|
||||
};
|
||||
|
||||
#define BUFFER_TIME 250000 // 250ms
|
||||
#define FRAGCOUNT 16
|
||||
|
||||
#define CHECK_ALSA_ERROR(message) \
|
||||
do { \
|
||||
if (err < 0) { \
|
||||
@@ -518,7 +525,7 @@ static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels
|
||||
|
||||
#endif /* else HAVE_CHMAP_API */
|
||||
|
||||
static void dump_hw_params(struct ao *ao, int msglevel, const char *msg,
|
||||
static void dump_hw_params(struct ao *ao, const char *msg,
|
||||
snd_pcm_hw_params_t *hw_params)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
@@ -530,7 +537,7 @@ static void dump_hw_params(struct ao *ao, int msglevel, const char *msg,
|
||||
char *tmp = NULL;
|
||||
size_t tmp_s = snd_output_buffer_string(p->output, &tmp);
|
||||
if (tmp)
|
||||
mp_msg(ao->log, msglevel, "%s---\n%.*s---\n", msg, (int)tmp_s, tmp);
|
||||
mp_msg(ao->log, MSGL_DEBUG, "%s---\n%.*s---\n", msg, (int)tmp_s, tmp);
|
||||
snd_output_flush(p->output);
|
||||
}
|
||||
|
||||
@@ -645,6 +652,7 @@ alsa_error: ;
|
||||
static int init_device(struct ao *ao, int mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct ao_alsa_opts *opts = p->opts;
|
||||
int ret = INIT_DEVICE_ERR_GENERIC;
|
||||
char *tmp;
|
||||
size_t tmp_s;
|
||||
@@ -678,7 +686,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Start HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, "Start HW params:\n", alsa_hwparams);
|
||||
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
@@ -686,7 +694,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
}
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after rate:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, "HW params after rate:\n", alsa_hwparams);
|
||||
|
||||
snd_pcm_access_t access = af_fmt_is_planar(ao->format)
|
||||
? SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
@@ -698,10 +706,10 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
|
||||
}
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after access:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, "HW params after access:\n", alsa_hwparams);
|
||||
|
||||
bool found_format = false;
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
int try_formats[AF_FORMAT_COUNT + 1];
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n] && !found_format; n++) {
|
||||
int mp_format = try_formats[n];
|
||||
@@ -741,7 +749,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
CHECK_ALSA_ERROR("Unable to set format");
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after format:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, "HW params after format:\n", alsa_hwparams);
|
||||
|
||||
// Stereo, or mono if input is 1 channel.
|
||||
struct mp_chmap reduced;
|
||||
@@ -770,7 +778,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_channels_near
|
||||
(p->alsa, alsa_hwparams, &num_channels);
|
||||
CHECK_ALSA_ERROR("Unable to set channels");
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after channels:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, "HW params after channels:\n", alsa_hwparams);
|
||||
|
||||
if (num_channels > MP_NUM_CHANNELS) {
|
||||
MP_FATAL(ao, "Too many audio channels (%d).\n", num_channels);
|
||||
@@ -780,32 +788,35 @@ static int init_device(struct ao *ao, int mode)
|
||||
err = snd_pcm_hw_params_set_rate_near
|
||||
(p->alsa, alsa_hwparams, &ao->samplerate, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to set samplerate-2");
|
||||
dump_hw_params(ao, MSGL_DEBUG, "HW params after rate-2:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, "HW params after rate-2:\n", alsa_hwparams);
|
||||
|
||||
snd_pcm_hw_params_t *hwparams_backup;
|
||||
snd_pcm_hw_params_alloca(&hwparams_backup);
|
||||
snd_pcm_hw_params_copy(hwparams_backup, alsa_hwparams);
|
||||
|
||||
// Cargo-culted buffer settings; might still be useful for PulseAudio.
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){BUFFER_TIME}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set buffer time near");
|
||||
if (err >= 0) {
|
||||
err = 0;
|
||||
if (opts->buffer_time) {
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){opts->buffer_time}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set buffer time near");
|
||||
}
|
||||
if (err >= 0 && opts->frags) {
|
||||
err = snd_pcm_hw_params_set_periods_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){FRAGCOUNT}, NULL);
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){opts->frags}, NULL);
|
||||
CHECK_ALSA_WARN("Unable to set periods");
|
||||
}
|
||||
if (err < 0)
|
||||
snd_pcm_hw_params_copy(alsa_hwparams, hwparams_backup);
|
||||
|
||||
dump_hw_params(ao, MSGL_V, "Going to set final HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, "Going to set final HW params:\n", alsa_hwparams);
|
||||
|
||||
/* finally install hardware parameters */
|
||||
err = snd_pcm_hw_params(p->alsa, alsa_hwparams);
|
||||
ret = INIT_DEVICE_ERR_HWPARAMS;
|
||||
CHECK_ALSA_ERROR("Unable to set hw-parameters");
|
||||
ret = INIT_DEVICE_ERR_GENERIC;
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Final HW params:\n", alsa_hwparams);
|
||||
dump_hw_params(ao, "Final HW params:\n", alsa_hwparams);
|
||||
|
||||
if (set_chmap(ao, &dev_chmap, num_channels) < 0)
|
||||
goto alsa_error;
|
||||
@@ -845,11 +856,6 @@ static int init_device(struct ao *ao, int mode)
|
||||
(p->alsa, alsa_swparams, p->outburst);
|
||||
CHECK_ALSA_ERROR("Unable to set start threshold");
|
||||
|
||||
/* disable underrun reporting */
|
||||
err = snd_pcm_sw_params_set_stop_threshold
|
||||
(p->alsa, alsa_swparams, boundary);
|
||||
CHECK_ALSA_ERROR("Unable to set stop threshold");
|
||||
|
||||
/* play silence when there is an underrun */
|
||||
err = snd_pcm_sw_params_set_silence_size
|
||||
(p->alsa, alsa_swparams, boundary);
|
||||
@@ -933,17 +939,20 @@ static void drain(struct ao *ao)
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_status_t *status;
|
||||
int err;
|
||||
|
||||
snd_pcm_status_alloca(&status);
|
||||
snd_pcm_sframes_t space = snd_pcm_avail(p->alsa);
|
||||
if (space < 0) {
|
||||
MP_ERR(ao, "Error received from snd_pcm_avail (%ld, %s)!\n",
|
||||
space, snd_strerror(space));
|
||||
if (space == -EPIPE) // EOF
|
||||
return p->buffersize;
|
||||
|
||||
err = snd_pcm_status(p->alsa, status);
|
||||
if (!check_device_present(ao, err))
|
||||
// request a reload of the AO if device is not present,
|
||||
// then error out.
|
||||
check_device_present(ao, space);
|
||||
goto alsa_error;
|
||||
CHECK_ALSA_ERROR("cannot get pcm status");
|
||||
}
|
||||
|
||||
unsigned space = snd_pcm_status_get_avail(status);
|
||||
if (space > p->buffersize) // Buffer underrun?
|
||||
space = p->buffersize;
|
||||
return space / p->outburst * p->outburst;
|
||||
@@ -1106,6 +1115,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
} else if (res < 0) {
|
||||
if (res == -ESTRPIPE) { /* suspend */
|
||||
resume_device(ao);
|
||||
} else if (res == -EPIPE) {
|
||||
// For some reason, writing a smaller fragment at the end
|
||||
// immediately underruns.
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
MP_WARN(ao, "Device underrun detected.\n");
|
||||
} else {
|
||||
MP_ERR(ao, "Write error: %s\n", snd_strerror(res));
|
||||
}
|
||||
|
||||
+73
-231
@@ -40,8 +40,8 @@
|
||||
#include "common/encode_lavc.h"
|
||||
|
||||
struct priv {
|
||||
AVStream *stream;
|
||||
AVCodecContext *codec;
|
||||
struct encoder_context *enc;
|
||||
|
||||
int pcmhack;
|
||||
int aframesize;
|
||||
int aframecount;
|
||||
@@ -53,16 +53,17 @@ struct priv {
|
||||
double expected_next_pts;
|
||||
|
||||
AVRational worst_time_base;
|
||||
int worst_time_base_is_stream;
|
||||
|
||||
bool shutdown;
|
||||
};
|
||||
|
||||
static bool supports_format(AVCodec *codec, int format)
|
||||
static void encode(struct ao *ao, double apts, void **data);
|
||||
|
||||
static bool supports_format(const AVCodec *codec, int format)
|
||||
{
|
||||
for (const enum AVSampleFormat *sampleformat = codec->sample_fmts;
|
||||
sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
|
||||
++sampleformat)
|
||||
sampleformat++)
|
||||
{
|
||||
if (af_from_avformat(*sampleformat) == format)
|
||||
return true;
|
||||
@@ -70,9 +71,9 @@ static bool supports_format(AVCodec *codec, int format)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void select_format(struct ao *ao, AVCodec *codec)
|
||||
static void select_format(struct ao *ao, const AVCodec *codec)
|
||||
{
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
int formats[AF_FORMAT_COUNT + 1];
|
||||
af_get_best_sample_formats(ao->format, formats);
|
||||
|
||||
for (int n = 0; formats[n]; n++) {
|
||||
@@ -83,72 +84,64 @@ static void select_format(struct ao *ao, AVCodec *codec)
|
||||
}
|
||||
}
|
||||
|
||||
static void on_ready(void *ptr)
|
||||
{
|
||||
struct ao *ao = ptr;
|
||||
|
||||
ao_add_events(ao, AO_EVENT_INITIAL_UNBLOCK);
|
||||
}
|
||||
|
||||
// open & setup audio device
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = talloc_zero(ao, struct priv);
|
||||
AVCodec *codec;
|
||||
struct priv *ac = ao->priv;
|
||||
|
||||
ao->priv = ac;
|
||||
|
||||
if (!encode_lavc_available(ao->encode_lavc_ctx)) {
|
||||
MP_ERR(ao, "the option --o (output file) must be specified\n");
|
||||
ac->enc = encoder_context_alloc(ao->encode_lavc_ctx, STREAM_AUDIO, ao->log);
|
||||
if (!ac->enc)
|
||||
return -1;
|
||||
}
|
||||
talloc_steal(ac, ac->enc);
|
||||
|
||||
pthread_mutex_lock(&ao->encode_lavc_ctx->lock);
|
||||
|
||||
if (encode_lavc_alloc_stream(ao->encode_lavc_ctx,
|
||||
AVMEDIA_TYPE_AUDIO,
|
||||
&ac->stream, &ac->codec) < 0) {
|
||||
MP_ERR(ao, "could not get a new audio stream\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
codec = ao->encode_lavc_ctx->ac;
|
||||
AVCodecContext *encoder = ac->enc->encoder;
|
||||
const AVCodec *codec = encoder->codec;
|
||||
|
||||
int samplerate = af_select_best_samplerate(ao->samplerate,
|
||||
codec->supported_samplerates);
|
||||
if (samplerate > 0)
|
||||
ao->samplerate = samplerate;
|
||||
|
||||
// TODO: Remove this redundancy with encode_lavc_alloc_stream also
|
||||
// setting the time base.
|
||||
// Using codec->time_bvase is deprecated, but needed for older lavf.
|
||||
ac->stream->time_base.num = 1;
|
||||
ac->stream->time_base.den = ao->samplerate;
|
||||
ac->codec->time_base.num = 1;
|
||||
ac->codec->time_base.den = ao->samplerate;
|
||||
encoder->time_base.num = 1;
|
||||
encoder->time_base.den = ao->samplerate;
|
||||
|
||||
ac->codec->sample_rate = ao->samplerate;
|
||||
encoder->sample_rate = ao->samplerate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false))
|
||||
goto fail;
|
||||
mp_chmap_reorder_to_lavc(&ao->channels);
|
||||
ac->codec->channels = ao->channels.num;
|
||||
ac->codec->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
encoder->channels = ao->channels.num;
|
||||
encoder->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
|
||||
ac->codec->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
encoder->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
|
||||
select_format(ao, codec);
|
||||
|
||||
ac->sample_size = af_fmt_to_bytes(ao->format);
|
||||
ac->codec->sample_fmt = af_to_avformat(ao->format);
|
||||
ac->codec->bits_per_raw_sample = ac->sample_size * 8;
|
||||
encoder->sample_fmt = af_to_avformat(ao->format);
|
||||
encoder->bits_per_raw_sample = ac->sample_size * 8;
|
||||
|
||||
if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->codec) < 0)
|
||||
if (!encoder_init_codec_and_muxer(ac->enc, on_ready, ao))
|
||||
goto fail;
|
||||
|
||||
ac->pcmhack = 0;
|
||||
if (ac->codec->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(ac->codec->codec_id) / 8;
|
||||
if (encoder->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(encoder->codec_id) / 8;
|
||||
|
||||
if (ac->pcmhack)
|
||||
if (ac->pcmhack) {
|
||||
ac->aframesize = 16384; // "enough"
|
||||
else
|
||||
ac->aframesize = ac->codec->frame_size;
|
||||
} else {
|
||||
ac->aframesize = encoder->frame_size;
|
||||
}
|
||||
|
||||
// enough frames for at least 0.25 seconds
|
||||
ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize);
|
||||
@@ -165,7 +158,6 @@ static int init(struct ao *ao)
|
||||
if (ao->channels.num > AV_NUM_DATA_POINTERS)
|
||||
goto fail;
|
||||
|
||||
pthread_mutex_unlock(&ao->encode_lavc_ctx->lock);
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
@@ -175,34 +167,22 @@ fail:
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static void encode(struct ao *ao, double apts, void **data);
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
|
||||
if (!ac || ac->shutdown)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
|
||||
if (!encode_lavc_start(ectx)) {
|
||||
MP_WARN(ao, "not even ready to encode audio at end -> dropped\n");
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ac->stream) {
|
||||
if (!ac->shutdown) {
|
||||
double outpts = ac->expected_next_pts;
|
||||
if (!ectx->options->rawts && ectx->options->copyts)
|
||||
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
if (!ac->enc->options->rawts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
outpts += encoder_get_offset(ac->enc);
|
||||
encode(ao, outpts, NULL);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
ac->shutdown = true;
|
||||
}
|
||||
|
||||
// return: how many samples can be played without blocking
|
||||
@@ -213,106 +193,21 @@ static int get_space(struct ao *ao)
|
||||
return ac->aframesize * ac->framecount;
|
||||
}
|
||||
|
||||
static void write_packet(struct ao *ao, AVPacket *packet)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
|
||||
packet->stream_index = ac->stream->index;
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(packet->pts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
} else {
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE) {
|
||||
packet->pts = av_rescale_q(ac->savepts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
}
|
||||
if (packet->dts != AV_NOPTS_VALUE) {
|
||||
packet->dts = av_rescale_q(packet->dts,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
if (packet->duration > 0) {
|
||||
packet->duration = av_rescale_q(packet->duration,
|
||||
ac->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
}
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx,
|
||||
ac->stream, packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %d %d/%d\n",
|
||||
(int) packet->pts,
|
||||
ac->stream->time_base.num,
|
||||
ac->stream->time_base.den);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
{
|
||||
// TODO: Can we unify this with the equivalent video code path?
|
||||
struct priv *ac = ao->priv;
|
||||
AVPacket packet = {0};
|
||||
|
||||
int status = avcodec_send_frame(ac->codec, frame);
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
av_init_packet(&packet);
|
||||
status = avcodec_receive_packet(ac->codec, &packet);
|
||||
if (status == AVERROR(EAGAIN)) { // No more packets for now.
|
||||
if (frame == NULL) {
|
||||
MP_ERR(ao, "sent flush frame, got EAGAIN");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status == AVERROR_EOF) { // No more packets, ever.
|
||||
if (frame != NULL) {
|
||||
MP_ERR(ao, "sent audio frame, got EOF");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (status < 0) {
|
||||
MP_ERR(ao, "error encoding at %d %d/%d\n",
|
||||
frame ? (int) frame->pts : -1,
|
||||
ac->codec->time_base.num,
|
||||
ac->codec->time_base.den);
|
||||
break;
|
||||
}
|
||||
if (frame) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->codec);
|
||||
write_packet(ao, &packet);
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
}
|
||||
|
||||
// must get exactly ac->aframesize amount of data
|
||||
static void encode(struct ao *ao, double apts, void **data)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
AVCodecContext *encoder = ac->enc->encoder;
|
||||
double realapts = ac->aframecount * (double) ac->aframesize /
|
||||
ao->samplerate;
|
||||
|
||||
ac->aframecount++;
|
||||
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
if (data)
|
||||
ectx->audio_pts_offset = realapts - apts;
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
if(data) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
@@ -326,31 +221,27 @@ static void encode(struct ao *ao, double apts, void **data)
|
||||
|
||||
frame->linesize[0] = frame->nb_samples * ao->sstride;
|
||||
|
||||
if (ectx->options->rawts || ectx->options->copyts) {
|
||||
// real audio pts
|
||||
frame->pts = floor(apts * ac->codec->time_base.den / ac->codec->time_base.num + 0.5);
|
||||
} else {
|
||||
// audio playback time
|
||||
frame->pts = floor(realapts * ac->codec->time_base.den / ac->codec->time_base.num + 0.5);
|
||||
}
|
||||
frame->pts = rint(apts * av_q2d(av_inv_q(encoder->time_base)));
|
||||
|
||||
int64_t frame_pts = av_rescale_q(frame->pts, ac->codec->time_base, ac->worst_time_base);
|
||||
int64_t frame_pts = av_rescale_q(frame->pts, encoder->time_base,
|
||||
ac->worst_time_base);
|
||||
if (ac->lastpts != AV_NOPTS_VALUE && frame_pts <= ac->lastpts) {
|
||||
// this indicates broken video
|
||||
// (video pts failing to increase fast enough to match audio)
|
||||
MP_WARN(ao, "audio frame pts went backwards (%d <- %d), autofixed\n",
|
||||
(int)frame->pts, (int)ac->lastpts);
|
||||
frame_pts = ac->lastpts + 1;
|
||||
frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, ac->codec->time_base);
|
||||
frame->pts = av_rescale_q(frame_pts, ac->worst_time_base,
|
||||
encoder->time_base);
|
||||
}
|
||||
ac->lastpts = frame_pts;
|
||||
|
||||
frame->quality = ac->codec->global_quality;
|
||||
encode_audio_and_write(ao, frame);
|
||||
frame->quality = encoder->global_quality;
|
||||
encoder_encode(ac->enc, frame);
|
||||
av_frame_free(&frame);
|
||||
} else {
|
||||
encoder_encode(ac->enc, NULL);
|
||||
}
|
||||
else
|
||||
encode_audio_and_write(ao, NULL);
|
||||
}
|
||||
|
||||
// this should round samples down to frame sizes
|
||||
@@ -358,20 +249,15 @@ static void encode(struct ao *ao, double apts, void **data)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
struct encoder_context *enc = ac->enc;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
int bufpos = 0;
|
||||
double nextpts;
|
||||
double outpts;
|
||||
int orig_samples = samples;
|
||||
|
||||
// for ectx PTS fields
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
|
||||
if (!encode_lavc_start(ectx)) {
|
||||
MP_WARN(ao, "not ready yet for encoding audio\n");
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
double pts = ectx->last_audio_in_pts;
|
||||
pts += ectx->samples_since_last_pts / (double)ao->samplerate;
|
||||
|
||||
@@ -393,60 +279,15 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
samples = (bytelen + extralen) / ao->sstride;
|
||||
}
|
||||
|
||||
if (pts == MP_NOPTS_VALUE) {
|
||||
MP_WARN(ao, "frame without pts, please report; synthesizing pts instead\n");
|
||||
// synthesize pts from previous expected next pts
|
||||
pts = ac->expected_next_pts;
|
||||
}
|
||||
|
||||
if (ac->worst_time_base.den == 0) {
|
||||
//if (ac->codec->time_base.num / ac->codec->time_base.den >= ac->stream->time_base.num / ac->stream->time_base.den)
|
||||
if (ac->codec->time_base.num * (double) ac->stream->time_base.den >=
|
||||
ac->stream->time_base.num * (double) ac->codec->time_base.den) {
|
||||
MP_VERBOSE(ao, "NOTE: using codec time base (%d/%d) for pts "
|
||||
"adjustment; the stream base (%d/%d) is not worse.\n",
|
||||
(int)ac->codec->time_base.num,
|
||||
(int)ac->codec->time_base.den,
|
||||
(int)ac->stream->time_base.num,
|
||||
(int)ac->stream->time_base.den);
|
||||
ac->worst_time_base = ac->codec->time_base;
|
||||
ac->worst_time_base_is_stream = 0;
|
||||
} else {
|
||||
MP_WARN(ao, "NOTE: not using codec time base (%d/%d) for pts "
|
||||
"adjustment; the stream base (%d/%d) is worse.\n",
|
||||
(int)ac->codec->time_base.num,
|
||||
(int)ac->codec->time_base.den,
|
||||
(int)ac->stream->time_base.num,
|
||||
(int)ac->stream->time_base.den);
|
||||
ac->worst_time_base = ac->stream->time_base;
|
||||
ac->worst_time_base_is_stream = 1;
|
||||
}
|
||||
|
||||
// NOTE: we use the following "axiom" of av_rescale_q:
|
||||
// if time base A is worse than time base B, then
|
||||
// av_rescale_q(av_rescale_q(x, A, B), B, A) == x
|
||||
// this can be proven as long as av_rescale_q rounds to nearest, which
|
||||
// it currently does
|
||||
|
||||
// av_rescale_q(x, A, B) * B = "round x*A to nearest multiple of B"
|
||||
// and:
|
||||
// av_rescale_q(av_rescale_q(x, A, B), B, A) * A
|
||||
// == "round av_rescale_q(x, A, B)*B to nearest multiple of A"
|
||||
// == "round 'round x*A to nearest multiple of B' to nearest multiple of A"
|
||||
//
|
||||
// assume this fails. Then there is a value of x*A, for which the
|
||||
// nearest multiple of B is outside the range [(x-0.5)*A, (x+0.5)*A[.
|
||||
// Absurd, as this range MUST contain at least one multiple of B.
|
||||
}
|
||||
|
||||
// Fix and apply the discontinuity pts offset.
|
||||
if (!ectx->options->rawts && ectx->options->copyts) {
|
||||
// fix the discontinuity pts offset
|
||||
double outpts = pts;
|
||||
if (!enc->options->rawts) {
|
||||
// Fix and apply the discontinuity pts offset.
|
||||
nextpts = pts;
|
||||
if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) {
|
||||
ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
|
||||
}
|
||||
else if (fabs(nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts) > 30) {
|
||||
} else if (fabs(nextpts + ectx->discontinuity_pts_offset -
|
||||
ectx->next_in_pts) > 30)
|
||||
{
|
||||
MP_WARN(ao, "detected an unexpected discontinuity (pts jumped by "
|
||||
"%f seconds)\n",
|
||||
nextpts + ectx->discontinuity_pts_offset - ectx->next_in_pts);
|
||||
@@ -455,12 +296,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
|
||||
outpts = pts + ectx->discontinuity_pts_offset;
|
||||
}
|
||||
else {
|
||||
outpts = pts;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
// Shift pts by the pts offset first.
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
outpts += encoder_get_offset(enc);
|
||||
|
||||
while (samples - bufpos >= ac->aframesize) {
|
||||
void *start[MP_NUM_CHANNELS] = {0};
|
||||
@@ -473,8 +313,10 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
// Calculate expected pts of next audio frame (input side).
|
||||
ac->expected_next_pts = pts + bufpos / (double) ao->samplerate;
|
||||
|
||||
pthread_mutex_lock(&ectx->lock);
|
||||
|
||||
// Set next allowed input pts value (input side).
|
||||
if (!ectx->options->rawts && ectx->options->copyts) {
|
||||
if (!enc->options->rawts) {
|
||||
nextpts = ac->expected_next_pts + ectx->discontinuity_pts_offset;
|
||||
if (nextpts > ectx->next_in_pts)
|
||||
ectx->next_in_pts = nextpts;
|
||||
@@ -488,13 +330,11 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
if (bufpos < orig_samples) {
|
||||
if (bufpos < orig_samples)
|
||||
MP_ERR(ao, "did not write enough data at the end\n");
|
||||
}
|
||||
} else {
|
||||
if (bufpos > orig_samples) {
|
||||
if (bufpos > orig_samples)
|
||||
MP_ERR(ao, "audio buffer overflow (should never happen)\n");
|
||||
}
|
||||
}
|
||||
|
||||
return taken;
|
||||
@@ -509,6 +349,8 @@ const struct ao_driver audio_out_lavc = {
|
||||
.encode = true,
|
||||
.description = "audio encoding using libavcodec",
|
||||
.name = "lavc",
|
||||
.initially_blocked = true,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.get_space = get_space,
|
||||
|
||||
@@ -58,6 +58,7 @@ struct priv {
|
||||
int outburst; // samples
|
||||
|
||||
struct m_channels channel_layouts;
|
||||
int format;
|
||||
};
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -88,6 +89,9 @@ static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
if (priv->format)
|
||||
ao->format = priv->format;
|
||||
|
||||
ao->untimed = priv->untimed;
|
||||
|
||||
struct mp_chmap_sel sel = {.tmp = ao};
|
||||
@@ -241,6 +245,7 @@ const struct ao_driver audio_out_null = {
|
||||
OPT_FLAG("broken-eof", broken_eof, 0),
|
||||
OPT_FLAG("broken-delay", broken_delay, 0),
|
||||
OPT_CHANNELS("channel-layouts", channel_layouts, 0),
|
||||
OPT_AUDIOFORMAT("format", format, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "ao-null",
|
||||
|
||||
+223
-109
@@ -57,19 +57,22 @@
|
||||
#include "options/m_option.h"
|
||||
|
||||
#define MAX_CHANS MP_NUM_CHANNELS
|
||||
#define NUM_BUF 128
|
||||
#define CHUNK_SAMPLES 256
|
||||
static ALuint buffers[MAX_CHANS][NUM_BUF];
|
||||
static ALuint sources[MAX_CHANS];
|
||||
#define MAX_BUF 128
|
||||
#define MAX_SAMPLES 32768
|
||||
static ALuint buffers[MAX_BUF];
|
||||
static ALuint buffer_size[MAX_BUF];
|
||||
static ALuint source;
|
||||
|
||||
static int cur_buf[MAX_CHANS];
|
||||
static int unqueue_buf[MAX_CHANS];
|
||||
static int cur_buf;
|
||||
static int unqueue_buf;
|
||||
|
||||
static struct ao *ao_data;
|
||||
|
||||
struct priv {
|
||||
ALenum al_format;
|
||||
int chunk_size;
|
||||
int num_buffers;
|
||||
int num_samples;
|
||||
int direct_channels;
|
||||
};
|
||||
|
||||
static void reset(struct ao *ao);
|
||||
@@ -89,53 +92,99 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
vol->left = vol->right = volume * 100;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
case AOCONTROL_GET_MUTE:
|
||||
case AOCONTROL_SET_MUTE: {
|
||||
bool mute = *(bool *)arg;
|
||||
|
||||
// openal has no mute control, only gain.
|
||||
// Thus reverse the muted state to get required gain
|
||||
ALfloat al_mute = (ALfloat)(!mute);
|
||||
if (cmd == AOCONTROL_SET_MUTE) {
|
||||
alSourcef(source, AL_GAIN, al_mute);
|
||||
}
|
||||
alGetSourcef(source, AL_GAIN, &al_mute);
|
||||
*(bool *)arg = !((bool)al_mute);
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
|
||||
case AOCONTROL_HAS_SOFT_VOLUME:
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
struct speaker {
|
||||
int id;
|
||||
float pos[3];
|
||||
};
|
||||
|
||||
static const struct speaker speaker_pos[] = {
|
||||
{MP_SPEAKER_ID_FL, {-0.500, 0, -0.866}}, // -30 deg
|
||||
{MP_SPEAKER_ID_FR, { 0.500, 0, -0.866}}, // 30 deg
|
||||
{MP_SPEAKER_ID_FC, { 0, 0, -1}}, // 0 deg
|
||||
{MP_SPEAKER_ID_LFE, { 0, -1, 0}}, // below
|
||||
{MP_SPEAKER_ID_BL, {-0.609, 0, 0.793}}, // -142.5 deg
|
||||
{MP_SPEAKER_ID_BR, { 0.609, 0, 0.793}}, // 142.5 deg
|
||||
{MP_SPEAKER_ID_BC, { 0, 0, 1}}, // 180 deg
|
||||
{MP_SPEAKER_ID_SL, {-0.985, 0, 0.174}}, // -100 deg
|
||||
{MP_SPEAKER_ID_SR, { 0.985, 0, 0.174}}, // 100 deg
|
||||
{-1},
|
||||
};
|
||||
|
||||
static ALenum get_al_format(int format)
|
||||
static enum af_format get_supported_format(int format)
|
||||
{
|
||||
switch (format) {
|
||||
case AF_FORMAT_U8P: return AL_FORMAT_MONO8;
|
||||
case AF_FORMAT_S16P: return AL_FORMAT_MONO16;
|
||||
case AF_FORMAT_FLOATP:
|
||||
case AF_FORMAT_U8:
|
||||
if (alGetEnumValue((ALchar*)"AL_FORMAT_MONO8"))
|
||||
return AF_FORMAT_U8;
|
||||
break;
|
||||
|
||||
case AF_FORMAT_S16:
|
||||
if (alGetEnumValue((ALchar*)"AL_FORMAT_MONO16"))
|
||||
return AF_FORMAT_S16;
|
||||
break;
|
||||
|
||||
case AF_FORMAT_S32:
|
||||
if (strstr(alGetString(AL_RENDERER), "X-Fi") != NULL)
|
||||
return AF_FORMAT_S32;
|
||||
break;
|
||||
|
||||
case AF_FORMAT_FLOAT:
|
||||
if (alIsExtensionPresent((ALchar*)"AL_EXT_float32") == AL_TRUE)
|
||||
return AL_FORMAT_MONO_FLOAT32;
|
||||
break;
|
||||
case AF_FORMAT_DOUBLEP:
|
||||
if (alIsExtensionPresent((ALchar*)"AL_EXT_double") == AL_TRUE)
|
||||
return AL_FORMAT_MONO_DOUBLE_EXT;
|
||||
return AF_FORMAT_FLOAT;
|
||||
break;
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
|
||||
static ALenum get_supported_layout(int format, int channels)
|
||||
{
|
||||
const char *channel_str[] = {
|
||||
[1] = "MONO",
|
||||
[2] = "STEREO",
|
||||
[4] = "QUAD",
|
||||
[6] = "51CHN",
|
||||
[7] = "61CHN",
|
||||
[8] = "71CHN",
|
||||
};
|
||||
const char *format_str[] = {
|
||||
[AF_FORMAT_U8] = "8",
|
||||
[AF_FORMAT_S16] = "16",
|
||||
[AF_FORMAT_S32] = "32",
|
||||
[AF_FORMAT_FLOAT] = "_FLOAT32",
|
||||
};
|
||||
if (channel_str[channels] == NULL || format_str[format] == NULL)
|
||||
return AL_FALSE;
|
||||
|
||||
char enum_name[32];
|
||||
// AF_FORMAT_FLOAT uses same enum name as AF_FORMAT_S32 for multichannel
|
||||
// playback, while it is different for mono and stereo.
|
||||
// OpenAL Soft does not support AF_FORMAT_S32 and seems to reuse the names.
|
||||
if (channels > 2 && format == AF_FORMAT_FLOAT)
|
||||
format = AF_FORMAT_S32;
|
||||
snprintf(enum_name, sizeof(enum_name), "AL_FORMAT_%s%s", channel_str[channels],
|
||||
format_str[format]);
|
||||
|
||||
if (alGetEnumValue((ALchar*)enum_name)) {
|
||||
return alGetEnumValue((ALchar*)enum_name);
|
||||
}
|
||||
return AL_FALSE;
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
alSourceStop(source);
|
||||
alSourcei(source, AL_BUFFER, 0);
|
||||
|
||||
alDeleteBuffers(p->num_buffers, buffers);
|
||||
alDeleteSources(1, &source);
|
||||
|
||||
ALCcontext *ctx = alcGetCurrentContext();
|
||||
ALCdevice *dev = alcGetContextsDevice(ctx);
|
||||
reset(ao);
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcDestroyContext(ctx);
|
||||
alcCloseDevice(dev);
|
||||
@@ -150,30 +199,12 @@ static int init(struct ao *ao)
|
||||
ALCcontext *ctx = NULL;
|
||||
ALCint freq = 0;
|
||||
ALCint attribs[] = {ALC_FREQUENCY, ao->samplerate, 0, 0};
|
||||
int i;
|
||||
struct priv *p = ao->priv;
|
||||
if (ao_data) {
|
||||
MP_FATAL(ao, "Not reentrant!\n");
|
||||
return -1;
|
||||
}
|
||||
ao_data = ao;
|
||||
struct mp_chmap_sel sel = {0};
|
||||
for (i = 0; speaker_pos[i].id != -1; i++)
|
||||
mp_chmap_sel_add_speaker(&sel, speaker_pos[i].id);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
goto err_out;
|
||||
struct speaker speakers[MAX_CHANS];
|
||||
for (i = 0; i < ao->channels.num; i++) {
|
||||
speakers[i].id = -1;
|
||||
for (int n = 0; speaker_pos[n].id >= 0; n++) {
|
||||
if (speaker_pos[n].id == ao->channels.speaker[i])
|
||||
speakers[i] = speaker_pos[n];
|
||||
}
|
||||
if (speakers[i].id < 0) {
|
||||
MP_FATAL(ao, "Unknown channel layout\n");
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
char *dev_name = ao->device;
|
||||
dev = alcOpenDevice(dev_name && dev_name[0] ? dev_name : NULL);
|
||||
if (!dev) {
|
||||
@@ -184,37 +215,74 @@ static int init(struct ao *ao)
|
||||
alcMakeContextCurrent(ctx);
|
||||
alListenerfv(AL_POSITION, position);
|
||||
alListenerfv(AL_ORIENTATION, direction);
|
||||
alGenSources(ao->channels.num, sources);
|
||||
for (i = 0; i < ao->channels.num; i++) {
|
||||
cur_buf[i] = 0;
|
||||
unqueue_buf[i] = 0;
|
||||
alGenBuffers(NUM_BUF, buffers[i]);
|
||||
alSourcefv(sources[i], AL_POSITION, speakers[i].pos);
|
||||
alSource3f(sources[i], AL_VELOCITY, 0, 0, 0);
|
||||
|
||||
alGenSources(1, &source);
|
||||
if (p->direct_channels && alGetEnumValue((ALchar*)"AL_DIRECT_CHANNELS_SOFT")) {
|
||||
alSourcei(source, alGetEnumValue((ALchar*)"AL_DIRECT_CHANNELS_SOFT"), AL_TRUE);
|
||||
}
|
||||
|
||||
cur_buf = 0;
|
||||
unqueue_buf = 0;
|
||||
for (int i = 0; i < p->num_buffers; ++i) {
|
||||
buffer_size[i] = 0;
|
||||
}
|
||||
|
||||
alGenBuffers(p->num_buffers, buffers);
|
||||
|
||||
alcGetIntegerv(dev, ALC_FREQUENCY, 1, &freq);
|
||||
if (alcGetError(dev) == ALC_NO_ERROR && freq)
|
||||
ao->samplerate = freq;
|
||||
|
||||
p->al_format = AL_FALSE;
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
// Check sample format
|
||||
int try_formats[AF_FORMAT_COUNT + 1];
|
||||
enum af_format sample_format = 0;
|
||||
af_get_best_sample_formats(ao->format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
p->al_format = get_al_format(try_formats[n]);
|
||||
if (p->al_format != AL_FALSE) {
|
||||
sample_format = get_supported_format(try_formats[n]);
|
||||
if (sample_format != AF_FORMAT_UNKNOWN) {
|
||||
ao->format = try_formats[n];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p->al_format == AL_FALSE) {
|
||||
if (sample_format == AF_FORMAT_UNKNOWN) {
|
||||
MP_FATAL(ao, "Can't find appropriate sample format.\n");
|
||||
uninit(ao);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
p->chunk_size = CHUNK_SAMPLES * af_fmt_to_bytes(ao->format);
|
||||
ao->period_size = CHUNK_SAMPLES;
|
||||
// Check if OpenAL driver supports the desired number of channels.
|
||||
int num_channels = ao->channels.num;
|
||||
do {
|
||||
p->al_format = get_supported_layout(sample_format, num_channels);
|
||||
if (p->al_format == AL_FALSE) {
|
||||
num_channels = num_channels - 1;
|
||||
}
|
||||
} while (p->al_format == AL_FALSE && num_channels > 1);
|
||||
|
||||
// Request number of speakers for output from ao.
|
||||
const struct mp_chmap possible_layouts[] = {
|
||||
{0}, // empty
|
||||
MP_CHMAP_INIT_MONO, // mono
|
||||
MP_CHMAP_INIT_STEREO, // stereo
|
||||
{0}, // 2.1
|
||||
MP_CHMAP4(FL, FR, BL, BR), // 4.0
|
||||
{0}, // 5.0
|
||||
MP_CHMAP6(FL, FR, FC, LFE, BL, BR), // 5.1
|
||||
MP_CHMAP7(FL, FR, FC, LFE, SL, SR, BC), // 6.1
|
||||
MP_CHMAP8(FL, FR, FC, LFE, BL, BR, SL, SR), // 7.1
|
||||
};
|
||||
ao->channels = possible_layouts[num_channels];
|
||||
if (!ao->channels.num)
|
||||
mp_chmap_set_unknown(&ao->channels, num_channels);
|
||||
|
||||
if (p->al_format == AL_FALSE || !mp_chmap_is_valid(&ao->channels)) {
|
||||
MP_FATAL(ao, "Can't find appropriate channel layout.\n");
|
||||
uninit(ao);
|
||||
goto err_out;
|
||||
}
|
||||
|
||||
ao->period_size = p->num_samples;
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
@@ -225,30 +293,27 @@ err_out:
|
||||
static void drain(struct ao *ao)
|
||||
{
|
||||
ALint state;
|
||||
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
while (state == AL_PLAYING) {
|
||||
mp_sleep_us(10000);
|
||||
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
}
|
||||
}
|
||||
|
||||
static void unqueue_buffers(void)
|
||||
static void unqueue_buffers(struct ao *ao)
|
||||
{
|
||||
struct priv *q = ao->priv;
|
||||
ALint p;
|
||||
int s;
|
||||
for (s = 0; s < ao_data->channels.num; s++) {
|
||||
int till_wrap = NUM_BUF - unqueue_buf[s];
|
||||
alGetSourcei(sources[s], AL_BUFFERS_PROCESSED, &p);
|
||||
if (p >= till_wrap) {
|
||||
alSourceUnqueueBuffers(sources[s], till_wrap,
|
||||
&buffers[s][unqueue_buf[s]]);
|
||||
unqueue_buf[s] = 0;
|
||||
p -= till_wrap;
|
||||
}
|
||||
if (p) {
|
||||
alSourceUnqueueBuffers(sources[s], p, &buffers[s][unqueue_buf[s]]);
|
||||
unqueue_buf[s] += p;
|
||||
}
|
||||
int till_wrap = q->num_buffers - unqueue_buf;
|
||||
alGetSourcei(source, AL_BUFFERS_PROCESSED, &p);
|
||||
if (p >= till_wrap) {
|
||||
alSourceUnqueueBuffers(source, till_wrap, &buffers[unqueue_buf]);
|
||||
unqueue_buf = 0;
|
||||
p -= till_wrap;
|
||||
}
|
||||
if (p) {
|
||||
alSourceUnqueueBuffers(source, p, &buffers[unqueue_buf]);
|
||||
unqueue_buf += p;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,8 +322,8 @@ static void unqueue_buffers(void)
|
||||
*/
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
alSourceStopv(ao->channels.num, sources);
|
||||
unqueue_buffers();
|
||||
alSourceStop(source);
|
||||
unqueue_buffers(ao);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -266,7 +331,7 @@ static void reset(struct ao *ao)
|
||||
*/
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
alSourcePausev(ao->channels.num, sources);
|
||||
alSourcePause(source);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,18 +339,19 @@ static void audio_pause(struct ao *ao)
|
||||
*/
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
alSourcePlayv(ao->channels.num, sources);
|
||||
alSourcePlay(source);
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ALint queued;
|
||||
unqueue_buffers();
|
||||
alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued);
|
||||
queued = NUM_BUF - queued - 3;
|
||||
unqueue_buffers(ao);
|
||||
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
|
||||
queued = p->num_buffers - queued;
|
||||
if (queued < 0)
|
||||
return 0;
|
||||
return queued * CHUNK_SAMPLES;
|
||||
return p->num_samples * queued;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -294,30 +360,66 @@ static int get_space(struct ao *ao)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ALint state;
|
||||
int num = samples / CHUNK_SAMPLES;
|
||||
for (int i = 0; i < num; i++) {
|
||||
for (int ch = 0; ch < ao->channels.num; ch++) {
|
||||
char *d = data[ch];
|
||||
d += i * p->chunk_size;
|
||||
alBufferData(buffers[ch][cur_buf[ch]], p->al_format, d,
|
||||
p->chunk_size, ao->samplerate);
|
||||
alSourceQueueBuffers(sources[ch], 1, &buffers[ch][cur_buf[ch]]);
|
||||
cur_buf[ch] = (cur_buf[ch] + 1) % NUM_BUF;
|
||||
}
|
||||
|
||||
int buffered_samples = 0;
|
||||
int num = 0;
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
num = 1;
|
||||
buffered_samples = samples;
|
||||
} else {
|
||||
num = samples / p->num_samples;
|
||||
buffered_samples = num * p->num_samples;
|
||||
}
|
||||
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
|
||||
|
||||
for (int i = 0; i < num; i++) {
|
||||
char *d = *data;
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
buffer_size[cur_buf] = samples;
|
||||
} else {
|
||||
buffer_size[cur_buf] = p->num_samples;
|
||||
}
|
||||
d += i * buffer_size[cur_buf] * ao->sstride;
|
||||
alBufferData(buffers[cur_buf], p->al_format, d,
|
||||
buffer_size[cur_buf] * ao->sstride, ao->samplerate);
|
||||
alSourceQueueBuffers(source, 1, &buffers[cur_buf]);
|
||||
cur_buf = (cur_buf + 1) % p->num_buffers;
|
||||
}
|
||||
|
||||
ALint state;
|
||||
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) // checked here in case of an underrun
|
||||
alSourcePlayv(ao->channels.num, sources);
|
||||
return num * CHUNK_SAMPLES;
|
||||
alSourcePlay(source);
|
||||
|
||||
return buffered_samples;
|
||||
}
|
||||
|
||||
static double get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
ALint queued;
|
||||
unqueue_buffers();
|
||||
alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued);
|
||||
return queued * CHUNK_SAMPLES / (double)ao->samplerate;
|
||||
unqueue_buffers(ao);
|
||||
alGetSourcei(source, AL_BUFFERS_QUEUED, &queued);
|
||||
|
||||
double soft_source_latency = 0;
|
||||
if(alIsExtensionPresent("AL_SOFT_source_latency")) {
|
||||
ALdouble offsets[2];
|
||||
LPALGETSOURCEDVSOFT alGetSourcedvSOFT = alGetProcAddress("alGetSourcedvSOFT");
|
||||
alGetSourcedvSOFT(source, AL_SEC_OFFSET_LATENCY_SOFT, offsets);
|
||||
// Additional latency to the play buffer, the remaining seconds to be
|
||||
// played minus the offset (seconds already played)
|
||||
soft_source_latency = offsets[1] - offsets[0];
|
||||
} else {
|
||||
float offset = 0;
|
||||
alGetSourcef(source, AL_SEC_OFFSET, &offset);
|
||||
soft_source_latency = -offset;
|
||||
}
|
||||
|
||||
int queued_samples = 0;
|
||||
for (int i = 0, index = cur_buf; i < queued; ++i) {
|
||||
queued_samples += buffer_size[index];
|
||||
index = (index + 1) % p->num_buffers;
|
||||
}
|
||||
return (queued_samples / (double)ao->samplerate) + soft_source_latency;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
@@ -336,4 +438,16 @@ const struct ao_driver audio_out_openal = {
|
||||
.reset = reset,
|
||||
.drain = drain,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.num_buffers = 4,
|
||||
.num_samples = 8192,
|
||||
.direct_channels = 0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("num-buffers", num_buffers, 0, 2, MAX_BUF),
|
||||
OPT_INTRANGE("num-samples", num_samples, 0, 256, MAX_SAMPLES),
|
||||
OPT_FLAG("direct-channels", direct_channels, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "openal",
|
||||
};
|
||||
+43
-37
@@ -35,12 +35,12 @@ struct priv {
|
||||
SLBufferQueueItf buffer_queue;
|
||||
SLEngineItf engine;
|
||||
SLPlayItf play;
|
||||
char *curbuf, *buf1, *buf2;
|
||||
char *buf;
|
||||
size_t buffer_size;
|
||||
pthread_mutex_t buffer_lock;
|
||||
double audio_latency;
|
||||
|
||||
int cfg_frames_per_buffer;
|
||||
int cfg_sample_rate;
|
||||
};
|
||||
|
||||
static const int fmtmap[][2] = {
|
||||
@@ -69,9 +69,8 @@ static void uninit(struct ao *ao)
|
||||
|
||||
pthread_mutex_destroy(&p->buffer_lock);
|
||||
|
||||
free(p->buf1);
|
||||
free(p->buf2);
|
||||
p->curbuf = p->buf1 = p->buf2 = NULL;
|
||||
free(p->buf);
|
||||
p->buf = NULL;
|
||||
p->buffer_size = 0;
|
||||
}
|
||||
|
||||
@@ -87,21 +86,20 @@ static void buffer_callback(SLBufferQueueItf buffer_queue, void *context)
|
||||
|
||||
pthread_mutex_lock(&p->buffer_lock);
|
||||
|
||||
data[0] = p->curbuf;
|
||||
data[0] = p->buf;
|
||||
delay = 2 * p->buffer_size / (double)ao->bps;
|
||||
delay += p->audio_latency;
|
||||
ao_read_data(ao, data, p->buffer_size / ao->sstride,
|
||||
mp_time_us() + 1000000LL * delay);
|
||||
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->curbuf, p->buffer_size);
|
||||
res = (*buffer_queue)->Enqueue(buffer_queue, p->buf, p->buffer_size);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
MP_ERR(ao, "Failed to Enqueue: %d\n", res);
|
||||
else
|
||||
p->curbuf = (p->curbuf == p->buf1) ? p->buf2 : p->buf1;
|
||||
|
||||
pthread_mutex_unlock(&p->buffer_lock);
|
||||
}
|
||||
|
||||
#define DEFAULT_BUFFER_SIZE_MS 50
|
||||
#define DEFAULT_BUFFER_SIZE_MS 250
|
||||
|
||||
#define CHK(stmt) \
|
||||
{ \
|
||||
@@ -131,12 +129,12 @@ static int init(struct ao *ao)
|
||||
CHK((*p->output_mix)->Realize(p->output_mix, SL_BOOLEAN_FALSE));
|
||||
|
||||
locator_buffer_queue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
|
||||
locator_buffer_queue.numBuffers = 2;
|
||||
locator_buffer_queue.numBuffers = 1;
|
||||
|
||||
pcm.formatType = SL_DATAFORMAT_PCM;
|
||||
pcm.numChannels = 2;
|
||||
|
||||
int compatible_formats[AF_FORMAT_COUNT];
|
||||
int compatible_formats[AF_FORMAT_COUNT + 1];
|
||||
af_get_best_sample_formats(ao->format, compatible_formats);
|
||||
pcm.bitsPerSample = 0;
|
||||
for (int i = 0; compatible_formats[i] && !pcm.bitsPerSample; ++i)
|
||||
@@ -154,9 +152,6 @@ static int init(struct ao *ao)
|
||||
pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
|
||||
|
||||
if (p->cfg_sample_rate)
|
||||
ao->samplerate = p->cfg_sample_rate;
|
||||
|
||||
// samplesPerSec is misnamed, actually it's samples per ms
|
||||
pcm.samplesPerSec = ao->samplerate * 1000;
|
||||
|
||||
@@ -166,10 +161,8 @@ static int init(struct ao *ao)
|
||||
ao->device_buffer = ao->samplerate * DEFAULT_BUFFER_SIZE_MS / 1000;
|
||||
p->buffer_size = ao->device_buffer * ao->channels.num *
|
||||
af_fmt_to_bytes(ao->format);
|
||||
p->buf1 = calloc(1, p->buffer_size);
|
||||
p->buf2 = calloc(1, p->buffer_size);
|
||||
p->curbuf = p->buf1;
|
||||
if (!p->buf1 || !p->buf2) {
|
||||
p->buf = calloc(1, p->buffer_size);
|
||||
if (!p->buf) {
|
||||
MP_ERR(ao, "Failed to allocate device buffer\n");
|
||||
goto error;
|
||||
}
|
||||
@@ -188,16 +181,41 @@ static int init(struct ao *ao)
|
||||
audio_sink.pLocator = (void*)&locator_output_mix;
|
||||
audio_sink.pFormat = NULL;
|
||||
|
||||
SLboolean required[] = { SL_BOOLEAN_TRUE };
|
||||
SLInterfaceID iid_array[] = { SL_IID_BUFFERQUEUE };
|
||||
SLInterfaceID iid_array[] = { SL_IID_BUFFERQUEUE, SL_IID_ANDROIDCONFIGURATION };
|
||||
SLboolean required[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_FALSE };
|
||||
CHK((*p->engine)->CreateAudioPlayer(p->engine, &p->player, &audio_source,
|
||||
&audio_sink, 1, iid_array, required));
|
||||
&audio_sink, 2, iid_array, required));
|
||||
|
||||
CHK((*p->player)->Realize(p->player, SL_BOOLEAN_FALSE));
|
||||
CHK((*p->player)->GetInterface(p->player, SL_IID_PLAY, (void*)&p->play));
|
||||
CHK((*p->player)->GetInterface(p->player, SL_IID_BUFFERQUEUE,
|
||||
(void*)&p->buffer_queue));
|
||||
CHK((*p->buffer_queue)->RegisterCallback(p->buffer_queue,
|
||||
buffer_callback, ao));
|
||||
CHK((*p->play)->SetPlayState(p->play, SL_PLAYSTATE_PLAYING));
|
||||
|
||||
SLAndroidConfigurationItf android_config;
|
||||
SLuint32 audio_latency = 0, value_size = sizeof(SLuint32);
|
||||
|
||||
SLint32 get_interface_result = (*p->player)->GetInterface(
|
||||
p->player,
|
||||
SL_IID_ANDROIDCONFIGURATION,
|
||||
&android_config
|
||||
);
|
||||
|
||||
if (get_interface_result == SL_RESULT_SUCCESS) {
|
||||
SLint32 get_configuration_result = (*android_config)->GetConfiguration(
|
||||
android_config,
|
||||
(const SLchar *)"androidGetAudioLatency",
|
||||
&value_size,
|
||||
&audio_latency
|
||||
);
|
||||
|
||||
if (get_configuration_result == SL_RESULT_SUCCESS) {
|
||||
p->audio_latency = (double)audio_latency / 1000.0;
|
||||
MP_INFO(ao, "Device latency is %f\n", p->audio_latency);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
error:
|
||||
@@ -207,26 +225,15 @@ error:
|
||||
|
||||
#undef CHK
|
||||
|
||||
static void set_play_state(struct ao *ao, SLuint32 state)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
SLresult res = (*p->play)->SetPlayState(p->play, state);
|
||||
if (res != SL_RESULT_SUCCESS)
|
||||
MP_ERR(ao, "Failed to SetPlayState(%d): %d\n", state, res);
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
set_play_state(ao, SL_PLAYSTATE_STOPPED);
|
||||
struct priv *p = ao->priv;
|
||||
(*p->buffer_queue)->Clear(p->buffer_queue);
|
||||
}
|
||||
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
set_play_state(ao, SL_PLAYSTATE_PLAYING);
|
||||
|
||||
// enqueue two buffers
|
||||
buffer_callback(p->buffer_queue, ao);
|
||||
buffer_callback(p->buffer_queue, ao);
|
||||
}
|
||||
|
||||
@@ -242,8 +249,7 @@ const struct ao_driver audio_out_opensles = {
|
||||
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("frames-per-buffer", cfg_frames_per_buffer, 0, 1, 10000),
|
||||
OPT_INTRANGE("sample-rate", cfg_sample_rate, 0, 1000, 100000),
|
||||
OPT_INTRANGE("frames-per-buffer", cfg_frames_per_buffer, 0, 1, 96000),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "opensles",
|
||||
|
||||
+6
-2
@@ -56,7 +56,11 @@
|
||||
// Define to 0 if the device must be reopened to reset it (stop all playback,
|
||||
// clear the buffer), and the device should be closed when unused.
|
||||
// Define to 1 if SNDCTL_DSP_RESET should be used to reset without close.
|
||||
#define KEEP_DEVICE (defined(SNDCTL_DSP_RESET) && !defined(__NetBSD__))
|
||||
#if defined(SNDCTL_DSP_RESET) && !defined(__NetBSD__)
|
||||
#define KEEP_DEVICE 1
|
||||
#else
|
||||
#define KEEP_DEVICE 0
|
||||
#endif
|
||||
|
||||
#define PATH_DEV_DSP "/dev/dsp"
|
||||
#define PATH_DEV_MIXER "/dev/mixer"
|
||||
@@ -311,7 +315,7 @@ static int reopen_device(struct ao *ao, bool allow_format_changes)
|
||||
}
|
||||
}
|
||||
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
int try_formats[AF_FORMAT_COUNT + 1];
|
||||
af_get_best_sample_formats(format, try_formats);
|
||||
for (int n = 0; try_formats[n]; n++) {
|
||||
format = try_formats[n];
|
||||
|
||||
@@ -831,7 +831,7 @@ const struct ao_driver audio_out_pulse = {
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.cfg_buffer = 250,
|
||||
.cfg_buffer = 100,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("host", cfg_host, 0),
|
||||
|
||||
+6
-5
@@ -113,8 +113,7 @@ static int init(struct ao *ao)
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
SDL_AudioSpec desired, obtained;
|
||||
|
||||
SDL_AudioSpec desired = {0};
|
||||
desired.format = AUDIO_S16SYS;
|
||||
for (int n = 0; fmtmap[n][0]; n++) {
|
||||
if (ao->format == fmtmap[n][0]) {
|
||||
@@ -124,8 +123,10 @@ static int init(struct ao *ao)
|
||||
}
|
||||
desired.freq = ao->samplerate;
|
||||
desired.channels = ao->channels.num;
|
||||
desired.samples = MPMIN(32768, ceil_power_of_two(ao->samplerate *
|
||||
priv->buflen));
|
||||
if (priv->buflen) {
|
||||
desired.samples = MPMIN(32768, ceil_power_of_two(ao->samplerate *
|
||||
priv->buflen));
|
||||
}
|
||||
desired.callback = audio_callback;
|
||||
desired.userdata = ao;
|
||||
|
||||
@@ -134,7 +135,7 @@ static int init(struct ao *ao)
|
||||
(int) desired.freq, (int) desired.channels,
|
||||
(int) desired.format, (int) desired.samples);
|
||||
|
||||
obtained = desired;
|
||||
SDL_AudioSpec obtained = desired;
|
||||
if (SDL_OpenAudio(&desired, &obtained)) {
|
||||
if (!ao->probing)
|
||||
MP_ERR(ao, "could not open audio: %s\n", SDL_GetError());
|
||||
|
||||
@@ -97,7 +97,7 @@ static const struct wasapi_sample_fmt wasapi_formats[] = {
|
||||
static void wasapi_get_best_sample_formats(
|
||||
int src_format, struct wasapi_sample_fmt *out_formats)
|
||||
{
|
||||
int mp_formats[AF_FORMAT_COUNT];
|
||||
int mp_formats[AF_FORMAT_COUNT + 1];
|
||||
af_get_best_sample_formats(src_format, mp_formats);
|
||||
for (int n = 0; mp_formats[n]; n++) {
|
||||
for (int i = 0; wasapi_formats[i].mp_format; i++) {
|
||||
|
||||
@@ -130,6 +130,10 @@ struct ao_driver {
|
||||
const char *name;
|
||||
// Description shown with --ao=help.
|
||||
const char *description;
|
||||
// This requires waiting for a AO_EVENT_INITIAL_UNBLOCK event before the
|
||||
// first play() call is done. Encode mode uses this, and push mode
|
||||
// respects it automatically (don't use with pull mode).
|
||||
bool initially_blocked;
|
||||
// Init the device using ao->format/ao->channels/ao->samplerate. If the
|
||||
// device doesn't accept these parameters, you can attempt to negotiate
|
||||
// fallback parameters, and set the ao format fields accordingly.
|
||||
|
||||
+16
-1
@@ -56,6 +56,7 @@ struct ao_push_state {
|
||||
bool still_playing;
|
||||
bool need_wakeup;
|
||||
bool paused;
|
||||
bool initial_unblocked;
|
||||
|
||||
// Whether the current buffer contains the complete audio.
|
||||
bool final_chunk;
|
||||
@@ -357,7 +358,8 @@ static void *playthread(void *arg)
|
||||
mpthread_set_name("ao");
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->terminate) {
|
||||
bool playing = !p->paused || ao->stream_silence;
|
||||
bool blocked = ao->driver->initially_blocked && !p->initial_unblocked;
|
||||
bool playing = (!p->paused || ao->stream_silence) && !blocked;
|
||||
if (playing)
|
||||
ao_play_data(ao);
|
||||
|
||||
@@ -502,6 +504,19 @@ int ao_play_silence(struct ao *ao, int samples)
|
||||
return ao->driver->play(ao, (void **)p->silence, samples, 0);
|
||||
}
|
||||
|
||||
void ao_unblock(struct ao *ao)
|
||||
{
|
||||
if (ao->api == &ao_api_push) {
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
pthread_mutex_lock(&p->lock);
|
||||
p->need_wakeup = true;
|
||||
p->initial_unblocked = true;
|
||||
wakeup_playthread(ao);
|
||||
pthread_cond_signal(&p->wakeup);
|
||||
pthread_mutex_unlock(&p->lock);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __MINGW32__
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
+2
-2
@@ -5,10 +5,10 @@
|
||||
from __future__ import print_function
|
||||
import os, sys, stat, hashlib, subprocess
|
||||
|
||||
WAFRELEASE = "waf-1.9.8"
|
||||
WAFRELEASE = "waf-2.0.9"
|
||||
WAFURLS = ["https://waf.io/" + WAFRELEASE,
|
||||
"http://www.freehackers.org/~tnagy/release/" + WAFRELEASE]
|
||||
SHA256HASH = "167dc42bab6d5bd823b798af195420319cb5c9b571e00db7d83df2a0fe1f4dbf"
|
||||
SHA256HASH = "2a8e0816f023995e557f79ea8940d322bec18f286917c8f9a6fa2dc3875dfa48"
|
||||
|
||||
if os.path.exists("waf"):
|
||||
wafver = subprocess.check_output([sys.executable, './waf', '--version']).decode()
|
||||
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
_mingw_sysroot=/usr/$TARGET/sysroot
|
||||
_mingw_prefix=$_mingw_sysroot/mingw
|
||||
_mingw_exec_prefix=$_mingw_prefix
|
||||
_mingw_libdir=$_mingw_exec_prefix/lib
|
||||
_mingw_datadir=$_mingw_prefix/share
|
||||
|
||||
export PKG_CONFIG_PATH="$_mingw_libdir/pkgconfig:$_mingw_datadir/pkgconfig";
|
||||
export CC=$TARGET-gcc
|
||||
export CXX=$TARGET-g++
|
||||
export AR=$TARGET-ar
|
||||
export NM=$TARGET-nm
|
||||
export RANLIB=$TARGET-ranlib
|
||||
export CFLAGS="-O2 -mtune=intel -g -ggdb -pipe -Wall --param=ssp-buffer-size=4 -mms-bitfields -fmessage-length=0 -D_FORTIFY_SOURCE=2 -fexceptions -fasynchronous-unwind-tables -fstack-protector-strong -fno-ident"
|
||||
export LDFLAGS="-Wl,--no-keep-memory -fstack-protector-strong"
|
||||
|
||||
./bootstrap.py
|
||||
./waf configure \
|
||||
--enable-static-build \
|
||||
--enable-libmpv-shared \
|
||||
--enable-lua \
|
||||
--enable-javascript \
|
||||
--enable-libarchive \
|
||||
--enable-libass \
|
||||
--enable-libbluray \
|
||||
--enable-dvdread \
|
||||
--enable-dvdnav \
|
||||
--enable-uchardet \
|
||||
--enable-vulkan \
|
||||
--enable-shaderc \
|
||||
--enable-rubberband \
|
||||
--enable-lcms2
|
||||
./waf build --verbose
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
./bootstrap.py
|
||||
./waf configure \
|
||||
--enable-cdda \
|
||||
--enable-dvbin \
|
||||
--enable-dvdnav \
|
||||
--enable-dvdread \
|
||||
--enable-libarchive \
|
||||
--enable-libmpv-shared \
|
||||
--enable-libsmbclient \
|
||||
--enable-manpage-build \
|
||||
--enable-shaderc \
|
||||
--enable-vulkan \
|
||||
--enable-zsh-comp
|
||||
./waf build --verbose
|
||||
+36
-61
@@ -26,6 +26,7 @@
|
||||
#include <libavutil/error.h>
|
||||
#include <libavutil/cpu.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
@@ -33,6 +34,7 @@
|
||||
#include "common/msg.h"
|
||||
#include "demux/packet.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "video/fmt-conversion.h"
|
||||
#include "av_common.h"
|
||||
#include "codecs.h"
|
||||
@@ -217,33 +219,47 @@ void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads
|
||||
avctx->thread_count = threads;
|
||||
}
|
||||
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
|
||||
static void add_codecs(struct mp_decoder_list *list, enum AVMediaType type,
|
||||
bool decoders)
|
||||
{
|
||||
AVCodec *cur = NULL;
|
||||
void *iter = NULL;
|
||||
for (;;) {
|
||||
cur = av_codec_next(cur);
|
||||
const AVCodec *cur = av_codec_iterate(&iter);
|
||||
if (!cur)
|
||||
break;
|
||||
if (av_codec_is_decoder(cur) && cur->type == type) {
|
||||
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
|
||||
if (av_codec_is_decoder(cur) == decoders &&
|
||||
(type == AVMEDIA_TYPE_UNKNOWN || cur->type == type))
|
||||
{
|
||||
mp_add_decoder(list, mp_codec_from_av_codec_id(cur->id),
|
||||
cur->name, cur->long_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type)
|
||||
{
|
||||
add_codecs(list, type, true);
|
||||
}
|
||||
|
||||
// (Abuses the decoder list data structures.)
|
||||
void mp_add_lavc_encoders(struct mp_decoder_list *list)
|
||||
{
|
||||
AVCodec *cur = NULL;
|
||||
add_codecs(list, AVMEDIA_TYPE_UNKNOWN, false);
|
||||
}
|
||||
|
||||
char **mp_get_lavf_demuxers(void)
|
||||
{
|
||||
char **list = NULL;
|
||||
void *iter = NULL;
|
||||
int num = 0;
|
||||
for (;;) {
|
||||
cur = av_codec_next(cur);
|
||||
const AVInputFormat *cur = av_demuxer_iterate(&iter);
|
||||
if (!cur)
|
||||
break;
|
||||
if (av_codec_is_encoder(cur)) {
|
||||
mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id),
|
||||
cur->name, cur->long_name);
|
||||
}
|
||||
MP_TARRAY_APPEND(NULL, list, num, talloc_strdup(NULL, cur->name));
|
||||
}
|
||||
MP_TARRAY_APPEND(NULL, list, num, NULL);
|
||||
return list;
|
||||
}
|
||||
|
||||
int mp_codec_to_av_codec_id(const char *codec)
|
||||
@@ -297,7 +313,7 @@ void mp_avdict_print_unset(struct mp_log *log, int msgl, AVDictionary *dict)
|
||||
// to the name of the n-th parameter.
|
||||
static void resolve_positional_arg(void *avobj, char **name)
|
||||
{
|
||||
if (!*name || (*name)[0] != '@')
|
||||
if (!*name || (*name)[0] != '@' || !avobj)
|
||||
return;
|
||||
|
||||
char *end = NULL;
|
||||
@@ -328,12 +344,19 @@ static void resolve_positional_arg(void *avobj, char **name)
|
||||
// Options which fail to set (error or not found) are printed to log.
|
||||
// Returns: >=0 success, <0 failed to set an option
|
||||
int mp_set_avopts(struct mp_log *log, void *avobj, char **kv)
|
||||
{
|
||||
return mp_set_avopts_pos(log, avobj, avobj, kv);
|
||||
}
|
||||
|
||||
// Like mp_set_avopts(), but the posargs argument is used to resolve positional
|
||||
// arguments. If posargs==NULL, positional args are disabled.
|
||||
int mp_set_avopts_pos(struct mp_log *log, void *avobj, void *posargs, char **kv)
|
||||
{
|
||||
int success = 0;
|
||||
for (int n = 0; kv && kv[n * 2]; n++) {
|
||||
char *k = kv[n * 2 + 0];
|
||||
char *v = kv[n * 2 + 1];
|
||||
resolve_positional_arg(avobj, &k);
|
||||
resolve_positional_arg(posargs, &k);
|
||||
int r = av_opt_set(avobj, k, v, AV_OPT_SEARCH_CHILDREN);
|
||||
if (r == AVERROR_OPTION_NOT_FOUND) {
|
||||
mp_err(log, "AVOption '%s' not found.\n", k);
|
||||
@@ -347,51 +370,3 @@ int mp_set_avopts(struct mp_log *log, void *avobj, char **kv)
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
AVFrameSideData *ffmpeg_garbage(AVFrame *frame,
|
||||
enum AVFrameSideDataType type,
|
||||
AVBufferRef *buf)
|
||||
{
|
||||
AVFrameSideData *ret, **tmp;
|
||||
|
||||
if (!buf)
|
||||
return NULL;
|
||||
|
||||
if (frame->nb_side_data > INT_MAX / sizeof(*frame->side_data) - 1)
|
||||
goto fail;
|
||||
|
||||
tmp = av_realloc(frame->side_data,
|
||||
(frame->nb_side_data + 1) * sizeof(*frame->side_data));
|
||||
if (!tmp)
|
||||
goto fail;
|
||||
frame->side_data = tmp;
|
||||
|
||||
ret = av_mallocz(sizeof(*ret));
|
||||
if (!ret)
|
||||
goto fail;
|
||||
|
||||
ret->buf = buf;
|
||||
ret->data = ret->buf->data;
|
||||
ret->size = buf->size;
|
||||
ret->type = type;
|
||||
|
||||
frame->side_data[frame->nb_side_data++] = ret;
|
||||
|
||||
return ret;
|
||||
fail:
|
||||
av_buffer_unref(&buf);
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
AVFrameSideData *ffmpeg_garbage(AVFrame *frame,
|
||||
enum AVFrameSideDataType type,
|
||||
AVBufferRef *buf)
|
||||
{
|
||||
AVFrameSideData *sd = av_frame_new_side_data(frame, type, buf->size);
|
||||
if (sd)
|
||||
memcpy(sd->data, buf->data, buf->size);
|
||||
av_buffer_unref(&buf);
|
||||
return sd;
|
||||
}
|
||||
#endif
|
||||
+2
-3
@@ -41,13 +41,12 @@ double mp_pts_from_av(int64_t av_pts, AVRational *tb);
|
||||
void mp_set_avcodec_threads(struct mp_log *l, AVCodecContext *avctx, int threads);
|
||||
void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type);
|
||||
void mp_add_lavc_encoders(struct mp_decoder_list *list);
|
||||
char **mp_get_lavf_demuxers(void);
|
||||
int mp_codec_to_av_codec_id(const char *codec);
|
||||
const char *mp_codec_from_av_codec_id(int codec_id);
|
||||
void mp_set_avdict(struct AVDictionary **dict, char **kv);
|
||||
void mp_avdict_print_unset(struct mp_log *log, int msgl, struct AVDictionary *d);
|
||||
int mp_set_avopts(struct mp_log *log, void *avobj, char **kv);
|
||||
AVFrameSideData *ffmpeg_garbage(AVFrame *frame,
|
||||
enum AVFrameSideDataType type,
|
||||
AVBufferRef *buf);
|
||||
int mp_set_avopts_pos(struct mp_log *log, void *avobj, void *posargs, char **kv);
|
||||
|
||||
#endif
|
||||
+2
-5
@@ -93,10 +93,10 @@ static struct mp_log *get_av_log(void *ptr)
|
||||
AVCodecContext *s = ptr;
|
||||
if (s->codec) {
|
||||
if (s->codec->type == AVMEDIA_TYPE_AUDIO) {
|
||||
if (s->codec->decode)
|
||||
if (av_codec_is_decoder(s->codec))
|
||||
return log_decaudio;
|
||||
} else if (s->codec->type == AVMEDIA_TYPE_VIDEO) {
|
||||
if (s->codec->decode)
|
||||
if (av_codec_is_decoder(s->codec))
|
||||
return log_decvideo;
|
||||
}
|
||||
}
|
||||
@@ -159,10 +159,7 @@ void init_libav(struct mpv_global *global)
|
||||
}
|
||||
pthread_mutex_unlock(&log_lock);
|
||||
|
||||
avcodec_register_all();
|
||||
av_register_all();
|
||||
avformat_network_init();
|
||||
avfilter_register_all();
|
||||
|
||||
#if HAVE_LIBAVDEVICE
|
||||
avdevice_register_all();
|
||||
|
||||
+3
-9
@@ -21,11 +21,10 @@
|
||||
#include "common/msg.h"
|
||||
#include "codecs.h"
|
||||
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *family,
|
||||
const char *codec, const char *decoder, const char *desc)
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *codec,
|
||||
const char *decoder, const char *desc)
|
||||
{
|
||||
struct mp_decoder_entry entry = {
|
||||
.family = talloc_strdup(list, family),
|
||||
.codec = talloc_strdup(list, codec),
|
||||
.decoder = talloc_strdup(list, decoder),
|
||||
.desc = talloc_strdup(list, desc),
|
||||
@@ -40,7 +39,7 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
{
|
||||
if (!entry || (codec && strcmp(entry->codec, codec) != 0))
|
||||
return;
|
||||
mp_add_decoder(to, entry->family, entry->codec, entry->decoder, entry->desc);
|
||||
mp_add_decoder(to, entry->codec, entry->decoder, entry->desc);
|
||||
}
|
||||
|
||||
// Select a decoder from the given list for the given codec. The selection
|
||||
@@ -72,11 +71,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
if (bstr_find0(entry, ":") >= 0) {
|
||||
mp_err(log, "Codec family selection was removed. "
|
||||
"Pass the codec name directly.\n");
|
||||
break;
|
||||
}
|
||||
for (int n = 0; n < all->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &all->entries[n];
|
||||
if (bstr_equals0(entry, cur->decoder))
|
||||
|
||||
+2
-3
@@ -21,7 +21,6 @@
|
||||
struct mp_log;
|
||||
|
||||
struct mp_decoder_entry {
|
||||
const char *family; // decoder module (e.g. ad_lavc => "lavc")
|
||||
const char *codec; // name of the codec (e.g. "mp3")
|
||||
const char *decoder; // decoder name (e.g. "mp3float")
|
||||
const char *desc; // human readable description
|
||||
@@ -32,8 +31,8 @@ struct mp_decoder_list {
|
||||
int num_entries;
|
||||
};
|
||||
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *family,
|
||||
const char *codec, const char *decoder, const char *desc);
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *codec,
|
||||
const char *decoder, const char *desc);
|
||||
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
struct mp_decoder_list *all,
|
||||
|
||||
+18
-2
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
@@ -48,14 +49,17 @@ char *mp_format_time_fmt(const char *fmt, double time)
|
||||
time = time < 0 ? -time : time;
|
||||
long long int itime = time;
|
||||
long long int h, m, tm, s;
|
||||
int ms;
|
||||
int ms = lrint((time - itime) * 1000);
|
||||
if (ms >= 1000) {
|
||||
ms -= 1000;
|
||||
itime += 1;
|
||||
}
|
||||
s = itime;
|
||||
tm = s / 60;
|
||||
h = s / 3600;
|
||||
s -= h * 3600;
|
||||
m = s / 60;
|
||||
s -= m * 60;
|
||||
ms = (time - itime) * 1000;
|
||||
char *res = talloc_strdup(NULL, "");
|
||||
while (*fmt) {
|
||||
if (fmt[0] == '%') {
|
||||
@@ -68,6 +72,7 @@ char *mp_format_time_fmt(const char *fmt, double time)
|
||||
case 's': appendf(&res, "%s%lld", sign, itime); break;
|
||||
case 'S': appendf(&res, "%02lld", s); break;
|
||||
case 'T': appendf(&res, "%03d", ms); break;
|
||||
case 'f': appendf(&res, "%f", time); break;
|
||||
case '%': appendf(&res, "%s", "%"); break;
|
||||
default: goto error;
|
||||
}
|
||||
@@ -302,3 +307,14 @@ char *mp_tprintf_buf(char *buf, size_t buf_size, const char *format, ...)
|
||||
va_end(ap);
|
||||
return buf;
|
||||
}
|
||||
|
||||
char **mp_dup_str_array(void *tctx, char **s)
|
||||
{
|
||||
char **r = NULL;
|
||||
int num_r = 0;
|
||||
for (int n = 0; s && s[n]; n++)
|
||||
MP_TARRAY_APPEND(tctx, r, num_r, talloc_strdup(tctx, s[n]));
|
||||
if (r)
|
||||
MP_TARRAY_APPEND(tctx, r, num_r, NULL);
|
||||
return r;
|
||||
}
|
||||
+14
-8
@@ -58,16 +58,9 @@ enum stream_type {
|
||||
STREAM_TYPE_COUNT,
|
||||
};
|
||||
|
||||
enum {
|
||||
DATA_OK = 1, // data is actually being returned
|
||||
DATA_WAIT = 0, // async wait: check state again after next wakeup
|
||||
DATA_AGAIN = -2, // repeat request (internal progress was made)
|
||||
DATA_STARVE = -1, // need input (might require to drain other outputs)
|
||||
DATA_EOF = -3, // no more data available
|
||||
};
|
||||
|
||||
extern const char mpv_version[];
|
||||
extern const char mpv_builddate[];
|
||||
extern const char mpv_copyright[];
|
||||
|
||||
char *mp_format_time(double time, bool fractions);
|
||||
char *mp_format_time_fmt(const char *fmt, double time);
|
||||
@@ -111,4 +104,17 @@ char *mp_tag_str_buf(char *buf, size_t buf_size, uint32_t tag);
|
||||
char *mp_tprintf_buf(char *buf, size_t buf_size, const char *format, ...)
|
||||
PRINTF_ATTRIBUTE(3, 4);
|
||||
|
||||
char **mp_dup_str_array(void *tctx, char **s);
|
||||
|
||||
// We generally do not handle allocation failure of small malloc()s. This would
|
||||
// create a large number of rarely tested code paths, which would probably
|
||||
// regress and cause security issues. We prefer to fail fast.
|
||||
// This macro generally behaves like an assert(), except it will make sure to
|
||||
// kill the process even with NDEBUG.
|
||||
#define MP_HANDLE_OOM(x) do { \
|
||||
assert(x); \
|
||||
if (!(x)) \
|
||||
abort(); \
|
||||
} while (0)
|
||||
|
||||
#endif /* MPLAYER_MPCOMMON_H */
|
||||
+10
-14
@@ -34,36 +34,32 @@ struct encode_opts {
|
||||
char *file;
|
||||
char *format;
|
||||
char **fopts;
|
||||
float fps;
|
||||
float maxfps;
|
||||
char *vcodec;
|
||||
char **vopts;
|
||||
char *acodec;
|
||||
char **aopts;
|
||||
int harddup;
|
||||
float voffset;
|
||||
float aoffset;
|
||||
int copyts;
|
||||
int rawts;
|
||||
int autofps;
|
||||
int neverdrop;
|
||||
int video_first;
|
||||
int audio_first;
|
||||
int metadata;
|
||||
int copy_metadata;
|
||||
char **set_metadata;
|
||||
char **remove_metadata;
|
||||
};
|
||||
|
||||
// interface for mplayer.c
|
||||
struct encode_lavc_context *encode_lavc_init(struct encode_opts *options,
|
||||
struct mpv_global *global);
|
||||
void encode_lavc_finish(struct encode_lavc_context *ctx);
|
||||
void encode_lavc_free(struct encode_lavc_context *ctx);
|
||||
// interface for player core
|
||||
struct encode_lavc_context *encode_lavc_init(struct mpv_global *global);
|
||||
bool encode_lavc_free(struct encode_lavc_context *ctx);
|
||||
void encode_lavc_discontinuity(struct encode_lavc_context *ctx);
|
||||
bool encode_lavc_showhelp(struct mp_log *log, struct encode_opts *options);
|
||||
int encode_lavc_getstatus(struct encode_lavc_context *ctx, char *buf, int bufsize, float relative_position);
|
||||
void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int mt);
|
||||
void encode_lavc_expect_stream(struct encode_lavc_context *ctx,
|
||||
enum stream_type type);
|
||||
void encode_lavc_stream_eof(struct encode_lavc_context *ctx,
|
||||
enum stream_type type);
|
||||
void encode_lavc_set_metadata(struct encode_lavc_context *ctx,
|
||||
struct mp_tags *metadata);
|
||||
void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps);
|
||||
void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts);
|
||||
bool encode_lavc_didfail(struct encode_lavc_context *ctx); // check if encoding failed
|
||||
|
||||
|
||||
+611
-843
File diff suppressed because it is too large.
Load diff
+56
-65
@@ -31,43 +31,26 @@
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "encode.h"
|
||||
#include "video/csputils.h"
|
||||
|
||||
struct encode_lavc_context {
|
||||
// --- Immutable after init
|
||||
struct mpv_global *global;
|
||||
struct encode_opts *options;
|
||||
struct mp_log *log;
|
||||
struct mp_tags *metadata;
|
||||
struct encode_priv *priv;
|
||||
AVOutputFormat *oformat;
|
||||
const char *filename;
|
||||
|
||||
// All entry points must be guarded with the lock. Functions called by
|
||||
// the playback core lock this automatically, but ao_lavc.c and vo_lavc.c
|
||||
// must lock manually before accessing state.
|
||||
pthread_mutex_t lock;
|
||||
|
||||
float vo_fps;
|
||||
|
||||
// FFmpeg contexts.
|
||||
AVFormatContext *avc;
|
||||
AVStream *vst;
|
||||
AVStream *ast;
|
||||
AVCodecContext *vcc;
|
||||
AVCodecContext *acc;
|
||||
|
||||
// these are processed from the options
|
||||
AVRational timebase;
|
||||
AVCodec *vc;
|
||||
AVCodec *ac;
|
||||
AVDictionary *foptions;
|
||||
AVDictionary *aoptions;
|
||||
AVDictionary *voptions;
|
||||
|
||||
// values created during encoding
|
||||
int header_written; // -1 means currently writing
|
||||
|
||||
// sync to audio mode
|
||||
double audio_pts_offset;
|
||||
double last_video_in_pts;
|
||||
|
||||
double last_audio_in_pts;
|
||||
int64_t samples_since_last_pts;
|
||||
@@ -75,51 +58,59 @@ struct encode_lavc_context {
|
||||
// anti discontinuity mode
|
||||
double next_in_pts;
|
||||
double discontinuity_pts_offset;
|
||||
|
||||
long long abytes;
|
||||
long long vbytes;
|
||||
struct stream *twopass_bytebuffer_a;
|
||||
struct stream *twopass_bytebuffer_v;
|
||||
double t0;
|
||||
unsigned int frames;
|
||||
double audioseconds;
|
||||
|
||||
bool expect_video;
|
||||
bool expect_audio;
|
||||
bool video_first;
|
||||
bool audio_first;
|
||||
|
||||
// has encoding failed?
|
||||
bool failed;
|
||||
bool finished;
|
||||
};
|
||||
|
||||
// interface for vo/ao drivers
|
||||
int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
enum AVMediaType mt, AVStream **stream_out,
|
||||
AVCodecContext **codec_out);
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *stream);
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
|
||||
AVPacket *packet);
|
||||
int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, enum AVPixelFormat format);
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
int encode_lavc_available(struct encode_lavc_context *ctx);
|
||||
int encode_lavc_timesyncfailed(struct encode_lavc_context *ctx);
|
||||
int encode_lavc_start(struct encode_lavc_context *ctx); // returns 1 on success
|
||||
int encode_lavc_oformat_flags(struct encode_lavc_context *ctx);
|
||||
double encode_lavc_getoffset(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...); // report failure of encoding
|
||||
// --- interface for vo/ao drivers
|
||||
|
||||
bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec, enum mp_csp csp);
|
||||
bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec, enum mp_csp_levels lev);
|
||||
enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec);
|
||||
// Static information after encoder init. This never changes (even if there are
|
||||
// dynamic runtime changes, they have to work over AVPacket side data).
|
||||
// For use in encoder_context, most fields are copied from encoder_context.encoder
|
||||
// by encoder_init_codec_and_muxer().
|
||||
struct encoder_stream_info {
|
||||
AVRational timebase; // timebase used by the encoder (in frames/out packets)
|
||||
AVCodecParameters *codecpar;
|
||||
};
|
||||
|
||||
// The encoder parts for each stream (no muxing parts included).
|
||||
// This is private to each stream.
|
||||
struct encoder_context {
|
||||
struct mpv_global *global;
|
||||
struct encode_opts *options;
|
||||
struct mp_log *log;
|
||||
AVOutputFormat *oformat;
|
||||
|
||||
// (avoid using this)
|
||||
struct encode_lavc_context *encode_lavc_ctx;
|
||||
|
||||
enum stream_type type;
|
||||
|
||||
// (different access restrictions before/after encoder init)
|
||||
struct encoder_stream_info info;
|
||||
AVCodecContext *encoder;
|
||||
struct mux_stream *mux_stream;
|
||||
|
||||
struct stream *twopass_bytebuffer;
|
||||
};
|
||||
|
||||
// Free with talloc_free(). (Keep in mind actual deinitialization requires
|
||||
// sending a flush packet.)
|
||||
// This can fail and return NULL.
|
||||
struct encoder_context *encoder_context_alloc(struct encode_lavc_context *ctx,
|
||||
enum stream_type type,
|
||||
struct mp_log *log);
|
||||
|
||||
// After setting your codec parameters on p->encoder, you call this to "open"
|
||||
// the encoder. This also initializes p->mux_stream. Returns false on failure.
|
||||
// on_ready is called as soon as the muxer has been initialized. Then you are
|
||||
// allowed to write packets with encoder_encode().
|
||||
// Warning: the on_ready callback is called asynchronously, so you need to
|
||||
// make sure to properly synchronize everything.
|
||||
bool encoder_init_codec_and_muxer(struct encoder_context *p,
|
||||
void (*on_ready)(void *ctx), void *ctx);
|
||||
|
||||
// Encode the frame and write the packet. frame is ref'ed as need.
|
||||
bool encoder_encode(struct encoder_context *p, AVFrame *frame);
|
||||
|
||||
double encoder_get_offset(struct encoder_context *p);
|
||||
|
||||
#endif
|
||||
+34
-7
@@ -18,6 +18,7 @@
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <strings.h>
|
||||
#include <assert.h>
|
||||
#include <libavutil/dict.h>
|
||||
#include "tags.h"
|
||||
#include "misc/bstr.h"
|
||||
@@ -44,6 +45,24 @@ void mp_tags_set_bstr(struct mp_tags *tags, bstr key, bstr value)
|
||||
tags->num_keys++;
|
||||
}
|
||||
|
||||
void mp_tags_remove_str(struct mp_tags *tags, const char *key)
|
||||
{
|
||||
mp_tags_remove_bstr(tags, bstr0(key));
|
||||
}
|
||||
|
||||
void mp_tags_remove_bstr(struct mp_tags *tags, bstr key)
|
||||
{
|
||||
for (int n = 0; n < tags->num_keys; n++) {
|
||||
if (bstrcasecmp0(key, tags->keys[n]) == 0) {
|
||||
talloc_free(tags->keys[n]);
|
||||
talloc_free(tags->values[n]);
|
||||
int num_keys = tags->num_keys; // copy so it's only decremented once
|
||||
MP_TARRAY_REMOVE_AT(tags->keys, num_keys, n);
|
||||
MP_TARRAY_REMOVE_AT(tags->values, tags->num_keys, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char *mp_tags_get_str(struct mp_tags *tags, const char *key)
|
||||
{
|
||||
return mp_tags_get_bstr(tags, bstr0(key));
|
||||
@@ -64,19 +83,27 @@ void mp_tags_clear(struct mp_tags *tags)
|
||||
talloc_free_children(tags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct mp_tags *mp_tags_dup(void *tparent, struct mp_tags *tags)
|
||||
{
|
||||
struct mp_tags *new = talloc_zero(tparent, struct mp_tags);
|
||||
MP_RESIZE_ARRAY(new, new->keys, tags->num_keys);
|
||||
MP_RESIZE_ARRAY(new, new->values, tags->num_keys);
|
||||
new->num_keys = tags->num_keys;
|
||||
for (int n = 0; n < tags->num_keys; n++) {
|
||||
new->keys[n] = talloc_strdup(new, tags->keys[n]);
|
||||
new->values[n] = talloc_strdup(new, tags->values[n]);
|
||||
}
|
||||
mp_tags_replace(new, tags);
|
||||
return new;
|
||||
}
|
||||
|
||||
void mp_tags_replace(struct mp_tags *dst, struct mp_tags *src)
|
||||
{
|
||||
mp_tags_clear(dst);
|
||||
MP_RESIZE_ARRAY(dst, dst->keys, src->num_keys);
|
||||
MP_RESIZE_ARRAY(dst, dst->values, src->num_keys);
|
||||
dst->num_keys = src->num_keys;
|
||||
for (int n = 0; n < src->num_keys; n++) {
|
||||
dst->keys[n] = talloc_strdup(dst, src->keys[n]);
|
||||
dst->values[n] = talloc_strdup(dst, src->values[n]);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a copy of the tags, but containing only keys in list. Also forces
|
||||
// the order and casing of the keys (for cosmetic reasons).
|
||||
// A trailing '*' matches the rest.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef MP_TAGS_H
|
||||
#define MP_TAGS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "misc/bstr.h"
|
||||
|
||||
struct mp_tags {
|
||||
@@ -11,10 +13,13 @@ struct mp_tags {
|
||||
|
||||
void mp_tags_set_str(struct mp_tags *tags, const char *key, const char *value);
|
||||
void mp_tags_set_bstr(struct mp_tags *tags, bstr key, bstr value);
|
||||
void mp_tags_remove_str(struct mp_tags *tags, const char *key);
|
||||
void mp_tags_remove_bstr(struct mp_tags *tags, bstr key);
|
||||
char *mp_tags_get_str(struct mp_tags *tags, const char *key);
|
||||
char *mp_tags_get_bstr(struct mp_tags *tags, bstr key);
|
||||
void mp_tags_clear(struct mp_tags *tags);
|
||||
struct mp_tags *mp_tags_dup(void *tparent, struct mp_tags *tags);
|
||||
void mp_tags_replace(struct mp_tags *dst, struct mp_tags *src);
|
||||
struct mp_tags *mp_tags_filtered(void *tparent, struct mp_tags *tags, char **list);
|
||||
void mp_tags_merge(struct mp_tags *tags, struct mp_tags *src);
|
||||
struct AVDictionary;
|
||||
|
||||
@@ -24,3 +24,4 @@
|
||||
|
||||
const char mpv_version[] = "mpv " VERSION;
|
||||
const char mpv_builddate[] = BUILDDATE;
|
||||
const char mpv_copyright[] = MPVCOPYRIGHT;
|
||||
+10
-2
@@ -75,7 +75,7 @@ static enum cue_command read_cmd(struct bstr *data, struct bstr *out_params)
|
||||
if (rest.len && !strchr(WHITESPACE, rest.start[0]))
|
||||
continue;
|
||||
if (out_params)
|
||||
*out_params = rest;
|
||||
*out_params = bstr_lstrip(rest);
|
||||
return cue_command_strings[n].command;
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,14 @@ static char *read_quoted(void *talloc_ctx, struct bstr *data)
|
||||
return bstrto0(talloc_ctx, res);
|
||||
}
|
||||
|
||||
static struct bstr strip_quotes(struct bstr data)
|
||||
{
|
||||
bstr s = data;
|
||||
if (bstr_eatstart0(&s, "\"") && bstr_eatend0(&s, "\""))
|
||||
return s;
|
||||
return data;
|
||||
}
|
||||
|
||||
// Read a 2 digit unsigned decimal integer.
|
||||
// Return -1 on failure.
|
||||
static int read_int_2(struct bstr *data)
|
||||
@@ -193,7 +201,7 @@ struct cue_file *mp_parse_cue(struct bstr data)
|
||||
[CUE_PERFORMER] = "performer",
|
||||
};
|
||||
struct mp_tags *tags = cur_track ? cur_track->tags : f->tags;
|
||||
mp_tags_set_bstr(tags, bstr0(metanames[cmd]), param);
|
||||
mp_tags_set_bstr(tags, bstr0(metanames[cmd]), strip_quotes(param));
|
||||
break;
|
||||
}
|
||||
case CUE_INDEX: {
|
||||
|
||||
+413
-154
File diff suppressed because it is too large.
Load diff
+12
-3
@@ -53,6 +53,9 @@ struct demux_ctrl_reader_state {
|
||||
double ts_end; // approx. timestamp of end of buffered range
|
||||
int64_t total_bytes;
|
||||
int64_t fw_bytes;
|
||||
double seeking; // current low level seek target, or NOPTS
|
||||
int low_level_seeks; // number of started low level seeks
|
||||
double ts_last; // approx. timestamp of demuxer position
|
||||
// Positions that can be seeked to without incurring the latency of a low
|
||||
// level seek.
|
||||
int num_seek_ranges;
|
||||
@@ -232,8 +235,7 @@ typedef struct demuxer {
|
||||
|
||||
// Since the demuxer can run in its own thread, and the stream is not
|
||||
// thread-safe, only the demuxer is allowed to access the stream directly.
|
||||
// You can freely use demux_stream_control() to send STREAM_CTRLs, or use
|
||||
// demux_pause() to get exclusive access to the stream.
|
||||
// You can freely use demux_stream_control() to send STREAM_CTRLs.
|
||||
// Also note that the stream can get replaced if fully_read is set.
|
||||
struct stream *stream;
|
||||
} demuxer_t;
|
||||
@@ -253,6 +255,8 @@ struct demux_packet *demux_read_packet(struct sh_stream *sh);
|
||||
int demux_read_packet_async(struct sh_stream *sh, struct demux_packet **out_pkt);
|
||||
bool demux_stream_is_selected(struct sh_stream *stream);
|
||||
bool demux_has_packet(struct sh_stream *sh);
|
||||
void demux_set_stream_wakeup_cb(struct sh_stream *sh,
|
||||
void (*cb)(void *ctx), void *ctx);
|
||||
struct demux_packet *demux_read_any_packet(struct demuxer *demuxer);
|
||||
|
||||
struct sh_stream *demux_get_stream(struct demuxer *demuxer, int index);
|
||||
@@ -282,6 +286,8 @@ void demux_set_ts_offset(struct demuxer *demuxer, double offset);
|
||||
|
||||
int demux_control(struct demuxer *demuxer, int cmd, void *arg);
|
||||
|
||||
void demux_block_reading(struct demuxer *demuxer, bool block);
|
||||
|
||||
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
|
||||
double ref_pts, bool selected);
|
||||
void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect);
|
||||
@@ -297,7 +303,7 @@ void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
||||
|
||||
int demux_stream_control(demuxer_t *demuxer, int ctrl, void *arg);
|
||||
|
||||
void demux_changed(demuxer_t *demuxer, int events);
|
||||
void demux_metadata_changed(demuxer_t *demuxer);
|
||||
void demux_update(demuxer_t *demuxer);
|
||||
|
||||
void demux_disable_cache(demuxer_t *demuxer);
|
||||
@@ -312,4 +318,7 @@ bool demux_matroska_uid_cmp(struct matroska_segment_uid *a,
|
||||
|
||||
const char *stream_type_name(enum stream_type type);
|
||||
|
||||
void mp_packet_tags_unref(struct mp_packet_tags *tags);
|
||||
void mp_packet_tags_setref(struct mp_packet_tags **dst, struct mp_packet_tags *src);
|
||||
|
||||
#endif /* MPLAYER_DEMUXER_H */
|
||||
+1
-1
@@ -138,7 +138,7 @@ static struct tl_parts *parse_edl(bstr str)
|
||||
bstr type = param_vals[0]; // value, because no "="
|
||||
if (bstr_equals0(type, "mp4_dash")) {
|
||||
tl->dash = true;
|
||||
if (bstr_equals0(param_names[1], "init"))
|
||||
if (nparam > 1 && bstr_equals0(param_names[1], "init"))
|
||||
tl->init_fragment_url = bstrto0(tl, param_vals[1]);
|
||||
}
|
||||
continue;
|
||||
|
||||
+58
-41
@@ -50,6 +50,12 @@
|
||||
#include "options/m_option.h"
|
||||
#include "options/path.h"
|
||||
|
||||
#ifndef AV_DISPOSITION_TIMED_THUMBNAILS
|
||||
#define AV_DISPOSITION_TIMED_THUMBNAILS 0
|
||||
#endif
|
||||
#ifndef AV_DISPOSITION_STILL_IMAGE
|
||||
#define AV_DISPOSITION_STILL_IMAGE 0
|
||||
#endif
|
||||
|
||||
#define INITIAL_PROBE_SIZE STREAM_BUFFER_SIZE
|
||||
#define PROBE_BUF_SIZE FFMIN(STREAM_MAX_BUFFER_SIZE, 2 * 1024 * 1024)
|
||||
@@ -70,7 +76,6 @@ struct demux_lavf_opts {
|
||||
char *format;
|
||||
char **avopts;
|
||||
int hacks;
|
||||
int genptsmode;
|
||||
char *sub_cp;
|
||||
int rtsp_transport;
|
||||
};
|
||||
@@ -79,7 +84,7 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_INTRANGE("demuxer-lavf-probesize", probesize, 0, 32, INT_MAX),
|
||||
OPT_CHOICE("demuxer-lavf-probe-info", probeinfo, 0,
|
||||
({"no", 0}, {"yes", 1}, {"auto", -1})),
|
||||
({"no", 0}, {"yes", 1}, {"auto", -1}, {"nostreams", -2})),
|
||||
OPT_STRING("demuxer-lavf-format", format, 0),
|
||||
OPT_FLOATRANGE("demuxer-lavf-analyzeduration", analyzeduration, 0,
|
||||
0, 3600),
|
||||
@@ -89,8 +94,6 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
OPT_INTRANGE("demuxer-lavf-probescore", probescore, 0,
|
||||
1, AVPROBE_SCORE_MAX),
|
||||
OPT_FLAG("demuxer-lavf-hacks", hacks, 0),
|
||||
OPT_CHOICE("demuxer-lavf-genpts-mode", genptsmode, 0,
|
||||
({"lavf", 1}, {"no", 0})),
|
||||
OPT_KEYVALUELIST("demuxer-lavf-o", avopts, 0),
|
||||
OPT_STRING("sub-codepage", sub_cp, 0),
|
||||
OPT_CHOICE("rtsp-transport", rtsp_transport, 0,
|
||||
@@ -133,6 +136,8 @@ struct format_hack {
|
||||
bool clear_filepos : 1;
|
||||
bool ignore_start : 1;
|
||||
bool fix_editlists : 1;
|
||||
bool is_network : 1;
|
||||
bool no_seek : 1;
|
||||
};
|
||||
|
||||
#define BLACKLIST(fmt) {fmt, .ignore = true}
|
||||
@@ -149,6 +154,8 @@ static const struct format_hack format_hacks[] = {
|
||||
{"mp3", NULL, 24, .max_probe = true},
|
||||
|
||||
{"hls", .no_stream = true, .clear_filepos = true},
|
||||
{"dash", .no_stream = true, .clear_filepos = true},
|
||||
{"sdp", .clear_filepos = true, .is_network = true, .no_seek = true},
|
||||
{"mpeg", .use_stream_ids = true},
|
||||
{"mpegts", .use_stream_ids = true},
|
||||
|
||||
@@ -176,8 +183,6 @@ static const struct format_hack format_hacks[] = {
|
||||
BLACKLIST("bin"),
|
||||
// Useless, does not work with custom streams.
|
||||
BLACKLIST("image2"),
|
||||
// Probably a security risk.
|
||||
BLACKLIST("ffm"),
|
||||
// Image demuxers ("<name>_pipe" is detected explicitly)
|
||||
{"image2pipe", .image_format = true},
|
||||
{0}
|
||||
@@ -253,9 +258,10 @@ static int64_t mp_seek(void *opaque, int64_t pos, int whence)
|
||||
whence == SEEK_CUR ? "cur" :
|
||||
whence == SEEK_SET ? "set" : "size");
|
||||
if (whence == SEEK_END || whence == AVSEEK_SIZE) {
|
||||
int64_t end = stream_get_size(stream) + priv->init_fragment.len;
|
||||
int64_t end = stream_get_size(stream);
|
||||
if (end < 0)
|
||||
return -1;
|
||||
end += priv->init_fragment.len;
|
||||
if (whence == AVSEEK_SIZE)
|
||||
return end;
|
||||
pos += end;
|
||||
@@ -310,8 +316,9 @@ static int64_t mp_read_seek(void *opaque, int stream_idx, int64_t ts, int flags)
|
||||
static void list_formats(struct demuxer *demuxer)
|
||||
{
|
||||
MP_INFO(demuxer, "Available lavf input formats:\n");
|
||||
AVInputFormat *fmt = NULL;
|
||||
while ((fmt = av_iformat_next(fmt)))
|
||||
const AVInputFormat *fmt;
|
||||
void *iter = NULL;
|
||||
while ((fmt = av_demuxer_iterate(&iter)))
|
||||
MP_INFO(demuxer, "%15s : %s\n", fmt->name, fmt->long_name);
|
||||
}
|
||||
|
||||
@@ -434,10 +441,8 @@ static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
priv->avif->name, score, avpd.buf_size,
|
||||
forced_format ? " (forced)" : "");
|
||||
|
||||
for (int n = 0; format_hacks[n].ff_name; n++) {
|
||||
for (int n = 0; lavfdopts->hacks && format_hacks[n].ff_name; n++) {
|
||||
const struct format_hack *entry = &format_hacks[n];
|
||||
if (!lavfdopts->hacks)
|
||||
continue;
|
||||
if (!matches_avinputformat_name(priv, entry->ff_name))
|
||||
continue;
|
||||
if (entry->mime_type && strcasecmp(entry->mime_type, mime_type) != 0)
|
||||
@@ -614,7 +619,9 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
case AVMEDIA_TYPE_VIDEO: {
|
||||
sh = demux_alloc_sh_stream(STREAM_VIDEO);
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_ATTACHED_PIC) {
|
||||
if ((st->disposition & AV_DISPOSITION_ATTACHED_PIC) &&
|
||||
!(st->disposition & AV_DISPOSITION_TIMED_THUMBNAILS))
|
||||
{
|
||||
sh->attached_picture =
|
||||
new_demux_packet_from_avpacket(&st->attached_pic);
|
||||
if (sh->attached_picture) {
|
||||
@@ -706,6 +713,10 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh->default_track = true;
|
||||
if (st->disposition & AV_DISPOSITION_FORCED)
|
||||
sh->forced_track = true;
|
||||
if (st->disposition & AV_DISPOSITION_DEPENDENT)
|
||||
sh->dependent_track = true;
|
||||
if (st->disposition & AV_DISPOSITION_STILL_IMAGE)
|
||||
sh->still_image = true;
|
||||
if (priv->format_hack.use_stream_ids)
|
||||
sh->demuxer_id = st->id;
|
||||
AVDictionaryEntry *title = av_dict_get(st->metadata, "title", NULL, 0);
|
||||
@@ -716,7 +727,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
if (!sh->title && st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
|
||||
sh->title = talloc_asprintf(sh, "hearing impaired");
|
||||
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
|
||||
if (lang && lang->value)
|
||||
if (lang && lang->value && strcmp(lang->value, "und") != 0)
|
||||
sh->lang = talloc_strdup(sh, lang->value);
|
||||
sh->hls_bitrate = dict_get_decimal(st->metadata, "variant_bitrate", 0);
|
||||
if (!sh->title && sh->hls_bitrate > 0)
|
||||
@@ -737,13 +748,13 @@ static void add_new_streams(demuxer_t *demuxer)
|
||||
handle_new_stream(demuxer, priv->num_streams);
|
||||
}
|
||||
|
||||
static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
|
||||
static void update_metadata(demuxer_t *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
if (priv->avfc->event_flags & AVFMT_EVENT_FLAG_METADATA_UPDATED) {
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, priv->avfc->metadata);
|
||||
priv->avfc->event_flags = 0;
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
demux_metadata_changed(demuxer);
|
||||
}
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
@@ -800,8 +811,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
if (!avfc)
|
||||
return -1;
|
||||
|
||||
if (lavfdopts->genptsmode)
|
||||
avfc->flags |= AVFMT_FLAG_GENPTS;
|
||||
if (index_mode != 1)
|
||||
avfc->flags |= AVFMT_FLAG_IGNIDX;
|
||||
|
||||
@@ -884,8 +893,11 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
priv->avfc = avfc;
|
||||
|
||||
bool probeinfo = lavfdopts->probeinfo < 0 ?
|
||||
!priv->format_hack.skipinfo : lavfdopts->probeinfo;
|
||||
bool probeinfo = lavfdopts->probeinfo != 0;
|
||||
switch (lavfdopts->probeinfo) {
|
||||
case -2: probeinfo = priv->avfc->nb_streams == 0; break;
|
||||
case -1: probeinfo = !priv->format_hack.skipinfo; break;
|
||||
}
|
||||
if (demuxer->params && demuxer->params->skip_lavf_probing)
|
||||
probeinfo = false;
|
||||
if (probeinfo) {
|
||||
@@ -909,7 +921,6 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
add_new_streams(demuxer);
|
||||
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, avfc->metadata);
|
||||
update_metadata(demuxer, NULL);
|
||||
|
||||
demuxer->ts_resets_possible =
|
||||
priv->avif_flags & (AVFMT_TS_DISCONT | AVFMT_NOTIMESTAMPS);
|
||||
@@ -919,6 +930,14 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
demuxer->fully_read = priv->format_hack.fully_read;
|
||||
|
||||
#ifdef AVFMTCTX_UNSEEKABLE
|
||||
if (avfc->ctx_flags & AVFMTCTX_UNSEEKABLE)
|
||||
demuxer->seekable = false;
|
||||
#endif
|
||||
|
||||
demuxer->is_network |= priv->format_hack.is_network;
|
||||
demuxer->seekable &= !priv->format_hack.no_seek;
|
||||
|
||||
if (priv->avfc->duration > 0) {
|
||||
demuxer->duration = (double)priv->avfc->duration / AV_TIME_BASE;
|
||||
} else {
|
||||
@@ -939,6 +958,19 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
demuxer->duration = duration;
|
||||
}
|
||||
|
||||
// In some cases, libavformat will export bogus bullshit timestamps anyway,
|
||||
// such as with mjpeg.
|
||||
if (priv->avif_flags & AVFMT_NOTIMESTAMPS) {
|
||||
MP_WARN(demuxer,
|
||||
"This format is marked by FFmpeg as having no timestamps!\n"
|
||||
"FFmpeg will likely make up its own broken timestamps. For\n"
|
||||
"video streams you can correct this with:\n"
|
||||
" --no-correct-pts --fps=VALUE\n"
|
||||
"with VALUE being the real framerate of the stream. You can\n"
|
||||
"expect seeking and buffering estimation to be generally\n"
|
||||
"broken as well.\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -959,7 +991,7 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
}
|
||||
|
||||
add_new_streams(demux);
|
||||
update_metadata(demux, pkt);
|
||||
update_metadata(demux);
|
||||
|
||||
assert(pkt->stream_index >= 0 && pkt->stream_index < priv->num_streams);
|
||||
struct sh_stream *stream = priv->streams[pkt->stream_index];
|
||||
@@ -1108,31 +1140,16 @@ redo:
|
||||
priv->cur_program = prog->progid = program->id;
|
||||
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, priv->avfc->programs[p]->metadata);
|
||||
update_metadata(demuxer, NULL);
|
||||
update_metadata(demuxer);
|
||||
// Enforce metadata update even if no explicit METADATA_UPDATED since we switched program.
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
demux_metadata_changed(demuxer);
|
||||
|
||||
return CONTROL_OK;
|
||||
}
|
||||
case DEMUXER_CTRL_RESYNC:
|
||||
/* NOTE:
|
||||
*
|
||||
* We actually want to call ff_read_frame_flush() here, but it is
|
||||
* internal.
|
||||
*
|
||||
* This function call seems to do the same for now.
|
||||
*
|
||||
* Once ff_read_frame_flush() is exported in some way, change this to
|
||||
* call the new API instead of relying on av_seek_frame() to do this
|
||||
* for us.
|
||||
*/
|
||||
// avio_flush() is designed for write-only streams, and does the wrong
|
||||
// thing when reading. Flush it manually instead.
|
||||
stream_drop_buffers(priv->stream);
|
||||
priv->avfc->pb->buf_ptr = priv->avfc->pb->buf_end = priv->avfc->pb->buffer;
|
||||
priv->avfc->pb->pos = stream_tell(priv->stream);
|
||||
av_seek_frame(priv->avfc, 0, stream_tell(priv->stream),
|
||||
AVSEEK_FLAG_BYTE);
|
||||
avio_flush(priv->avfc->pb);
|
||||
avformat_flush(priv->avfc);
|
||||
return CONTROL_OK;
|
||||
case DEMUXER_CTRL_REPLACE_STREAM:
|
||||
if (priv->own_stream)
|
||||
|
||||
+101
-40
@@ -193,7 +193,6 @@ typedef struct mkv_demuxer {
|
||||
struct header_elem {
|
||||
int32_t id;
|
||||
int64_t pos;
|
||||
bool needed;
|
||||
bool parsed;
|
||||
} *headers;
|
||||
int num_headers;
|
||||
@@ -207,7 +206,10 @@ typedef struct mkv_demuxer {
|
||||
|
||||
bool eof_warning, keyframe_warning;
|
||||
|
||||
struct block_info tmp_block;
|
||||
// Small queue of read but not yet returned packets. This is mostly
|
||||
// temporary data, and not normally larger than 0 or 1 elements.
|
||||
struct block_info *blocks;
|
||||
int num_blocks;
|
||||
} mkv_demuxer_t;
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_mkv_opts
|
||||
@@ -251,6 +253,7 @@ const struct m_sub_options demux_mkv_conf = {
|
||||
|
||||
static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos);
|
||||
static void probe_first_timestamp(struct demuxer *demuxer);
|
||||
static int read_next_block_into_queue(demuxer_t *demuxer);
|
||||
static void free_block(struct block_info *block);
|
||||
|
||||
#define AAC_SYNC_EXTENSION_TYPE 0x02b7
|
||||
@@ -353,7 +356,7 @@ static bstr demux_mkv_decode(struct mp_log *log, mkv_track_t *track,
|
||||
dest = NULL;
|
||||
goto error;
|
||||
}
|
||||
mp_dbg(log, "lzo decompression buffer too small.\n");
|
||||
mp_trace(log, "lzo decompression buffer too small.\n");
|
||||
if (dstlen >= maxlen / 2) {
|
||||
talloc_free(dest);
|
||||
dest = NULL;
|
||||
@@ -700,7 +703,10 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
}
|
||||
track->uid = entry->track_uid;
|
||||
|
||||
track->name = talloc_strdup(track, entry->name);
|
||||
if (entry->name) {
|
||||
track->name = talloc_strdup(track, entry->name);
|
||||
MP_VERBOSE(demuxer, "| + Name: %s\n", track->name);
|
||||
}
|
||||
|
||||
track->type = entry->track_type;
|
||||
MP_VERBOSE(demuxer, "| + Track type: ");
|
||||
@@ -888,11 +894,11 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
|
||||
cue_index_add(demuxer, trackpos->cue_track, pos,
|
||||
time, trackpos->cue_duration);
|
||||
mkv_d->index_has_durations |= trackpos->n_cue_duration > 0;
|
||||
MP_DBG(demuxer, "|+ found cue point for track %"PRIu64": "
|
||||
"timecode %"PRIu64", filepos: %"PRIu64""
|
||||
"offset %"PRIu64", duration %"PRIu64"\n",
|
||||
trackpos->cue_track, time, pos,
|
||||
trackpos->cue_relative_position, trackpos->cue_duration);
|
||||
MP_TRACE(demuxer, "|+ found cue point for track %"PRIu64": "
|
||||
"timecode %"PRIu64", filepos: %"PRIu64""
|
||||
"offset %"PRIu64", duration %"PRIu64"\n",
|
||||
trackpos->cue_track, time, pos,
|
||||
trackpos->cue_relative_position, trackpos->cue_duration);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1072,7 +1078,7 @@ static int demux_mkv_read_tags(demuxer_t *demuxer)
|
||||
if (ebml_read_element(s, &parse_ctx, &tags, &ebml_tags_desc) < 0)
|
||||
return -1;
|
||||
|
||||
mkv_d->tags = talloc_memdup(mkv_d, &tags, sizeof(tags));
|
||||
mkv_d->tags = talloc_dup(mkv_d, &tags);
|
||||
talloc_steal(mkv_d->tags, parse_ctx.talloc_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -1226,8 +1232,8 @@ static int demux_mkv_read_seekhead(demuxer_t *demuxer)
|
||||
continue;
|
||||
}
|
||||
uint64_t pos = seek->seek_position + mkv_d->segment_start;
|
||||
MP_DBG(demuxer, "Element 0x%"PRIx32" at %"PRIu64".\n",
|
||||
seek->seek_id, pos);
|
||||
MP_TRACE(demuxer, "Element 0x%"PRIx32" at %"PRIu64".\n",
|
||||
seek->seek_id, pos);
|
||||
get_header_element(demuxer, seek->seek_id, pos);
|
||||
}
|
||||
out:
|
||||
@@ -1377,6 +1383,7 @@ static const char *const mkv_video_tags[][2] = {
|
||||
{"V_PRORES", "prores"},
|
||||
{"V_MPEGH/ISO/HEVC", "hevc"},
|
||||
{"V_SNOW", "snow"},
|
||||
{"V_AV1", "av1"},
|
||||
{0}
|
||||
};
|
||||
|
||||
@@ -1581,6 +1588,7 @@ static const char *const mkv_audio_tags[][2] = {
|
||||
{ "A_FLAC", "flac" },
|
||||
{ "A_ALAC", "alac" },
|
||||
{ "A_TTA1", "tta" },
|
||||
{ "A_MLP", "mlp" },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
@@ -1753,7 +1761,7 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
|
||||
const char *codec = sh_a->codec;
|
||||
if (!strcmp(codec, "mp2") || !strcmp(codec, "mp3") ||
|
||||
!strcmp(codec, "truehd"))
|
||||
!strcmp(codec, "truehd") || !strcmp(codec, "eac3"))
|
||||
{
|
||||
track->parse = true;
|
||||
} else if (!strcmp(codec, "flac")) {
|
||||
@@ -1875,6 +1883,47 @@ static int demux_mkv_open_sub(demuxer_t *demuxer, mkv_track_t *track)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void probe_x264_garbage(demuxer_t *demuxer)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
|
||||
for (int n = 0; n < mkv_d->num_tracks; n++) {
|
||||
mkv_track_t *track = mkv_d->tracks[n];
|
||||
struct sh_stream *sh = track->stream;
|
||||
|
||||
if (!sh || sh->type != STREAM_VIDEO)
|
||||
continue;
|
||||
|
||||
if (sh->codec->codec && strcmp(sh->codec->codec, "h264") != 0)
|
||||
continue;
|
||||
|
||||
struct block_info *block = NULL;
|
||||
|
||||
// Find first block for this track.
|
||||
// Restrict reading number of total packets. (Arbitrary to avoid bloat.)
|
||||
for (int i = 0; i < 100; i++) {
|
||||
if (i >= mkv_d->num_blocks && read_next_block_into_queue(demuxer) < 1)
|
||||
break;
|
||||
if (mkv_d->blocks[i].track == track) {
|
||||
block = &mkv_d->blocks[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!block || block->num_laces < 1)
|
||||
continue;
|
||||
|
||||
bstr sblock = {block->laces[0]->data, block->laces[0]->size};
|
||||
bstr nblock = demux_mkv_decode(demuxer->log, track, sblock, 1);
|
||||
|
||||
sh->codec->first_packet = new_demux_packet_from(nblock.start, nblock.len);
|
||||
talloc_steal(mkv_d, sh->codec->first_packet);
|
||||
|
||||
if (nblock.start != sblock.start)
|
||||
talloc_free(nblock.start);
|
||||
}
|
||||
}
|
||||
|
||||
static int read_ebml_header(demuxer_t *demuxer)
|
||||
{
|
||||
stream_t *s = demuxer->stream;
|
||||
@@ -1890,7 +1939,7 @@ static int read_ebml_header(demuxer_t *demuxer)
|
||||
"Assuming \"matroska\".\n");
|
||||
} else if (strcmp(ebml_master.doc_type, "matroska") != 0
|
||||
&& strcmp(ebml_master.doc_type, "webm") != 0) {
|
||||
MP_DBG(demuxer, "no head found\n");
|
||||
MP_TRACE(demuxer, "no head found\n");
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
return 0;
|
||||
}
|
||||
@@ -2030,7 +2079,6 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
elem->needed = true;
|
||||
only_cue = only_cue < 0 && elem->id == MATROSKA_ID_CUES;
|
||||
}
|
||||
|
||||
@@ -2045,7 +2093,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
struct header_elem *lowest = NULL;
|
||||
for (int n = 0; n < mkv_d->num_headers; n++) {
|
||||
struct header_elem *elem = &mkv_d->headers[n];
|
||||
if (!elem->needed)
|
||||
if (elem->parsed)
|
||||
continue;
|
||||
if (!lowest || elem->pos < lowest->pos)
|
||||
lowest = elem;
|
||||
@@ -2054,7 +2102,6 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
if (!lowest)
|
||||
break;
|
||||
|
||||
lowest->needed = false;
|
||||
if (read_deferred_element(demuxer, lowest) < 0)
|
||||
return -1;
|
||||
}
|
||||
@@ -2074,6 +2121,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
probe_first_timestamp(demuxer);
|
||||
if (mkv_d->opts->probe_duration)
|
||||
probe_last_timestamp(demuxer, start_pos);
|
||||
probe_x264_garbage(demuxer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -2285,7 +2333,9 @@ static void mkv_seek_reset(demuxer_t *demuxer)
|
||||
avcodec_free_context(&track->av_parser_codec);
|
||||
}
|
||||
|
||||
free_block(&mkv_d->tmp_block);
|
||||
for (int n = 0; n < mkv_d->num_blocks; n++)
|
||||
free_block(&mkv_d->blocks[n]);
|
||||
mkv_d->num_blocks = 0;
|
||||
|
||||
mkv_d->skip_to_timecode = INT64_MIN;
|
||||
}
|
||||
@@ -2710,8 +2760,7 @@ static int read_block_group(demuxer_t *demuxer, int64_t end,
|
||||
&ebml_block_additions_desc) < 0)
|
||||
return -1;
|
||||
if (additions.n_block_more > 0) {
|
||||
block->additions =
|
||||
talloc_memdup(NULL, &additions, sizeof(additions));
|
||||
block->additions = talloc_dup(NULL, &additions);
|
||||
talloc_steal(block->additions, parse_ctx.talloc_ctx);
|
||||
parse_ctx.talloc_ctx = NULL;
|
||||
}
|
||||
@@ -2736,16 +2785,11 @@ error:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
static int read_next_block_into_queue(demuxer_t *demuxer)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
if (mkv_d->tmp_block.num_laces) {
|
||||
*block = mkv_d->tmp_block;
|
||||
mkv_d->tmp_block = (struct block_info){0};
|
||||
return 1;
|
||||
}
|
||||
struct block_info block = {0};
|
||||
|
||||
while (1) {
|
||||
while (stream_tell(s) < mkv_d->cluster_end) {
|
||||
@@ -2764,21 +2808,21 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
end += stream_tell(s);
|
||||
if (end > mkv_d->cluster_end)
|
||||
goto find_next_cluster;
|
||||
int res = read_block_group(demuxer, end, block);
|
||||
int res = read_block_group(demuxer, end, &block);
|
||||
if (res < 0)
|
||||
goto find_next_cluster;
|
||||
if (res > 0)
|
||||
return 1;
|
||||
goto add_block;
|
||||
break;
|
||||
}
|
||||
|
||||
case MATROSKA_ID_SIMPLEBLOCK: {
|
||||
*block = (struct block_info){ .simple = true };
|
||||
int res = read_block(demuxer, mkv_d->cluster_end, block);
|
||||
block = (struct block_info){ .simple = true };
|
||||
int res = read_block(demuxer, mkv_d->cluster_end, &block);
|
||||
if (res < 0)
|
||||
goto find_next_cluster;
|
||||
if (res > 0)
|
||||
return 1;
|
||||
goto add_block;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2828,6 +2872,29 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
if (mkv_d->cluster_end != EBML_UINT_INVALID)
|
||||
mkv_d->cluster_end += stream_tell(s);
|
||||
}
|
||||
assert(0); // unreachable
|
||||
|
||||
add_block:
|
||||
index_block(demuxer, &block);
|
||||
MP_TARRAY_APPEND(mkv_d, mkv_d->blocks, mkv_d->num_blocks, block);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
|
||||
if (!mkv_d->num_blocks) {
|
||||
int res = read_next_block_into_queue(demuxer);
|
||||
if (res < 1)
|
||||
return res;
|
||||
|
||||
assert(mkv_d->num_blocks);
|
||||
}
|
||||
|
||||
*block = mkv_d->blocks[0];
|
||||
MP_TARRAY_REMOVE_AT(mkv_d->blocks, mkv_d->num_blocks, 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int demux_mkv_fill_buffer(demuxer_t *demuxer)
|
||||
@@ -2839,7 +2906,6 @@ static int demux_mkv_fill_buffer(demuxer_t *demuxer)
|
||||
if (res < 0)
|
||||
return 0;
|
||||
if (res > 0) {
|
||||
index_block(demuxer, &block);
|
||||
res = handle_block(demuxer, &block);
|
||||
free_block(&block);
|
||||
if (res > 0)
|
||||
@@ -2887,7 +2953,6 @@ static int create_index_until(struct demuxer *demuxer, int64_t timecode)
|
||||
if (res < 0)
|
||||
break;
|
||||
if (res > 0) {
|
||||
index_block(demuxer, &block);
|
||||
free_block(&block);
|
||||
}
|
||||
index = get_highest_index_entry(demuxer);
|
||||
@@ -3113,7 +3178,7 @@ static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos)
|
||||
}
|
||||
}
|
||||
|
||||
free_block(&mkv_d->tmp_block);
|
||||
mkv_seek_reset(demuxer);
|
||||
|
||||
int64_t last_ts[STREAM_TYPE_COUNT] = {0};
|
||||
while (1) {
|
||||
@@ -3151,11 +3216,7 @@ static void probe_first_timestamp(struct demuxer *demuxer)
|
||||
if (!mkv_d->opts->probe_start_time)
|
||||
return;
|
||||
|
||||
struct block_info block;
|
||||
if (read_next_block(demuxer, &block) > 0) {
|
||||
index_block(demuxer, &block);
|
||||
mkv_d->tmp_block = block;
|
||||
}
|
||||
read_next_block_into_queue(demuxer);
|
||||
|
||||
demuxer->start_time = mkv_d->cluster_tc / 1e9;
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ static int try_open_file(struct demuxer *demux, enum demux_check check)
|
||||
if (!bstr_startswith0(bstr0(demux->filename), "null://") &&
|
||||
check != DEMUX_CHECK_REQUEST)
|
||||
return -1;
|
||||
demux->seekable = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ static int demux_open_tv(demuxer_t *demuxer, enum demux_check check)
|
||||
/* get IMAGE FORMAT */
|
||||
int fourcc;
|
||||
funcs->control(tvh->priv, TVI_CONTROL_VID_GET_FORMAT, &fourcc);
|
||||
if (fourcc == MP_FOURCC_MJPEG) {
|
||||
if (fourcc == MP_FOURCC_MJPEG || fourcc == MP_FOURCC_JPEG) {
|
||||
sh_v->codec->codec = "mjpeg";
|
||||
} else {
|
||||
sh_v->codec->codec = "rawvideo";
|
||||
|
||||
+32
-31
@@ -327,7 +327,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
{
|
||||
assert(type->type == EBML_TYPE_SUBELEMENTS);
|
||||
assert(level < 8);
|
||||
MP_DBG(ctx, "%.*sParsing element %s\n", level, " ", type->name);
|
||||
MP_TRACE(ctx, "%.*sParsing element %s\n", level, " ", type->name);
|
||||
|
||||
char *s = target;
|
||||
uint8_t *end = data + size;
|
||||
@@ -340,7 +340,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
if (len > end - p)
|
||||
goto past_end_error;
|
||||
if (len < 0) {
|
||||
MP_DBG(ctx, "Error parsing subelement id\n");
|
||||
MP_ERR(ctx, "Error parsing subelement id\n");
|
||||
goto other_error;
|
||||
}
|
||||
p += len;
|
||||
@@ -348,7 +348,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
if (len > end - p)
|
||||
goto past_end_error;
|
||||
if (len < 0) {
|
||||
MP_DBG(ctx, "Error parsing subelement length\n");
|
||||
MP_ERR(ctx, "Error parsing subelement length\n");
|
||||
goto other_error;
|
||||
}
|
||||
p += len;
|
||||
@@ -368,7 +368,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
if (length > end - p) {
|
||||
if (field_idx >= 0 && type->fields[field_idx].desc->type
|
||||
!= EBML_TYPE_SUBELEMENTS) {
|
||||
MP_DBG(ctx, "Subelement content goes "
|
||||
MP_ERR(ctx, "Subelement content goes "
|
||||
"past end of containing element\n");
|
||||
goto other_error;
|
||||
}
|
||||
@@ -381,7 +381,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
continue;
|
||||
|
||||
past_end_error:
|
||||
MP_DBG(ctx, "Subelement headers go past end of containing element\n");
|
||||
MP_ERR(ctx, "Subelement headers go past end of containing element\n");
|
||||
other_error:
|
||||
ctx->has_errors = true;
|
||||
end = startp;
|
||||
@@ -437,20 +437,20 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
int len;
|
||||
uint32_t id = ebml_parse_id(data, end - data, &len);
|
||||
if (len < 0 || len > end - data) {
|
||||
MP_DBG(ctx, "Error parsing subelement\n");
|
||||
MP_ERR(ctx, "Error parsing subelement\n");
|
||||
break;
|
||||
}
|
||||
data += len;
|
||||
uint64_t length = ebml_parse_length(data, end - data, &len);
|
||||
if (len < 0 || len > end - data) {
|
||||
MP_DBG(ctx, "Error parsing subelement length\n");
|
||||
MP_ERR(ctx, "Error parsing subelement length\n");
|
||||
break;
|
||||
}
|
||||
data += len;
|
||||
if (length > end - data) {
|
||||
// Try to parse what is possible from inside this partial element
|
||||
length = end - data;
|
||||
MP_DBG(ctx, "Next subelement content goes "
|
||||
MP_ERR(ctx, "Next subelement content goes "
|
||||
"past end of containing element, will be truncated\n");
|
||||
}
|
||||
int field_idx = -1;
|
||||
@@ -460,16 +460,17 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
break;
|
||||
}
|
||||
if (field_idx < 0) {
|
||||
if (id == 0xec)
|
||||
MP_DBG(ctx, "%.*sIgnoring Void element "
|
||||
"size: %"PRIu64"\n", level+1, " ", length);
|
||||
else if (id == 0xbf)
|
||||
MP_DBG(ctx, "%.*sIgnoring CRC-32 "
|
||||
"element size: %"PRIu64"\n", level+1, " ",
|
||||
length);
|
||||
else
|
||||
if (id == 0xec) {
|
||||
MP_TRACE(ctx, "%.*sIgnoring Void element "
|
||||
"size: %"PRIu64"\n", level+1, " ", length);
|
||||
} else if (id == 0xbf) {
|
||||
MP_TRACE(ctx, "%.*sIgnoring CRC-32 "
|
||||
"element size: %"PRIu64"\n", level+1, " ",
|
||||
length);
|
||||
} else {
|
||||
MP_DBG(ctx, "Ignoring unrecognized "
|
||||
"subelement. ID: %x size: %"PRIu64"\n", id, length);
|
||||
}
|
||||
data += length;
|
||||
continue;
|
||||
}
|
||||
@@ -485,20 +486,20 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
continue;
|
||||
}
|
||||
if (*countptr > 0 && !multiple) {
|
||||
MP_DBG(ctx, "Another subelement of type "
|
||||
"%x %s (size: %"PRIu64"). Only one allowed. Ignoring.\n",
|
||||
id, ed->name, length);
|
||||
MP_WARN(ctx, "Another subelement of type "
|
||||
"%x %s (size: %"PRIu64"). Only one allowed. Ignoring.\n",
|
||||
id, ed->name, length);
|
||||
ctx->has_errors = true;
|
||||
data += length;
|
||||
continue;
|
||||
}
|
||||
MP_DBG(ctx, "%.*sParsing %x %s size: %"PRIu64
|
||||
" value: ", level+1, " ", id, ed->name, length);
|
||||
MP_TRACE(ctx, "%.*sParsing %x %s size: %"PRIu64
|
||||
" value: ", level+1, " ", id, ed->name, length);
|
||||
|
||||
char *fieldptr = s + fd->offset;
|
||||
switch (ed->type) {
|
||||
case EBML_TYPE_SUBELEMENTS:
|
||||
MP_DBG(ctx, "subelements\n");
|
||||
MP_TRACE(ctx, "subelements\n");
|
||||
char *subelptr;
|
||||
if (multiple) {
|
||||
char *array_start = (char *) *(generic_struct **) fieldptr;
|
||||
@@ -517,29 +518,29 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
subelptr = (fieldtype *) fieldptr
|
||||
GETPTR(uintptr, uint64_t);
|
||||
if (length < 1 || length > 8) {
|
||||
MP_DBG(ctx, "uint invalid length %"PRIu64"\n", length);
|
||||
MP_ERR(ctx, "uint invalid length %"PRIu64"\n", length);
|
||||
goto error;
|
||||
}
|
||||
*uintptr = ebml_parse_uint(data, length);
|
||||
MP_DBG(ctx, "uint %"PRIu64"\n", *uintptr);
|
||||
MP_TRACE(ctx, "uint %"PRIu64"\n", *uintptr);
|
||||
break;
|
||||
|
||||
case EBML_TYPE_SINT:;
|
||||
int64_t *sintptr;
|
||||
GETPTR(sintptr, int64_t);
|
||||
if (length > 8) {
|
||||
MP_DBG(ctx, "sint invalid length %"PRIu64"\n", length);
|
||||
MP_ERR(ctx, "sint invalid length %"PRIu64"\n", length);
|
||||
goto error;
|
||||
}
|
||||
*sintptr = ebml_parse_sint(data, length);
|
||||
MP_DBG(ctx, "sint %"PRId64"\n", *sintptr);
|
||||
MP_TRACE(ctx, "sint %"PRId64"\n", *sintptr);
|
||||
break;
|
||||
|
||||
case EBML_TYPE_FLOAT:;
|
||||
double *floatptr;
|
||||
GETPTR(floatptr, double);
|
||||
if (length != 0 && length != 4 && length != 8) {
|
||||
MP_DBG(ctx, "float invalid length %"PRIu64"\n", length);
|
||||
MP_ERR(ctx, "float invalid length %"PRIu64"\n", length);
|
||||
goto error;
|
||||
}
|
||||
*floatptr = ebml_parse_float(data, length);
|
||||
@@ -554,7 +555,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
char **strptr;
|
||||
GETPTR(strptr, char *);
|
||||
*strptr = talloc_strndup(ctx->talloc_ctx, data, length);
|
||||
MP_DBG(ctx, "string \"%s\"\n", *strptr);
|
||||
MP_TRACE(ctx, "string \"%s\"\n", *strptr);
|
||||
break;
|
||||
|
||||
case EBML_TYPE_BINARY:;
|
||||
@@ -566,7 +567,7 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
GETPTR(binptr, struct bstr);
|
||||
binptr->start = data;
|
||||
binptr->len = length;
|
||||
MP_DBG(ctx, "binary %zd bytes\n", binptr->len);
|
||||
MP_TRACE(ctx, "binary %zd bytes\n", binptr->len);
|
||||
break;
|
||||
|
||||
case EBML_TYPE_EBML_ID:;
|
||||
@@ -574,10 +575,10 @@ static void ebml_parse_element(struct ebml_parse_ctx *ctx, void *target,
|
||||
GETPTR(idptr, uint32_t);
|
||||
*idptr = ebml_parse_id(data, end - data, &len);
|
||||
if (len != length) {
|
||||
MP_DBG(ctx, "ebml_id broken value\n");
|
||||
MP_ERR(ctx, "ebml_id broken value\n");
|
||||
goto error;
|
||||
}
|
||||
MP_DBG(ctx, "ebml_id %x\n", (unsigned)*idptr);
|
||||
MP_TRACE(ctx, "ebml_id %x\n", (unsigned)*idptr);
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include "common/av_common.h"
|
||||
#include "common/common.h"
|
||||
#include "demux.h"
|
||||
|
||||
#include "packet.h"
|
||||
|
||||
@@ -34,6 +35,7 @@ static void packet_destroy(void *ptr)
|
||||
{
|
||||
struct demux_packet *dp = ptr;
|
||||
av_packet_unref(dp->avpacket);
|
||||
mp_packet_tags_unref(dp->metadata);
|
||||
}
|
||||
|
||||
// This actually preserves only data and side data, not PTS/DTS/pos/etc.
|
||||
@@ -78,6 +80,8 @@ struct demux_packet *new_demux_packet_from_avpacket(struct AVPacket *avpkt)
|
||||
// (buf must include proper padding)
|
||||
struct demux_packet *new_demux_packet_from_buf(struct AVBufferRef *buf)
|
||||
{
|
||||
if (!buf)
|
||||
return NULL;
|
||||
AVPacket pkt = {
|
||||
.size = buf->size,
|
||||
.data = buf->data,
|
||||
@@ -127,6 +131,7 @@ void demux_packet_copy_attribs(struct demux_packet *dst, struct demux_packet *sr
|
||||
dst->codec = src->codec;
|
||||
dst->keyframe = src->keyframe;
|
||||
dst->stream = src->stream;
|
||||
mp_packet_tags_setref(&dst->metadata, src->metadata);
|
||||
}
|
||||
|
||||
struct demux_packet *demux_copy_packet(struct demux_packet *dp)
|
||||
|
||||
@@ -44,6 +44,7 @@ typedef struct demux_packet {
|
||||
struct demux_packet *next;
|
||||
struct AVPacket *avpacket; // keep the buffer allocation and sidedata
|
||||
double kf_seek_pts; // demux.c internal: seek pts for keyframe range
|
||||
struct mp_packet_tags *metadata; // timed metadata (demux.c internal)
|
||||
} demux_packet_t;
|
||||
|
||||
struct AVBufferRef;
|
||||
|
||||
@@ -45,6 +45,8 @@ struct sh_stream {
|
||||
char *lang; // language code
|
||||
bool default_track; // container default track flag
|
||||
bool forced_track; // container forced track flag
|
||||
bool dependent_track; // container dependent track flag
|
||||
bool still_image; // video stream contains still images
|
||||
int hls_bitrate;
|
||||
|
||||
struct mp_tags *tags;
|
||||
@@ -76,6 +78,11 @@ struct mp_codec_params {
|
||||
// Timestamp granularity for converting double<->rational timestamps.
|
||||
int native_tb_num, native_tb_den;
|
||||
|
||||
// Used by an obscure bug workaround mechanism. As an exception to the usual
|
||||
// rules, demuxers are allowed to set this after adding the sh_stream, but
|
||||
// only before the demuxer open call returns.
|
||||
struct demux_packet *first_packet;
|
||||
|
||||
// STREAM_AUDIO
|
||||
int samplerate;
|
||||
struct mp_chmap channels;
|
||||
|
||||
+11
-1
@@ -17,7 +17,6 @@ idle=yes
|
||||
terminal=no
|
||||
input-terminal=no
|
||||
osc=no
|
||||
ytdl=no
|
||||
input-default-bindings=no
|
||||
input-vo-keyboard=no
|
||||
# OSX/Cocoa global input hooks
|
||||
@@ -44,6 +43,17 @@ correct-downscaling=yes
|
||||
sigmoid-upscaling=yes
|
||||
deband=yes
|
||||
|
||||
[low-latency]
|
||||
audio-buffer=0 # minimize extra audio buffer (can lead to dropouts)
|
||||
vd-lavc-threads=1 # multithreaded decoding buffers extra frames
|
||||
cache-pause=no # do not pause on underruns
|
||||
demuxer-lavf-o-add=fflags=+nobuffer # can help for weird reasons
|
||||
demuxer-lavf-probe-info=nostreams # avoid probing unless absolutely needed
|
||||
demuxer-lavf-analyzeduration=0.1 # if it probes, reduce it
|
||||
video-sync=audio # DS currently requires reading ahead a frame
|
||||
interpolation=no # requires reference frames (more buffering)
|
||||
video-latency-hacks=yes # typically 1 or 2 video frame less latency
|
||||
|
||||
# Compatibility alias (deprecated)
|
||||
[opengl-hq]
|
||||
profile=gpu-hq
|
||||
@@ -31,7 +31,7 @@
|
||||
#
|
||||
# Examples:
|
||||
# mpv --profile=enc-to-dvdpal --o=outfile.mpg infile.mkv
|
||||
# mpv --profile=enc-f-avi --ofps=30 --o=outfile.avi infile.mkv
|
||||
# mpv --profile=enc-f-avi --vf=fps=30 --o=outfile.avi infile.mkv
|
||||
# mpv --profile=enc-v-mpeg4 --ovcopts-add=qscale=7 --profile=enc-a-mp3 --oacopts-add=b=320k --o=outfile.avi infile.mkv
|
||||
|
||||
################
|
||||
@@ -105,24 +105,20 @@ ovcopts = speed=6,lag-in-frames=8,slices=2,threads=0,crf=18,qmin=0,qmax=36
|
||||
[enc-f-3gp]
|
||||
profile-desc = "H.263 + AAC (for 3GP)"
|
||||
of = 3gp
|
||||
ocopyts = yes
|
||||
profile = enc-v-h263
|
||||
profile = enc-a-aac
|
||||
ofopts-clr = yes
|
||||
ofopts = ""
|
||||
|
||||
[enc-f-avi]
|
||||
profile-desc = "MPEG-4 + MP3 (for AVI)"
|
||||
of = avi
|
||||
ocopyts = no
|
||||
oautofps = yes
|
||||
profile = enc-v-mpeg4
|
||||
profile = enc-a-mp3
|
||||
ofopts-clr = yes
|
||||
ofopts = ""
|
||||
|
||||
[enc-f-mp4]
|
||||
profile-desc = "H.264 + AAC (for MP4)"
|
||||
of = mp4
|
||||
ocopyts = yes
|
||||
profile = enc-v-h264
|
||||
profile = enc-a-aac
|
||||
## equivalent to using qt-faststart tool
|
||||
@@ -132,10 +128,9 @@ profile = enc-a-aac
|
||||
[enc-f-webm]
|
||||
profile-desc = "VP9 + Opus (for WebM)"
|
||||
of = webm
|
||||
ocopyts = yes
|
||||
profile = enc-v-vp9
|
||||
profile = enc-a-opus
|
||||
ofopts-clr = yes
|
||||
ofopts = ""
|
||||
|
||||
##################
|
||||
# target devices #
|
||||
@@ -147,9 +142,7 @@ profile = enc-a-ac3
|
||||
of = dvd
|
||||
ofopts-append = packetsize=2048
|
||||
ofopts-append = muxrate=10080000
|
||||
ofps = 25
|
||||
oharddup = yes
|
||||
ovfirst = yes # dvdauthor needs this
|
||||
vf-add = fps=25
|
||||
audio-samplerate = 48000
|
||||
ovcopts = g=15,b=6000000,maxrate=9000000,minrate=0,bufsize=1835008
|
||||
|
||||
@@ -160,9 +153,7 @@ profile = enc-a-ac3
|
||||
of = dvd
|
||||
ofopts-append = packetsize=2048
|
||||
ofopts-append = muxrate=10080000
|
||||
ofps = 24000/1001
|
||||
oharddup = yes
|
||||
ovfirst = yes # dvdauthor needs this
|
||||
vf-add = fps="24000/1001"
|
||||
audio-samplerate = 48000
|
||||
ovcopts = g=18,b=6000000,maxrate=9000000,minrate=0,bufsize=1835008
|
||||
|
||||
@@ -177,12 +168,11 @@ ovcopts-append = maxrate=10000k
|
||||
ovcopts-append = bufsize=10000k
|
||||
ovcopts-append = rc_init_occupancy=9000k
|
||||
ovcopts-append = refs=5
|
||||
omaxfps = 30
|
||||
|
||||
[enc-to-nok-6300]
|
||||
profile-desc = "3GP for Nokia 6300"
|
||||
profile = enc-f-3gp
|
||||
ofps = 25
|
||||
vf-add = fps=25
|
||||
vf-add = lavfi=graph="scale=176:144"
|
||||
audio-samplerate = 16000
|
||||
audio-channels = 1
|
||||
@@ -191,7 +181,6 @@ oacopts-add = b=32k
|
||||
[enc-to-hp-slate-7]
|
||||
profile-desc = "MP4 for HP Slate 7 (1024x600, crazy aspect)"
|
||||
profile = enc-f-mp4
|
||||
omaxfps = 30
|
||||
ovcopts-add = profile=high
|
||||
# DW = 1024, DH = 600, DAR = 97:54 (=> SAR = 2425:2304)
|
||||
vf-add = lavfi=graph="scale=floor(min(1024*min(1\,dar/(97/54))\,in_w)/2+0.5)*2:floor(min(600*min((97/54)/dar\,1)\,in_h)/2+0.5)*2,setsar=sar=sar/(2425/2304)"
|
||||
|
||||
+25
-8
@@ -54,15 +54,30 @@
|
||||
# Skip to previous/next subtitle (subject to some restrictions; see manpage)
|
||||
#Ctrl+LEFT no-osd sub-seek -1
|
||||
#Ctrl+RIGHT no-osd sub-seek 1
|
||||
# Adjust timing to previous/next subtitle
|
||||
#Ctrl+Shift+LEFT sub-step -1
|
||||
#Ctrl+Shift+RIGHT sub-step 1
|
||||
# Move video rectangle
|
||||
#Alt+left add video-pan-x 0.1
|
||||
#Alt+right add video-pan-x -0.1
|
||||
#Alt+up add video-pan-y 0.1
|
||||
#Alt+down add video-pan-y -0.1
|
||||
# Zoom/unzoom video
|
||||
#Alt++ add video-zoom 0.1
|
||||
#Alt+- add video-zoom -0.1
|
||||
# Reset video zoom/pan settings
|
||||
#Alt+BS set video-zoom 0 ; set video-pan-x 0 ; set video-pan-y 0
|
||||
#PGUP add chapter 1 # skip to next chapter
|
||||
#PGDWN add chapter -1 # skip to previous chapter
|
||||
#Shift+PGUP seek 600
|
||||
#Shift+PGDWN seek -600
|
||||
#[ multiply speed 0.9091 # scale playback speed
|
||||
#[ multiply speed 1/1.1 # scale playback speed
|
||||
#] multiply speed 1.1
|
||||
#{ multiply speed 0.5
|
||||
#} multiply speed 2.0
|
||||
#BS set speed 1.0 # reset speed to normal
|
||||
#Shift+BS revert-seek # undo previous (or marked) seek
|
||||
#Shift+Ctrl+BS revert-seek mark # mark position for revert-seek
|
||||
#q quit
|
||||
#Q quit-watch-later
|
||||
#q {encode} quit 4
|
||||
@@ -81,7 +96,8 @@
|
||||
#i script-binding stats/display-stats
|
||||
#I script-binding stats/display-stats-toggle
|
||||
#z add sub-delay -0.1 # subtract 100 ms delay from subs
|
||||
#x add sub-delay +0.1 # add
|
||||
#Z add sub-delay +0.1 # add
|
||||
#x add sub-delay +0.1 # same as previous binding (discouraged)
|
||||
#ctrl++ add audio-delay 0.100 # this changes audio/video sync
|
||||
#ctrl+- add audio-delay -0.100
|
||||
#9 add volume -2
|
||||
@@ -103,7 +119,8 @@
|
||||
# toggle deinterlacer (automatically inserts or removes required filter)
|
||||
#d cycle deinterlace
|
||||
#r add sub-pos -1 # move subtitles up
|
||||
#t add sub-pos +1 # down
|
||||
#R add sub-pos +1 # down
|
||||
#t add sub-pos +1 # same as previous binding (discouraged)
|
||||
#v cycle sub-visibility
|
||||
# stretch SSA/ASS subtitles with anamorphic videos to match historical
|
||||
#V cycle sub-ass-vsfilter-aspect-compat
|
||||
@@ -121,7 +138,8 @@
|
||||
#Ctrl+s async screenshot window # ...with subtitles and OSD, and scaled
|
||||
#Alt+s screenshot each-frame # automatically screenshot every frame
|
||||
#w add panscan -0.1 # zoom out with -panscan 0 -fs
|
||||
#e add panscan +0.1 # in
|
||||
#W add panscan +0.1 # in
|
||||
#e add panscan +0.1 # same as previous binding (discouraged)
|
||||
# cycle video aspect ratios; "-1" is the container aspect
|
||||
#A cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1"
|
||||
#POWER quit
|
||||
@@ -143,6 +161,9 @@
|
||||
#L cycle-values loop-file "inf" "no" # toggle infinite looping
|
||||
#ctrl+c quit 4
|
||||
#DEL script-binding osc/visibility # cycle OSC display
|
||||
#ctrl+h cycle-values hwdec "auto" "no" # cycle hardware decoding
|
||||
#F8 show_text ${playlist} # show playlist
|
||||
#F9 show_text ${track-list} # show list of audio/sub streams
|
||||
|
||||
# Apple Remote section
|
||||
#AR_PLAY cycle pause
|
||||
@@ -173,11 +194,7 @@
|
||||
|
||||
# ? add sub-scale +0.1 # increase subtitle font size
|
||||
# ? add sub-scale -0.1 # decrease subtitle font size
|
||||
# ? sub-step -1 # immediately display next subtitle
|
||||
# ? sub-step +1 # previous
|
||||
# ? cycle angle # switch DVD/Bluray angle
|
||||
# ? add balance -0.1 # adjust audio balance in favor of left
|
||||
# ? add balance 0.1 # right
|
||||
# ? cycle sub-forced-only # toggle DVD forced subs
|
||||
# ? cycle program # cycle transport stream programs
|
||||
# ? stop # stop playback (quit or enter idle mode)
|
||||
+4
-2
@@ -4,6 +4,7 @@ Name=mpv Media Player
|
||||
Name[ca]=Reproductor multimèdia mpv
|
||||
Name[cs]=mpv přehrávač
|
||||
Name[da]=mpv-medieafspiller
|
||||
Name[fr]=Lecteur multimédia mpv
|
||||
Name[pl]=Odtwarzacz mpv
|
||||
Name[ru]=Проигрыватель mpv
|
||||
Name[zh_CN]=mpv 媒体播放器
|
||||
@@ -11,6 +12,7 @@ Name[zh_TW]=mpv 媒體播放器
|
||||
GenericName=Multimedia player
|
||||
GenericName[cs]=Multimediální přehrávač
|
||||
GenericName[da]=Multimedieafspiller
|
||||
GenericName[fr]=Lecteur multimédia
|
||||
GenericName[zh_CN]=多媒体播放器
|
||||
GenericName[zh_TW]=多媒體播放器
|
||||
Comment=Play movies and songs
|
||||
@@ -19,7 +21,7 @@ Comment[cs]=Přehrává filmy a hudbu
|
||||
Comment[da]=Afspil film og sange
|
||||
Comment[de]=Filme und Musik abspielen
|
||||
Comment[es]=Reproduzca vídeos y canciones
|
||||
Comment[fr]=Lecteur multimédia
|
||||
Comment[fr]=Lire des vidéos et des musiques
|
||||
Comment[it]=Lettore multimediale
|
||||
Comment[pl]=Odtwarzaj filmy i muzykę
|
||||
Comment[ru]=Воспроизвести фильмы и музыку
|
||||
@@ -30,5 +32,5 @@ TryExec=mpv
|
||||
Exec=mpv --player-operation-mode=pseudo-gui -- %U
|
||||
Terminal=false
|
||||
Categories=AudioVideo;Audio;Video;Player;TV;
|
||||
MimeType=application/ogg;application/x-ogg;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/m4a;audio/x-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/x-mpeg;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg;video/x-mpeg2;video/mp4;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/x-fli;video/x-flv;video/x-theora;video/x-matroska;video/webm;audio/x-flac;audio/x-vorbis+ogg;video/x-ogm+ogg;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;video/mp2t;audio/flac;audio/mp4;
|
||||
MimeType=application/ogg;application/x-ogg;application/mxf;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/aiff;audio/x-aiff;audio/m4a;audio/x-m4a;application/x-extension-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/mpeg2;audio/mpeg3;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/musepack;audio/x-musepack;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;video/mpeg;video/x-mpeg2;video/x-mpeg3;video/mp4v-es;video/x-m4v;video/mp4;application/x-extension-mp4;video/divx;video/vnd.divx;video/msvideo;video/x-msvideo;video/ogg;video/quicktime;video/vnd.rn-realvideo;video/x-ms-afs;video/x-ms-asf;audio/x-ms-asf;application/vnd.ms-asf;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvxvideo;video/x-avi;video/avi;video/x-flic;video/fli;video/x-flc;video/flv;video/x-flv;video/x-theora;video/x-theora+ogg;video/x-matroska;video/mkv;audio/x-matroska;application/x-matroska;video/webm;audio/webm;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/x-ogm;video/x-ogm+ogg;application/x-ogm;application/x-ogm-audio;application/x-ogm-video;application/x-shorten;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/eac3;audio/amr-wb;video/mp2t;audio/flac;audio/mp4;application/x-mpegurl;video/vnd.mpegurl;application/vnd.apple.mpegurl;audio/x-pn-au;video/3gp;video/3gpp;video/3gpp2;audio/3gpp;audio/3gpp2;video/dv;audio/dv;audio/opus;audio/vnd.dts;audio/vnd.dts.hd;audio/x-adpcm;application/x-cue;audio/m3u;
|
||||
X-KDE-Protocols=ftp,http,https,mms,rtmp,rtsp,sftp,smb
|
||||
@@ -0,0 +1,363 @@
|
||||
#include <math.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/options.h"
|
||||
#include "video/mp_image.h"
|
||||
|
||||
#include "f_auto_filters.h"
|
||||
#include "f_swscale.h"
|
||||
#include "f_utils.h"
|
||||
#include "filter.h"
|
||||
#include "filter_internal.h"
|
||||
#include "user_filters.h"
|
||||
|
||||
struct deint_priv {
|
||||
struct mp_subfilter sub;
|
||||
int prev_imgfmt;
|
||||
int prev_setting;
|
||||
struct m_config_cache *opts;
|
||||
};
|
||||
|
||||
static void deint_process(struct mp_filter *f)
|
||||
{
|
||||
struct deint_priv *p = f->priv;
|
||||
|
||||
if (!mp_subfilter_read(&p->sub))
|
||||
return;
|
||||
|
||||
struct mp_frame frame = p->sub.frame;
|
||||
|
||||
if (mp_frame_is_signaling(frame)) {
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.type != MP_FRAME_VIDEO) {
|
||||
MP_ERR(f, "video input required!\n");
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
m_config_cache_update(p->opts);
|
||||
struct filter_opts *opts = p->opts->opts;
|
||||
|
||||
if (!opts->deinterlace)
|
||||
mp_subfilter_destroy(&p->sub);
|
||||
|
||||
struct mp_image *img = frame.data;
|
||||
|
||||
if (img->imgfmt == p->prev_imgfmt && p->prev_setting == opts->deinterlace) {
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mp_subfilter_drain_destroy(&p->sub))
|
||||
return;
|
||||
|
||||
assert(!p->sub.filter);
|
||||
|
||||
p->prev_imgfmt = img->imgfmt;
|
||||
p->prev_setting = opts->deinterlace;
|
||||
if (!p->prev_setting) {
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
if (img->imgfmt == IMGFMT_VDPAU) {
|
||||
char *args[] = {"deint", "yes", NULL};
|
||||
p->sub.filter =
|
||||
mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vdpaupp", args);
|
||||
} else if (img->imgfmt == IMGFMT_VAAPI) {
|
||||
p->sub.filter =
|
||||
mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "vavpp", NULL);
|
||||
} else if (img->imgfmt == IMGFMT_D3D11) {
|
||||
p->sub.filter =
|
||||
mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "d3d11vpp", NULL);
|
||||
} else if (mp_sws_supports_input(img->imgfmt)) {
|
||||
char *args[] = {"mode", "send_field", NULL};
|
||||
p->sub.filter =
|
||||
mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "yadif", args);
|
||||
} else {
|
||||
MP_ERR(f, "no deinterlace filter available for this format\n");
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!p->sub.filter)
|
||||
MP_ERR(f, "creating deinterlacer failed\n");
|
||||
|
||||
mp_subfilter_continue(&p->sub);
|
||||
}
|
||||
|
||||
static void deint_reset(struct mp_filter *f)
|
||||
{
|
||||
struct deint_priv *p = f->priv;
|
||||
|
||||
mp_subfilter_reset(&p->sub);
|
||||
}
|
||||
|
||||
static void deint_destroy(struct mp_filter *f)
|
||||
{
|
||||
struct deint_priv *p = f->priv;
|
||||
|
||||
mp_subfilter_reset(&p->sub);
|
||||
TA_FREEP(&p->sub.filter);
|
||||
}
|
||||
|
||||
static bool deint_command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct deint_priv *p = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) {
|
||||
cmd->is_active = !!p->sub.filter;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info deint_filter = {
|
||||
.name = "deint",
|
||||
.priv_size = sizeof(struct deint_priv),
|
||||
.command = deint_command,
|
||||
.process = deint_process,
|
||||
.reset = deint_reset,
|
||||
.destroy = deint_destroy,
|
||||
};
|
||||
|
||||
struct mp_filter *mp_deint_create(struct mp_filter *parent)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &deint_filter);
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
struct deint_priv *p = f->priv;
|
||||
|
||||
p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
p->opts = m_config_cache_alloc(f, f->global, &filter_conf);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
struct rotate_priv {
|
||||
struct mp_subfilter sub;
|
||||
int prev_rotate;
|
||||
int prev_imgfmt;
|
||||
int target_rotate;
|
||||
};
|
||||
|
||||
static void rotate_process(struct mp_filter *f)
|
||||
{
|
||||
struct rotate_priv *p = f->priv;
|
||||
|
||||
if (!mp_subfilter_read(&p->sub))
|
||||
return;
|
||||
|
||||
struct mp_frame frame = p->sub.frame;
|
||||
|
||||
if (mp_frame_is_signaling(frame)) {
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
if (frame.type != MP_FRAME_VIDEO) {
|
||||
MP_ERR(f, "video input required!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
struct mp_image *img = frame.data;
|
||||
|
||||
if (img->params.rotate == p->prev_rotate &&
|
||||
img->imgfmt == p->prev_imgfmt)
|
||||
{
|
||||
img->params.rotate = p->target_rotate;
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mp_subfilter_drain_destroy(&p->sub))
|
||||
return;
|
||||
|
||||
assert(!p->sub.filter);
|
||||
|
||||
int rotate = p->prev_rotate = img->params.rotate;
|
||||
p->target_rotate = rotate;
|
||||
p->prev_imgfmt = img->imgfmt;
|
||||
|
||||
struct mp_stream_info *info = mp_filter_find_stream_info(f);
|
||||
if (rotate == 0 || (info && info->rotate90 && !(rotate % 90))) {
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mp_sws_supports_input(img->imgfmt)) {
|
||||
MP_ERR(f, "Video rotation with this format not supported\n");
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
double angle = rotate / 360.0 * M_PI * 2;
|
||||
char *args[] = {"angle", mp_tprintf(30, "%f", angle),
|
||||
"ow", mp_tprintf(30, "rotw(%f)", angle),
|
||||
"oh", mp_tprintf(30, "roth(%f)", angle),
|
||||
NULL};
|
||||
p->sub.filter =
|
||||
mp_create_user_filter(f, MP_OUTPUT_CHAIN_VIDEO, "rotate", args);
|
||||
|
||||
if (p->sub.filter) {
|
||||
MP_INFO(f, "Inserting rotation filter.\n");
|
||||
p->target_rotate = 0;
|
||||
} else {
|
||||
MP_ERR(f, "could not create rotation filter\n");
|
||||
}
|
||||
|
||||
mp_subfilter_continue(&p->sub);
|
||||
}
|
||||
|
||||
static void rotate_reset(struct mp_filter *f)
|
||||
{
|
||||
struct rotate_priv *p = f->priv;
|
||||
|
||||
mp_subfilter_reset(&p->sub);
|
||||
}
|
||||
|
||||
static void rotate_destroy(struct mp_filter *f)
|
||||
{
|
||||
struct rotate_priv *p = f->priv;
|
||||
|
||||
mp_subfilter_reset(&p->sub);
|
||||
TA_FREEP(&p->sub.filter);
|
||||
}
|
||||
|
||||
static bool rotate_command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct rotate_priv *p = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) {
|
||||
cmd->is_active = !!p->sub.filter;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const struct mp_filter_info rotate_filter = {
|
||||
.name = "autorotate",
|
||||
.priv_size = sizeof(struct rotate_priv),
|
||||
.command = rotate_command,
|
||||
.process = rotate_process,
|
||||
.reset = rotate_reset,
|
||||
.destroy = rotate_destroy,
|
||||
};
|
||||
|
||||
struct mp_filter *mp_autorotate_create(struct mp_filter *parent)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &rotate_filter);
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
struct rotate_priv *p = f->priv;
|
||||
p->prev_rotate = -1;
|
||||
|
||||
p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
struct aspeed_priv {
|
||||
struct mp_subfilter sub;
|
||||
double cur_speed;
|
||||
};
|
||||
|
||||
static void aspeed_process(struct mp_filter *f)
|
||||
{
|
||||
struct aspeed_priv *p = f->priv;
|
||||
|
||||
if (!mp_subfilter_read(&p->sub))
|
||||
return;
|
||||
|
||||
if (fabs(p->cur_speed - 1.0) < 1e-8) {
|
||||
if (p->sub.filter)
|
||||
MP_VERBOSE(f, "removing scaletempo\n");
|
||||
if (!mp_subfilter_drain_destroy(&p->sub))
|
||||
return;
|
||||
} else if (!p->sub.filter) {
|
||||
MP_VERBOSE(f, "adding scaletempo\n");
|
||||
p->sub.filter =
|
||||
mp_create_user_filter(f, MP_OUTPUT_CHAIN_AUDIO, "scaletempo", NULL);
|
||||
if (!p->sub.filter) {
|
||||
MP_ERR(f, "could not create scaletempo filter\n");
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (p->sub.filter) {
|
||||
struct mp_filter_command cmd = {
|
||||
.type = MP_FILTER_COMMAND_SET_SPEED,
|
||||
.speed = p->cur_speed,
|
||||
};
|
||||
mp_filter_command(p->sub.filter, &cmd);
|
||||
}
|
||||
|
||||
mp_subfilter_continue(&p->sub);
|
||||
}
|
||||
|
||||
static bool aspeed_command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct aspeed_priv *p = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED) {
|
||||
p->cur_speed = cmd->speed;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) {
|
||||
cmd->is_active = !!p->sub.filter;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void aspeed_reset(struct mp_filter *f)
|
||||
{
|
||||
struct aspeed_priv *p = f->priv;
|
||||
|
||||
mp_subfilter_reset(&p->sub);
|
||||
}
|
||||
|
||||
static void aspeed_destroy(struct mp_filter *f)
|
||||
{
|
||||
struct aspeed_priv *p = f->priv;
|
||||
|
||||
mp_subfilter_reset(&p->sub);
|
||||
TA_FREEP(&p->sub.filter);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info aspeed_filter = {
|
||||
.name = "autoaspeed",
|
||||
.priv_size = sizeof(struct aspeed_priv),
|
||||
.command = aspeed_command,
|
||||
.process = aspeed_process,
|
||||
.reset = aspeed_reset,
|
||||
.destroy = aspeed_destroy,
|
||||
};
|
||||
|
||||
struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &aspeed_filter);
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
struct aspeed_priv *p = f->priv;
|
||||
p->cur_speed = 1.0;
|
||||
|
||||
p->sub.in = mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
p->sub.out = mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
return f;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "filter.h"
|
||||
|
||||
// A filter which inserts the required deinterlacing filter based on the
|
||||
// hardware decode mode and the deinterlace user option.
|
||||
struct mp_filter *mp_deint_create(struct mp_filter *parent);
|
||||
|
||||
// Rotate according to mp_image.rotate and VO capabilities.
|
||||
struct mp_filter *mp_autorotate_create(struct mp_filter *parent);
|
||||
|
||||
// Insert a filter that inserts scaletempo depending on speed settings.
|
||||
struct mp_filter *mp_autoaspeed_create(struct mp_filter *parent);
|
||||
@@ -0,0 +1,473 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
#include "audio/format.h"
|
||||
#include "common/common.h"
|
||||
#include "common/msg.h"
|
||||
#include "video/hwdec.h"
|
||||
#include "video/mp_image.h"
|
||||
|
||||
#include "f_autoconvert.h"
|
||||
#include "f_hwtransfer.h"
|
||||
#include "f_swresample.h"
|
||||
#include "f_swscale.h"
|
||||
#include "f_utils.h"
|
||||
#include "filter.h"
|
||||
#include "filter_internal.h"
|
||||
|
||||
struct priv {
|
||||
struct mp_log *log;
|
||||
|
||||
struct mp_subfilter sub;
|
||||
|
||||
bool force_update;
|
||||
|
||||
int *imgfmts;
|
||||
int *subfmts;
|
||||
int num_imgfmts;
|
||||
|
||||
// Enable special conversion for the final stage before the VO.
|
||||
bool vo_convert;
|
||||
|
||||
// sws state
|
||||
int in_imgfmt, in_subfmt;
|
||||
|
||||
int *afmts;
|
||||
int num_afmts;
|
||||
int *srates;
|
||||
int num_srates;
|
||||
struct mp_chmap_sel chmaps;
|
||||
|
||||
int in_afmt, in_srate;
|
||||
struct mp_chmap in_chmap;
|
||||
|
||||
double audio_speed;
|
||||
bool resampling_forced;
|
||||
|
||||
bool format_change_blocked;
|
||||
bool format_change_cont;
|
||||
|
||||
struct mp_autoconvert public;
|
||||
};
|
||||
|
||||
// Dummy filter for bundling sub-conversion filters.
|
||||
static const struct mp_filter_info convert_filter = {
|
||||
.name = "convert",
|
||||
};
|
||||
|
||||
// For hw decoding: thing which can convert between underlying surface formats.
|
||||
// The filter detects the needed target format from struct mp_hwdec_ctx.
|
||||
struct subfmt_conv {
|
||||
int hw_imgfmt;
|
||||
struct mp_filter *(*create)(struct mp_filter *parent);
|
||||
};
|
||||
|
||||
static const struct subfmt_conv subfmt_converters[] = {
|
||||
#if HAVE_D3D_HWACCEL
|
||||
{IMGFMT_D3D11, vf_d3d11_create_outconv},
|
||||
#endif
|
||||
{0}
|
||||
};
|
||||
|
||||
void mp_autoconvert_clear(struct mp_autoconvert *c)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
|
||||
p->num_imgfmts = 0;
|
||||
p->num_afmts = 0;
|
||||
p->num_srates = 0;
|
||||
p->chmaps = (struct mp_chmap_sel){0};
|
||||
p->force_update = true;
|
||||
}
|
||||
|
||||
void mp_autoconvert_add_imgfmt(struct mp_autoconvert *c, int imgfmt, int subfmt)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
|
||||
MP_TARRAY_GROW(p, p->imgfmts, p->num_imgfmts);
|
||||
MP_TARRAY_GROW(p, p->subfmts, p->num_imgfmts);
|
||||
|
||||
p->imgfmts[p->num_imgfmts] = imgfmt;
|
||||
p->subfmts[p->num_imgfmts] = subfmt;
|
||||
|
||||
p->num_imgfmts += 1;
|
||||
p->force_update = true;
|
||||
}
|
||||
|
||||
void mp_autoconvert_add_vo_hwdec_subfmts(struct mp_autoconvert *c,
|
||||
struct mp_hwdec_devices *devs)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
assert(devs);
|
||||
|
||||
int prev_format = 0;
|
||||
|
||||
for (int n = 0; ; n++) {
|
||||
struct mp_hwdec_ctx *ctx = hwdec_devices_get_n(devs, n);
|
||||
if (!ctx)
|
||||
break;
|
||||
if (!ctx->hw_imgfmt || !ctx->supported_formats)
|
||||
continue;
|
||||
// Very hacky: don't let d3d11-egl-rgb overwrite d3d11-egl
|
||||
if (ctx->hw_imgfmt == prev_format)
|
||||
continue;
|
||||
prev_format = ctx->hw_imgfmt;
|
||||
// Stupidity: VOs export imgfmt only, so subfmt is always 0. Remove it
|
||||
// to fix it up.
|
||||
for (int i = 0; i < p->num_imgfmts; i++) {
|
||||
if (p->imgfmts[i] != ctx->hw_imgfmt)
|
||||
continue;
|
||||
|
||||
int count = p->num_imgfmts;
|
||||
MP_TARRAY_REMOVE_AT(p->imgfmts, count, i);
|
||||
count = p->num_imgfmts;
|
||||
MP_TARRAY_REMOVE_AT(p->subfmts, count, i);
|
||||
p->num_imgfmts -= 1;
|
||||
break;
|
||||
}
|
||||
for (int i = 0; ctx->supported_formats[i]; i++)
|
||||
mp_autoconvert_add_imgfmt(c, ctx->hw_imgfmt, ctx->supported_formats[i]);
|
||||
}
|
||||
|
||||
p->vo_convert = true;
|
||||
}
|
||||
|
||||
void mp_autoconvert_add_afmt(struct mp_autoconvert *c, int afmt)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
|
||||
MP_TARRAY_APPEND(p, p->afmts, p->num_afmts, afmt);
|
||||
p->force_update = true;
|
||||
}
|
||||
|
||||
void mp_autoconvert_add_chmap(struct mp_autoconvert *c, struct mp_chmap *chmap)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
|
||||
mp_chmap_sel_add_map(&p->chmaps, chmap);
|
||||
p->force_update = true;
|
||||
}
|
||||
|
||||
void mp_autoconvert_add_srate(struct mp_autoconvert *c, int rate)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
|
||||
MP_TARRAY_APPEND(p, p->srates, p->num_srates, rate);
|
||||
// Some other API we call expects a 0-terminated sample rates array.
|
||||
MP_TARRAY_GROW(p, p->srates, p->num_srates);
|
||||
p->srates[p->num_srates] = 0;
|
||||
p->force_update = true;
|
||||
}
|
||||
|
||||
static void handle_video_frame(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
struct mp_image *img = p->sub.frame.data;
|
||||
|
||||
if (p->force_update)
|
||||
p->in_imgfmt = p->in_subfmt = 0;
|
||||
|
||||
if (img->imgfmt == p->in_imgfmt && img->params.hw_subfmt == p->in_subfmt) {
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mp_subfilter_drain_destroy(&p->sub)) {
|
||||
p->in_imgfmt = p->in_subfmt = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
p->in_imgfmt = img->params.imgfmt;
|
||||
p->in_subfmt = img->params.hw_subfmt;
|
||||
p->force_update = false;
|
||||
|
||||
if (!p->num_imgfmts) {
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
|
||||
bool different_subfmt = false;
|
||||
|
||||
for (int n = 0; n < p->num_imgfmts; n++) {
|
||||
bool samefmt = img->params.imgfmt == p->imgfmts[n];
|
||||
bool samesubffmt = img->params.hw_subfmt == p->subfmts[n];
|
||||
if (samefmt && !samesubffmt)
|
||||
different_subfmt = true;
|
||||
if (samefmt && (samesubffmt || !p->subfmts[n])) {
|
||||
mp_subfilter_continue(&p->sub);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_stream_info *info = mp_filter_find_stream_info(f);
|
||||
|
||||
struct mp_filter *conv = mp_filter_create(f, &convert_filter);
|
||||
mp_filter_add_pin(conv, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(conv, MP_PIN_OUT, "out");
|
||||
|
||||
struct mp_filter *filters[2] = {0};
|
||||
bool need_sws = true;
|
||||
|
||||
int *fmts = p->imgfmts;
|
||||
int num_fmts = p->num_imgfmts;
|
||||
|
||||
// Source is sw, all targets are hw -> try to upload.
|
||||
bool sw_to_hw = !IMGFMT_IS_HWACCEL(img->imgfmt);
|
||||
for (int n = 0; n < num_fmts; n++)
|
||||
sw_to_hw &= IMGFMT_IS_HWACCEL(fmts[n]);
|
||||
|
||||
if (sw_to_hw && num_fmts > 0) {
|
||||
// We can probably use this! Very lazy and very approximate.
|
||||
struct mp_hwupload *upload = mp_hwupload_create(conv, fmts[0]);
|
||||
if (upload) {
|
||||
MP_INFO(p, "HW-uploading to %s\n", mp_imgfmt_to_name(fmts[0]));
|
||||
filters[1] = upload->f;
|
||||
fmts = upload->upload_fmts;
|
||||
num_fmts = upload->num_upload_fmts;
|
||||
}
|
||||
} else if (p->vo_convert && different_subfmt && info && info->hwdec_devs) {
|
||||
for (int n = 0; subfmt_converters[n].hw_imgfmt; n++) {
|
||||
if (subfmt_converters[n].hw_imgfmt == img->imgfmt) {
|
||||
MP_INFO(p, "Using HW sub-conversion.\n");
|
||||
filters[1] = subfmt_converters[n].create(conv);
|
||||
if (filters[1]) {
|
||||
need_sws = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_sws) {
|
||||
// Create a new conversion filter.
|
||||
struct mp_sws_filter *sws = mp_sws_filter_create(conv);
|
||||
if (!sws) {
|
||||
MP_ERR(p, "error creating conversion filter\n");
|
||||
return;
|
||||
}
|
||||
|
||||
int out = mp_sws_find_best_out_format(img->imgfmt, fmts, num_fmts);
|
||||
if (!out) {
|
||||
MP_ERR(p, "can't find video conversion for %s/%s\n",
|
||||
mp_imgfmt_to_name(img->imgfmt),
|
||||
mp_imgfmt_to_name(img->params.hw_subfmt));
|
||||
talloc_free(conv);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (out == img->imgfmt) {
|
||||
// Can happen if hwupload goes to same format.
|
||||
talloc_free(sws->f);
|
||||
} else {
|
||||
sws->out_format = out;
|
||||
MP_INFO(p, "Converting %s -> %s\n", mp_imgfmt_to_name(img->imgfmt),
|
||||
mp_imgfmt_to_name(sws->out_format));
|
||||
filters[0] = sws->f;
|
||||
}
|
||||
}
|
||||
|
||||
mp_chain_filters(conv->ppins[0], conv->ppins[1], filters, 2);
|
||||
|
||||
p->sub.filter = conv;
|
||||
mp_subfilter_continue(&p->sub);
|
||||
}
|
||||
|
||||
static void handle_audio_frame(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
struct mp_aframe *aframe = p->sub.frame.data;
|
||||
|
||||
int afmt = mp_aframe_get_format(aframe);
|
||||
int srate = mp_aframe_get_rate(aframe);
|
||||
struct mp_chmap chmap = {0};
|
||||
mp_aframe_get_chmap(aframe, &chmap);
|
||||
|
||||
if (p->resampling_forced && !af_fmt_is_pcm(afmt)) {
|
||||
MP_WARN(p, "ignoring request to resample non-PCM audio for speed change\n");
|
||||
p->resampling_forced = false;
|
||||
}
|
||||
|
||||
bool format_change = afmt != p->in_afmt ||
|
||||
srate != p->in_srate ||
|
||||
!mp_chmap_equals(&chmap, &p->in_chmap) ||
|
||||
p->force_update;
|
||||
|
||||
if (!format_change && (!p->resampling_forced || p->sub.filter))
|
||||
goto cont;
|
||||
|
||||
if (!mp_subfilter_drain_destroy(&p->sub))
|
||||
return;
|
||||
|
||||
if (format_change && p->public.on_audio_format_change) {
|
||||
if (p->format_change_blocked)
|
||||
return;
|
||||
|
||||
if (!p->format_change_cont) {
|
||||
p->format_change_blocked = true;
|
||||
p->public.
|
||||
on_audio_format_change(p->public.on_audio_format_change_opaque);
|
||||
return;
|
||||
}
|
||||
p->format_change_cont = false;
|
||||
}
|
||||
|
||||
p->in_afmt = afmt;
|
||||
p->in_srate = srate;
|
||||
p->in_chmap = chmap;
|
||||
p->force_update = false;
|
||||
|
||||
int out_afmt = 0;
|
||||
int best_score = 0;
|
||||
for (int n = 0; n < p->num_afmts; n++) {
|
||||
int score = af_format_conversion_score(p->afmts[n], afmt);
|
||||
if (!out_afmt || score > best_score) {
|
||||
best_score = score;
|
||||
out_afmt = p->afmts[n];
|
||||
}
|
||||
}
|
||||
if (!out_afmt)
|
||||
out_afmt = afmt;
|
||||
|
||||
// (The p->srates array is 0-terminated already.)
|
||||
int out_srate = af_select_best_samplerate(srate, p->srates);
|
||||
if (out_srate <= 0)
|
||||
out_srate = p->num_srates ? p->srates[0] : srate;
|
||||
|
||||
struct mp_chmap out_chmap = chmap;
|
||||
if (p->chmaps.num_chmaps) {
|
||||
if (!mp_chmap_sel_adjust(&p->chmaps, &out_chmap))
|
||||
out_chmap = p->chmaps.chmaps[0]; // violently force fallback
|
||||
}
|
||||
|
||||
if (out_afmt == p->in_afmt && out_srate == p->in_srate &&
|
||||
mp_chmap_equals(&out_chmap, &p->in_chmap) && !p->resampling_forced)
|
||||
{
|
||||
goto cont;
|
||||
}
|
||||
|
||||
MP_VERBOSE(p, "inserting resampler\n");
|
||||
|
||||
struct mp_swresample *s = mp_swresample_create(f, NULL);
|
||||
if (!s)
|
||||
abort();
|
||||
|
||||
s->out_format = out_afmt;
|
||||
s->out_rate = out_srate;
|
||||
s->out_channels = out_chmap;
|
||||
|
||||
p->sub.filter = s->f;
|
||||
|
||||
cont:
|
||||
|
||||
if (p->sub.filter) {
|
||||
struct mp_filter_command cmd = {
|
||||
.type = MP_FILTER_COMMAND_SET_SPEED_RESAMPLE,
|
||||
.speed = p->audio_speed,
|
||||
};
|
||||
mp_filter_command(p->sub.filter, &cmd);
|
||||
}
|
||||
|
||||
mp_subfilter_continue(&p->sub);
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (!mp_subfilter_read(&p->sub))
|
||||
return;
|
||||
|
||||
if (p->sub.frame.type == MP_FRAME_VIDEO) {
|
||||
handle_video_frame(f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (p->sub.frame.type == MP_FRAME_AUDIO) {
|
||||
handle_audio_frame(f);
|
||||
return;
|
||||
}
|
||||
|
||||
mp_subfilter_continue(&p->sub);
|
||||
}
|
||||
|
||||
void mp_autoconvert_format_change_continue(struct mp_autoconvert *c)
|
||||
{
|
||||
struct priv *p = c->f->priv;
|
||||
|
||||
if (p->format_change_blocked) {
|
||||
p->format_change_cont = true;
|
||||
p->format_change_blocked = false;
|
||||
mp_filter_wakeup(c->f);
|
||||
}
|
||||
}
|
||||
|
||||
static bool command(struct mp_filter *f, struct mp_filter_command *cmd)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_SET_SPEED_RESAMPLE) {
|
||||
p->audio_speed = cmd->speed;
|
||||
// If we needed resampling once, keep forcing resampling, as it might be
|
||||
// quickly changing between 1.0 and other values for A/V compensation.
|
||||
if (p->audio_speed != 1.0)
|
||||
p->resampling_forced = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cmd->type == MP_FILTER_COMMAND_IS_ACTIVE) {
|
||||
cmd->is_active = !!p->sub.filter;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
mp_subfilter_reset(&p->sub);
|
||||
|
||||
p->format_change_cont = false;
|
||||
p->format_change_blocked = false;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
mp_subfilter_reset(&p->sub);
|
||||
TA_FREEP(&p->sub.filter);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info autoconvert_filter = {
|
||||
.name = "autoconvert",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.command = command,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
struct mp_autoconvert *mp_autoconvert_create(struct mp_filter *parent)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &autoconvert_filter);
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_IN, "in");
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
struct priv *p = f->priv;
|
||||
p->public.f = f;
|
||||
p->log = f->log;
|
||||
p->audio_speed = 1.0;
|
||||
p->sub.in = f->ppins[0];
|
||||
p->sub.out = f->ppins[1];
|
||||
|
||||
return &p->public;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "filter.h"
|
||||
|
||||
// A filter which automatically creates and uses a conversion filter based on
|
||||
// the filter settings, or passes through data unchanged if no conversion is
|
||||
// required.
|
||||
struct mp_autoconvert {
|
||||
// f->pins[0] is input, f->pins[1] is output
|
||||
struct mp_filter *f;
|
||||
|
||||
// If this is set, the callback is invoked (from the process function), and
|
||||
// further data flow is blocked until mp_autoconvert_format_change_continue()
|
||||
// is called. The idea is that you can reselect the output parameters on
|
||||
// format changes and continue filtering when ready.
|
||||
void (*on_audio_format_change)(void *opaque);
|
||||
void *on_audio_format_change_opaque;
|
||||
};
|
||||
|
||||
// (to free this, free the filter itself, mp_autoconvert.f)
|
||||
struct mp_autoconvert *mp_autoconvert_create(struct mp_filter *parent);
|
||||
|
||||
// Add the imgfmt as allowed video image format, and error on non-video frames.
|
||||
// Each call adds to the list of allowed formats. Before the first call, all
|
||||
// formats are allowed (even non-video).
|
||||
// subfmt can be used to specify underlying surface formats for hardware formats,
|
||||
// otherwise must be 0.
|
||||
void mp_autoconvert_add_imgfmt(struct mp_autoconvert *c, int imgfmt, int subfmt);
|
||||
|
||||
// Add the formats supported by the hwdec interops (or essentially refine them),
|
||||
// and trigger conversion if hw_subfmts mismatch. This is mostly a hack for
|
||||
// D3D11/ANGLE (which supports NV12 only).
|
||||
// Must be called mp_autoconvert_add_imgfmt(), and overrides them where formats
|
||||
// collide.
|
||||
struct mp_hwdec_devices;
|
||||
void mp_autoconvert_add_vo_hwdec_subfmts(struct mp_autoconvert *c,
|
||||
struct mp_hwdec_devices *devs);
|
||||
|
||||
// Add afmt (an AF_FORMAT_* value) as allowed audio format.
|
||||
// See mp_autoconvert_add_imgfmt() for other remarks.
|
||||
void mp_autoconvert_add_afmt(struct mp_autoconvert *c, int afmt);
|
||||
|
||||
// Add allowed audio channel configuration.
|
||||
struct mp_chmap;
|
||||
void mp_autoconvert_add_chmap(struct mp_autoconvert *c, struct mp_chmap *chmap);
|
||||
|
||||
// Add allowed audio sample rate.
|
||||
void mp_autoconvert_add_srate(struct mp_autoconvert *c, int rate);
|
||||
|
||||
// Reset set of allowed formats back to initial state. (This does not flush
|
||||
// any frames or remove currently active filters, although to get reasonable
|
||||
// behavior, you need to readd all previously allowed formats, or reset the
|
||||
// filter.)
|
||||
void mp_autoconvert_clear(struct mp_autoconvert *c);
|
||||
|
||||
// See mp_autoconvert.on_audio_format_change.
|
||||
void mp_autoconvert_format_change_continue(struct mp_autoconvert *c);
|
||||
|
||||
// vf_d3d11vpp.c
|
||||
struct mp_filter *vf_d3d11_create_outconv(struct mp_filter *parent);
|
||||
@@ -0,0 +1,723 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/buffer.h>
|
||||
#include <libavutil/rational.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#include "demux/demux.h"
|
||||
#include "demux/packet.h"
|
||||
|
||||
#include "common/codecs.h"
|
||||
#include "common/global.h"
|
||||
#include "common/recorder.h"
|
||||
|
||||
#include "audio/aframe.h"
|
||||
#include "video/out/vo.h"
|
||||
#include "video/csputils.h"
|
||||
|
||||
#include "demux/stheader.h"
|
||||
|
||||
#include "f_decoder_wrapper.h"
|
||||
#include "f_demux_in.h"
|
||||
#include "filter_internal.h"
|
||||
|
||||
struct priv {
|
||||
struct mp_filter *f;
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
|
||||
struct sh_stream *header;
|
||||
struct mp_codec_params *codec;
|
||||
|
||||
struct mp_decoder *decoder;
|
||||
|
||||
// Demuxer output.
|
||||
struct mp_pin *demux;
|
||||
|
||||
// Last PTS from decoder (set with each vd_driver->decode() call)
|
||||
double codec_pts;
|
||||
int num_codec_pts_problems;
|
||||
|
||||
// Last packet DTS from decoder (passed through from source packets)
|
||||
double codec_dts;
|
||||
int num_codec_dts_problems;
|
||||
|
||||
// PTS or DTS of packet first read
|
||||
double first_packet_pdts;
|
||||
|
||||
// There was at least one packet with nonsense timestamps.
|
||||
int has_broken_packet_pts; // <0: uninitialized, 0: no problems, 1: broken
|
||||
|
||||
int has_broken_decoded_pts;
|
||||
|
||||
int packets_without_output; // number packets sent without frame received
|
||||
|
||||
// Final PTS of previously decoded frame
|
||||
double pts;
|
||||
|
||||
struct mp_image_params dec_format, last_format, fixed_format;
|
||||
|
||||
double start_pts;
|
||||
double start, end;
|
||||
struct demux_packet *new_segment;
|
||||
struct mp_frame packet;
|
||||
|
||||
struct mp_frame decoded_coverart;
|
||||
int coverart_returned; // 0: no, 1: coverart frame itself, 2: EOF returned
|
||||
|
||||
struct mp_decoder_wrapper public;
|
||||
};
|
||||
|
||||
static void reset_decoder(struct priv *p)
|
||||
{
|
||||
p->first_packet_pdts = MP_NOPTS_VALUE;
|
||||
p->start_pts = MP_NOPTS_VALUE;
|
||||
p->pts = MP_NOPTS_VALUE;
|
||||
p->codec_pts = MP_NOPTS_VALUE;
|
||||
p->codec_dts = MP_NOPTS_VALUE;
|
||||
p->has_broken_decoded_pts = 0;
|
||||
p->last_format = p->fixed_format = (struct mp_image_params){0};
|
||||
p->public.dropped_frames = 0;
|
||||
p->public.attempt_framedrops = 0;
|
||||
p->public.pts_reset = false;
|
||||
p->packets_without_output = 0;
|
||||
mp_frame_unref(&p->packet);
|
||||
talloc_free(p->new_segment);
|
||||
p->new_segment = NULL;
|
||||
p->start = p->end = MP_NOPTS_VALUE;
|
||||
p->coverart_returned = 0;
|
||||
|
||||
if (p->decoder)
|
||||
mp_filter_reset(p->decoder->f);
|
||||
}
|
||||
|
||||
static void reset(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
reset_decoder(p);
|
||||
}
|
||||
|
||||
int mp_decoder_wrapper_control(struct mp_decoder_wrapper *d,
|
||||
enum dec_ctrl cmd, void *arg)
|
||||
{
|
||||
struct priv *p = d->f->priv;
|
||||
if (p->decoder && p->decoder->control)
|
||||
return p->decoder->control(p->decoder->f, cmd, arg);
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static void destroy(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
if (p->decoder) {
|
||||
MP_VERBOSE(f, "Uninit decoder.\n");
|
||||
talloc_free(p->decoder->f);
|
||||
p->decoder = NULL;
|
||||
}
|
||||
reset_decoder(p);
|
||||
mp_frame_unref(&p->decoded_coverart);
|
||||
}
|
||||
|
||||
struct mp_decoder_list *video_decoder_list(void)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
vd_lavc.add_decoders(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
struct mp_decoder_list *audio_decoder_list(void)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
ad_lavc.add_decoders(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d)
|
||||
{
|
||||
struct priv *p = d->f->priv;
|
||||
struct MPOpts *opts = p->opts;
|
||||
|
||||
if (p->decoder)
|
||||
talloc_free(p->decoder->f);
|
||||
p->decoder = NULL;
|
||||
|
||||
reset_decoder(p);
|
||||
p->has_broken_packet_pts = -10; // needs 10 packets to reach decision
|
||||
|
||||
const struct mp_decoder_fns *driver = NULL;
|
||||
struct mp_decoder_list *list = NULL;
|
||||
char *user_list = NULL;
|
||||
|
||||
if (p->codec->type == STREAM_VIDEO) {
|
||||
driver = &vd_lavc;
|
||||
user_list = opts->video_decoders;
|
||||
} else if (p->codec->type == STREAM_AUDIO) {
|
||||
driver = &ad_lavc;
|
||||
user_list = opts->audio_decoders;
|
||||
|
||||
if (p->public.try_spdif && p->codec->codec) {
|
||||
struct mp_decoder_list *spdif =
|
||||
select_spdif_codec(p->codec->codec, opts->audio_spdif);
|
||||
if (spdif->num_entries) {
|
||||
driver = &ad_spdif;
|
||||
list = spdif;
|
||||
} else {
|
||||
talloc_free(spdif);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
struct mp_decoder_list *full = talloc_zero(NULL, struct mp_decoder_list);
|
||||
if (driver)
|
||||
driver->add_decoders(full);
|
||||
list = mp_select_decoders(p->log, full, p->codec->codec, user_list);
|
||||
talloc_free(full);
|
||||
}
|
||||
|
||||
mp_print_decoders(p->log, MSGL_V, "Codec list:", list);
|
||||
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *sel = &list->entries[n];
|
||||
MP_VERBOSE(p, "Opening decoder %s\n", sel->decoder);
|
||||
|
||||
p->decoder = driver->create(p->f, p->codec, sel->decoder);
|
||||
if (p->decoder) {
|
||||
p->public.decoder_desc =
|
||||
talloc_asprintf(p, "%s (%s)", sel->decoder, sel->desc);
|
||||
MP_VERBOSE(p, "Selected codec: %s\n", p->public.decoder_desc);
|
||||
break;
|
||||
}
|
||||
|
||||
MP_WARN(p, "Decoder init failed for %s\n", sel->decoder);
|
||||
}
|
||||
|
||||
if (!p->decoder) {
|
||||
MP_ERR(p, "Failed to initialize a decoder for codec '%s'.\n",
|
||||
p->codec->codec ? p->codec->codec : "<?>");
|
||||
}
|
||||
|
||||
talloc_free(list);
|
||||
return !!p->decoder;
|
||||
}
|
||||
|
||||
static bool is_valid_peak(float sig_peak)
|
||||
{
|
||||
return !sig_peak || (sig_peak >= 1 && sig_peak <= 100);
|
||||
}
|
||||
|
||||
static void fix_image_params(struct priv *p,
|
||||
struct mp_image_params *params)
|
||||
{
|
||||
struct MPOpts *opts = p->opts;
|
||||
struct mp_image_params m = *params;
|
||||
struct mp_codec_params *c = p->codec;
|
||||
|
||||
MP_VERBOSE(p, "Decoder format: %s\n", mp_image_params_to_str(params));
|
||||
p->dec_format = *params;
|
||||
|
||||
// While mp_image_params normally always have to have d_w/d_h set, the
|
||||
// decoder signals unknown bitstream aspect ratio with both set to 0.
|
||||
bool use_container = true;
|
||||
if (opts->aspect_method == 1 && m.p_w > 0 && m.p_h > 0) {
|
||||
MP_VERBOSE(p, "Using bitstream aspect ratio.\n");
|
||||
use_container = false;
|
||||
}
|
||||
|
||||
if (use_container && c->par_w > 0 && c->par_h) {
|
||||
MP_VERBOSE(p, "Using container aspect ratio.\n");
|
||||
m.p_w = c->par_w;
|
||||
m.p_h = c->par_h;
|
||||
}
|
||||
|
||||
if (opts->movie_aspect >= 0) {
|
||||
MP_VERBOSE(p, "Forcing user-set aspect ratio.\n");
|
||||
if (opts->movie_aspect == 0) {
|
||||
m.p_w = m.p_h = 1;
|
||||
} else {
|
||||
AVRational a = av_d2q(opts->movie_aspect, INT_MAX);
|
||||
mp_image_params_set_dsize(&m, a.num, a.den);
|
||||
}
|
||||
}
|
||||
|
||||
// Assume square pixels if no aspect ratio is set at all.
|
||||
if (m.p_w <= 0 || m.p_h <= 0)
|
||||
m.p_w = m.p_h = 1;
|
||||
|
||||
m.rotate = p->codec->rotate;
|
||||
m.stereo3d = p->codec->stereo_mode;
|
||||
|
||||
if (opts->video_rotate < 0) {
|
||||
m.rotate = 0;
|
||||
} else {
|
||||
m.rotate = (m.rotate + opts->video_rotate) % 360;
|
||||
}
|
||||
|
||||
mp_colorspace_merge(&m.color, &c->color);
|
||||
|
||||
// Sanitize the HDR peak. Sadly necessary
|
||||
if (!is_valid_peak(m.color.sig_peak)) {
|
||||
MP_WARN(p, "Invalid HDR peak in stream: %f\n", m.color.sig_peak);
|
||||
m.color.sig_peak = 0.0;
|
||||
}
|
||||
|
||||
m.spherical = c->spherical;
|
||||
if (m.spherical.type == MP_SPHERICAL_AUTO)
|
||||
m.spherical.type = MP_SPHERICAL_NONE;
|
||||
|
||||
// Guess missing colorspace fields from metadata. This guarantees all
|
||||
// fields are at least set to legal values afterwards.
|
||||
mp_image_params_guess_csp(&m);
|
||||
|
||||
p->last_format = *params;
|
||||
p->fixed_format = m;
|
||||
}
|
||||
|
||||
static void process_video_frame(struct priv *p, struct mp_image *mpi)
|
||||
{
|
||||
struct MPOpts *opts = p->opts;
|
||||
|
||||
// Note: the PTS is reordered, but the DTS is not. Both should be monotonic.
|
||||
double pts = mpi->pts;
|
||||
double dts = mpi->dts;
|
||||
|
||||
if (pts != MP_NOPTS_VALUE) {
|
||||
if (pts < p->codec_pts)
|
||||
p->num_codec_pts_problems++;
|
||||
p->codec_pts = mpi->pts;
|
||||
}
|
||||
|
||||
if (dts != MP_NOPTS_VALUE) {
|
||||
if (dts <= p->codec_dts)
|
||||
p->num_codec_dts_problems++;
|
||||
p->codec_dts = mpi->dts;
|
||||
}
|
||||
|
||||
if (p->has_broken_packet_pts < 0)
|
||||
p->has_broken_packet_pts++;
|
||||
if (p->num_codec_pts_problems)
|
||||
p->has_broken_packet_pts = 1;
|
||||
|
||||
// If PTS is unset, or non-monotonic, fall back to DTS.
|
||||
if ((p->num_codec_pts_problems > p->num_codec_dts_problems ||
|
||||
pts == MP_NOPTS_VALUE) && dts != MP_NOPTS_VALUE)
|
||||
pts = dts;
|
||||
|
||||
if (!opts->correct_pts || pts == MP_NOPTS_VALUE) {
|
||||
double fps = p->public.fps > 0 ? p->public.fps : 25;
|
||||
|
||||
if (opts->correct_pts) {
|
||||
if (p->has_broken_decoded_pts <= 1) {
|
||||
MP_WARN(p, "No video PTS! Making something up. Using "
|
||||
"%f FPS.\n", fps);
|
||||
if (p->has_broken_decoded_pts == 1)
|
||||
MP_WARN(p, "Ignoring further missing PTS warnings.\n");
|
||||
p->has_broken_decoded_pts++;
|
||||
}
|
||||
}
|
||||
|
||||
double frame_time = 1.0f / fps;
|
||||
double base = p->first_packet_pdts;
|
||||
pts = p->pts;
|
||||
if (pts == MP_NOPTS_VALUE) {
|
||||
pts = base == MP_NOPTS_VALUE ? 0 : base;
|
||||
} else {
|
||||
pts += frame_time;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mp_image_params_equal(&p->last_format, &mpi->params))
|
||||
fix_image_params(p, &mpi->params);
|
||||
|
||||
mpi->params = p->fixed_format;
|
||||
mpi->nominal_fps = p->public.fps;
|
||||
|
||||
mpi->pts = pts;
|
||||
p->pts = pts;
|
||||
|
||||
// Compensate for incorrectly using mpeg-style DTS for avi timestamps.
|
||||
if (p->decoder && p->decoder->control && p->codec->avi_dts &&
|
||||
opts->correct_pts && mpi->pts != MP_NOPTS_VALUE && p->public.fps > 0)
|
||||
{
|
||||
int delay = -1;
|
||||
p->decoder->control(p->decoder->f, VDCTRL_GET_BFRAMES, &delay);
|
||||
mpi->pts -= MPMAX(delay, 0) / p->public.fps;
|
||||
}
|
||||
|
||||
struct demux_packet *ccpkt = new_demux_packet_from_buf(mpi->a53_cc);
|
||||
if (ccpkt) {
|
||||
av_buffer_unref(&mpi->a53_cc);
|
||||
ccpkt->pts = mpi->pts;
|
||||
ccpkt->dts = mpi->dts;
|
||||
demuxer_feed_caption(p->header, ccpkt);
|
||||
}
|
||||
|
||||
if (mpi->pts == MP_NOPTS_VALUE || mpi->pts >= p->start_pts)
|
||||
p->start_pts = MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
void mp_decoder_wrapper_reset_params(struct mp_decoder_wrapper *d)
|
||||
{
|
||||
struct priv *p = d->f->priv;
|
||||
p->last_format = (struct mp_image_params){0};
|
||||
}
|
||||
|
||||
void mp_decoder_wrapper_get_video_dec_params(struct mp_decoder_wrapper *d,
|
||||
struct mp_image_params *m)
|
||||
{
|
||||
struct priv *p = d->f->priv;
|
||||
*m = p->dec_format;
|
||||
}
|
||||
|
||||
static void process_audio_frame(struct priv *p, struct mp_aframe *aframe)
|
||||
{
|
||||
double frame_pts = mp_aframe_get_pts(aframe);
|
||||
if (frame_pts != MP_NOPTS_VALUE) {
|
||||
if (p->pts != MP_NOPTS_VALUE)
|
||||
MP_STATS(p, "value %f audio-pts-err", p->pts - frame_pts);
|
||||
|
||||
double diff = fabs(p->pts - frame_pts);
|
||||
|
||||
// Attempt to detect jumps in PTS. Even for the lowest sample rates and
|
||||
// with worst container rounded timestamp, this should be a margin more
|
||||
// than enough.
|
||||
if (p->pts != MP_NOPTS_VALUE && diff > 0.1) {
|
||||
MP_WARN(p, "Invalid audio PTS: %f -> %f\n", p->pts, frame_pts);
|
||||
if (diff >= 5)
|
||||
p->public.pts_reset = true;
|
||||
}
|
||||
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (p->pts == MP_NOPTS_VALUE || diff > 0.001)
|
||||
p->pts = frame_pts;
|
||||
}
|
||||
|
||||
if (p->pts == MP_NOPTS_VALUE && p->header->missing_timestamps)
|
||||
p->pts = 0;
|
||||
|
||||
mp_aframe_set_pts(aframe, p->pts);
|
||||
|
||||
if (p->pts != MP_NOPTS_VALUE)
|
||||
p->pts += mp_aframe_duration(aframe);
|
||||
}
|
||||
|
||||
|
||||
// Frames before the start timestamp can be dropped. (Used for hr-seek.)
|
||||
void mp_decoder_wrapper_set_start_pts(struct mp_decoder_wrapper *d, double pts)
|
||||
{
|
||||
struct priv *p = d->f->priv;
|
||||
p->start_pts = pts;
|
||||
}
|
||||
|
||||
static bool is_new_segment(struct priv *p, struct mp_frame frame)
|
||||
{
|
||||
if (frame.type != MP_FRAME_PACKET)
|
||||
return false;
|
||||
struct demux_packet *pkt = frame.data;
|
||||
return pkt->segmented && (pkt->start != p->start || pkt->end != p->end ||
|
||||
pkt->codec != p->codec);
|
||||
}
|
||||
|
||||
static void feed_packet(struct priv *p)
|
||||
{
|
||||
if (!p->decoder || !mp_pin_in_needs_data(p->decoder->f->pins[0]))
|
||||
return;
|
||||
|
||||
if (!p->packet.type && !p->new_segment) {
|
||||
p->packet = mp_pin_out_read(p->demux);
|
||||
if (!p->packet.type)
|
||||
return;
|
||||
if (p->packet.type != MP_FRAME_EOF && p->packet.type != MP_FRAME_PACKET) {
|
||||
MP_ERR(p, "invalid frame type from demuxer\n");
|
||||
mp_frame_unref(&p->packet);
|
||||
mp_filter_internal_mark_failed(p->f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush current data if the packet is a new segment.
|
||||
if (is_new_segment(p, p->packet)) {
|
||||
assert(!p->new_segment);
|
||||
p->new_segment = p->packet.data;
|
||||
p->packet = MP_EOF_FRAME;
|
||||
}
|
||||
|
||||
assert(p->packet.type == MP_FRAME_PACKET || p->packet.type == MP_FRAME_EOF);
|
||||
struct demux_packet *packet = p->packet.data;
|
||||
|
||||
// For video framedropping, including parts of the hr-seek logic.
|
||||
if (p->decoder->control) {
|
||||
double start_pts = p->start_pts;
|
||||
if (p->start != MP_NOPTS_VALUE && (start_pts == MP_NOPTS_VALUE ||
|
||||
p->start > start_pts))
|
||||
start_pts = p->start;
|
||||
|
||||
int framedrop_type = 0;
|
||||
|
||||
if (p->public.attempt_framedrops)
|
||||
framedrop_type = 1;
|
||||
|
||||
if (start_pts != MP_NOPTS_VALUE && packet &&
|
||||
packet->pts < start_pts - .005 && !p->has_broken_packet_pts)
|
||||
framedrop_type = 2;
|
||||
|
||||
p->decoder->control(p->decoder->f, VDCTRL_SET_FRAMEDROP, &framedrop_type);
|
||||
}
|
||||
|
||||
if (p->public.recorder_sink)
|
||||
mp_recorder_feed_packet(p->public.recorder_sink, packet);
|
||||
|
||||
double pkt_pts = packet ? packet->pts : MP_NOPTS_VALUE;
|
||||
double pkt_dts = packet ? packet->dts : MP_NOPTS_VALUE;
|
||||
|
||||
if (pkt_pts == MP_NOPTS_VALUE)
|
||||
p->has_broken_packet_pts = 1;
|
||||
|
||||
if (packet && packet->dts == MP_NOPTS_VALUE && !p->codec->avi_dts)
|
||||
packet->dts = packet->pts;
|
||||
|
||||
double pkt_pdts = pkt_pts == MP_NOPTS_VALUE ? pkt_dts : pkt_pts;
|
||||
if (p->first_packet_pdts == MP_NOPTS_VALUE)
|
||||
p->first_packet_pdts = pkt_pdts;
|
||||
|
||||
mp_pin_in_write(p->decoder->f->pins[0], p->packet);
|
||||
p->packet = MP_NO_FRAME;
|
||||
|
||||
p->packets_without_output += 1;
|
||||
}
|
||||
|
||||
// Return true if the current frame is outside segment range.
|
||||
static bool process_decoded_frame(struct priv *p, struct mp_frame *frame)
|
||||
{
|
||||
if (frame->type == MP_FRAME_EOF) {
|
||||
// if we were just draining current segment, don't propagate EOF
|
||||
if (p->new_segment)
|
||||
mp_frame_unref(frame);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool segment_ended = false;
|
||||
|
||||
if (frame->type == MP_FRAME_VIDEO) {
|
||||
struct mp_image *mpi = frame->data;
|
||||
|
||||
process_video_frame(p, mpi);
|
||||
|
||||
if (mpi->pts != MP_NOPTS_VALUE) {
|
||||
double vpts = mpi->pts;
|
||||
segment_ended = p->end != MP_NOPTS_VALUE && vpts >= p->end;
|
||||
if ((p->start != MP_NOPTS_VALUE && vpts < p->start) || segment_ended)
|
||||
mp_frame_unref(frame);
|
||||
}
|
||||
} else if (frame->type == MP_FRAME_AUDIO) {
|
||||
struct mp_aframe *aframe = frame->data;
|
||||
|
||||
process_audio_frame(p, aframe);
|
||||
|
||||
mp_aframe_clip_timestamps(aframe, p->start, p->end);
|
||||
double pts = mp_aframe_get_pts(aframe);
|
||||
if (pts != MP_NOPTS_VALUE && p->start != MP_NOPTS_VALUE)
|
||||
segment_ended = pts >= p->end;
|
||||
if (mp_aframe_get_size(aframe) == 0)
|
||||
mp_frame_unref(frame);
|
||||
} else {
|
||||
MP_ERR(p, "unknown frame type from decoder\n");
|
||||
}
|
||||
|
||||
return segment_ended;
|
||||
}
|
||||
|
||||
static void read_frame(struct priv *p)
|
||||
{
|
||||
struct mp_pin *pin = p->f->ppins[0];
|
||||
|
||||
if (!p->decoder || !mp_pin_in_needs_data(pin))
|
||||
return;
|
||||
|
||||
if (p->decoded_coverart.type) {
|
||||
if (p->coverart_returned == 0) {
|
||||
mp_pin_in_write(pin, mp_frame_ref(p->decoded_coverart));
|
||||
p->coverart_returned = 1;
|
||||
} else if (p->coverart_returned == 1) {
|
||||
mp_pin_in_write(pin, MP_EOF_FRAME);
|
||||
p->coverart_returned = 2;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
struct mp_frame frame = mp_pin_out_read(p->decoder->f->pins[1]);
|
||||
if (!frame.type)
|
||||
return;
|
||||
|
||||
if (p->public.attempt_framedrops) {
|
||||
int dropped = MPMAX(0, p->packets_without_output - 1);
|
||||
p->public.attempt_framedrops =
|
||||
MPMAX(0, p->public.attempt_framedrops - dropped);
|
||||
p->public.dropped_frames += dropped;
|
||||
}
|
||||
p->packets_without_output = 0;
|
||||
|
||||
bool segment_ended = process_decoded_frame(p, &frame);
|
||||
|
||||
// If there's a new segment, start it as soon as we're drained/finished.
|
||||
if (segment_ended && p->new_segment) {
|
||||
struct demux_packet *new_segment = p->new_segment;
|
||||
p->new_segment = NULL;
|
||||
|
||||
reset_decoder(p);
|
||||
|
||||
if (p->codec != new_segment->codec) {
|
||||
p->codec = new_segment->codec;
|
||||
if (!mp_decoder_wrapper_reinit(&p->public))
|
||||
mp_filter_internal_mark_failed(p->f);
|
||||
}
|
||||
|
||||
p->start = new_segment->start;
|
||||
p->end = new_segment->end;
|
||||
|
||||
p->packet = MAKE_FRAME(MP_FRAME_PACKET, new_segment);
|
||||
mp_filter_internal_mark_progress(p->f);
|
||||
}
|
||||
|
||||
if (!frame.type) {
|
||||
mp_filter_internal_mark_progress(p->f); // make it retry
|
||||
return;
|
||||
}
|
||||
|
||||
if (p->header->attached_picture && frame.type == MP_FRAME_VIDEO) {
|
||||
p->decoded_coverart = mp_frame_ref(frame);
|
||||
p->coverart_returned = 1;
|
||||
}
|
||||
|
||||
mp_pin_in_write(pin, frame);
|
||||
}
|
||||
|
||||
static void process(struct mp_filter *f)
|
||||
{
|
||||
struct priv *p = f->priv;
|
||||
|
||||
feed_packet(p);
|
||||
read_frame(p);
|
||||
}
|
||||
|
||||
static const struct mp_filter_info decode_wrapper_filter = {
|
||||
.name = "decode",
|
||||
.priv_size = sizeof(struct priv),
|
||||
.process = process,
|
||||
.reset = reset,
|
||||
.destroy = destroy,
|
||||
};
|
||||
|
||||
struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent,
|
||||
struct sh_stream *src)
|
||||
{
|
||||
struct mp_filter *f = mp_filter_create(parent, &decode_wrapper_filter);
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
struct priv *p = f->priv;
|
||||
struct mp_decoder_wrapper *w = &p->public;
|
||||
p->opts = f->global->opts;
|
||||
p->log = f->log;
|
||||
p->f = f;
|
||||
p->header = src;
|
||||
p->codec = p->header->codec;
|
||||
w->f = f;
|
||||
|
||||
mp_filter_add_pin(f, MP_PIN_OUT, "out");
|
||||
|
||||
if (p->header->type == STREAM_VIDEO) {
|
||||
p->log = f->log = mp_log_new(f, parent->log, "!vd");
|
||||
|
||||
p->public.fps = src->codec->fps;
|
||||
|
||||
MP_VERBOSE(p, "Container reported FPS: %f\n", p->public.fps);
|
||||
|
||||
if (p->opts->force_fps) {
|
||||
p->public.fps = p->opts->force_fps;
|
||||
MP_INFO(p, "FPS forced to %5.3f.\n", p->public.fps);
|
||||
MP_INFO(p, "Use --no-correct-pts to force FPS based timing.\n");
|
||||
}
|
||||
} else if (p->header->type == STREAM_AUDIO) {
|
||||
p->log = f->log = mp_log_new(f, parent->log, "!ad");
|
||||
}
|
||||
|
||||
struct mp_filter *demux = mp_demux_in_create(f, p->header);
|
||||
if (!demux)
|
||||
goto error;
|
||||
p->demux = demux->pins[0];
|
||||
|
||||
return w;
|
||||
error:
|
||||
talloc_free(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void lavc_process(struct mp_filter *f, bool *eof_flag,
|
||||
bool (*send)(struct mp_filter *f, struct demux_packet *pkt),
|
||||
bool (*receive)(struct mp_filter *f, struct mp_frame *res))
|
||||
{
|
||||
if (!mp_pin_in_needs_data(f->ppins[1]))
|
||||
return;
|
||||
|
||||
struct mp_frame frame = {0};
|
||||
if (!receive(f, &frame)) {
|
||||
if (!*eof_flag)
|
||||
mp_pin_in_write(f->ppins[1], MP_EOF_FRAME);
|
||||
*eof_flag = true;
|
||||
} else if (frame.type) {
|
||||
*eof_flag = false;
|
||||
mp_pin_in_write(f->ppins[1], frame);
|
||||
} else {
|
||||
// Need to feed a packet.
|
||||
frame = mp_pin_out_read(f->ppins[0]);
|
||||
struct demux_packet *pkt = NULL;
|
||||
if (frame.type == MP_FRAME_PACKET) {
|
||||
pkt = frame.data;
|
||||
} else if (frame.type != MP_FRAME_EOF) {
|
||||
if (frame.type) {
|
||||
MP_ERR(f, "unexpected frame type\n");
|
||||
mp_frame_unref(&frame);
|
||||
mp_filter_internal_mark_failed(f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!send(f, pkt)) {
|
||||
// Should never happen, but can happen with broken decoders.
|
||||
MP_WARN(f, "could not consume packet\n");
|
||||
mp_pin_out_unread(f->ppins[0], frame);
|
||||
mp_filter_wakeup(f);
|
||||
return;
|
||||
}
|
||||
talloc_free(pkt);
|
||||
mp_filter_internal_mark_progress(f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "filter.h"
|
||||
|
||||
struct sh_stream;
|
||||
struct mp_codec_params;
|
||||
struct mp_image_params;
|
||||
struct mp_decoder_list;
|
||||
struct demux_packet;
|
||||
|
||||
// (free with talloc_free(mp_decoder_wrapper.f)
|
||||
struct mp_decoder_wrapper {
|
||||
// Filter with no input and 1 output, which returns the decoded data.
|
||||
struct mp_filter *f;
|
||||
|
||||
// For informational purposes.
|
||||
char *decoder_desc;
|
||||
|
||||
// Can be set by user.
|
||||
struct mp_recorder_sink *recorder_sink;
|
||||
|
||||
// --- for STREAM_VIDEO
|
||||
|
||||
// FPS from demuxer or from user override
|
||||
float fps;
|
||||
|
||||
// Framedrop control for playback (not used for hr seek etc.)
|
||||
int attempt_framedrops; // try dropping this many frames
|
||||
int dropped_frames; // total frames _probably_ dropped
|
||||
|
||||
// --- for STREAM_AUDIO
|
||||
|
||||
// Prefer spdif wrapper over real decoders.
|
||||
bool try_spdif;
|
||||
|
||||
// A pts reset was observed (audio only, heuristic).
|
||||
bool pts_reset;
|
||||
};
|
||||
|
||||
// Create the decoder wrapper for the given stream, plus underlying decoder.
|
||||
// The src stream must be selected, and remain valid and selected until the
|
||||
// wrapper is destroyed.
|
||||
struct mp_decoder_wrapper *mp_decoder_wrapper_create(struct mp_filter *parent,
|
||||
struct sh_stream *src);
|
||||
|
||||
struct mp_decoder_list *video_decoder_list(void);
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
|
||||
// For precise seeking: if possible, try to drop frames up until the given PTS.
|
||||
// This is automatically unset if the target is reached, or on reset.
|
||||
void mp_decoder_wrapper_set_start_pts(struct mp_decoder_wrapper *d, double pts);
|
||||
|
||||
enum dec_ctrl {
|
||||
VDCTRL_FORCE_HWDEC_FALLBACK, // force software decoding fallback
|
||||
VDCTRL_GET_HWDEC,
|
||||
VDCTRL_REINIT,
|
||||
VDCTRL_GET_BFRAMES,
|
||||
// framedrop mode: 0=none, 1=standard, 2=hrseek
|
||||
VDCTRL_SET_FRAMEDROP,
|
||||
};
|
||||
|
||||
int mp_decoder_wrapper_control(struct mp_decoder_wrapper *d,
|
||||
enum dec_ctrl cmd, void *arg);
|
||||
|
||||
// Force it to reevaluate output parameters (for overrides like aspect).
|
||||
void mp_decoder_wrapper_reset_params(struct mp_decoder_wrapper *d);
|
||||
|
||||
void mp_decoder_wrapper_get_video_dec_params(struct mp_decoder_wrapper *d,
|
||||
struct mp_image_params *p);
|
||||
|
||||
bool mp_decoder_wrapper_reinit(struct mp_decoder_wrapper *d);
|
||||
|
||||
struct mp_decoder {
|
||||
// Bidirectional filter; takes MP_FRAME_PACKET for input.
|
||||
struct mp_filter *f;
|
||||
|
||||
// Can be set by decoder impl. on init for "special" functionality.
|
||||
int (*control)(struct mp_filter *f, enum dec_ctrl cmd, void *arg);
|
||||
};
|
||||
|
||||
struct mp_decoder_fns {
|
||||
struct mp_decoder *(*create)(struct mp_filter *parent,
|
||||
struct mp_codec_params *codec,
|
||||
const char *decoder);
|
||||
void (*add_decoders)(struct mp_decoder_list *list);
|
||||
};
|
||||
|
||||
extern const struct mp_decoder_fns vd_lavc;
|
||||
extern const struct mp_decoder_fns ad_lavc;
|
||||
extern const struct mp_decoder_fns ad_spdif;
|
||||
|
||||
// Convenience wrapper for lavc based decoders. eof_flag must be set to false
|
||||
// on init and resets.
|
||||
void lavc_process(struct mp_filter *f, bool *eof_flag,
|
||||
bool (*send)(struct mp_filter *f, struct demux_packet *pkt),
|
||||
bool (*receive)(struct mp_filter *f, struct mp_frame *res));
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
Loaded 100 of 341 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user