Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c226bc7616 | ||
|
|
e60a36bd4e | ||
|
|
bc97d60542 | ||
|
|
4121016689 | ||
|
|
b636b19058 | ||
|
|
cb9b60ef19 | ||
|
|
31f28da0f3 | ||
|
|
ef0b2e33b1 | ||
|
|
2a5b61244f | ||
|
|
c4ba600832 | ||
|
|
c218d9e960 | ||
|
|
a47d849df7 | ||
|
|
6980575e15 | ||
|
|
724f60bf9a | ||
|
|
74d4073771 | ||
|
|
93104142f4 | ||
|
|
9a5e062a04 | ||
|
|
6b676d82fd | ||
|
|
af103aebd7 | ||
|
|
969c011522 | ||
|
|
1a8af89b7d | ||
|
|
e057629493 | ||
|
|
cb31e72589 | ||
|
|
ed644b0d33 | ||
|
|
23993e91f3 | ||
|
|
68dc869d6a | ||
|
|
e5f61c2bd5 | ||
|
|
2fbd1d3610 | ||
|
|
05e4df3f0c | ||
|
|
4aaa83339c | ||
|
|
5d2dfbdfca | ||
|
|
4e3663a2da | ||
|
|
e6952c7053 | ||
|
|
0f83caf96a | ||
|
|
1e53fc3a15 | ||
|
|
a1dec6f54a | ||
|
|
bbcd0b6a03 | ||
|
|
7bba97b301 | ||
|
|
0a0967f48b | ||
|
|
e02cb674ce | ||
|
|
f5bbb5aed2 | ||
|
|
07f8b64754 | ||
|
|
3a7e86ff6e | ||
|
|
12e251c29e | ||
|
|
814dacdd7d | ||
|
|
86fa1e6129 | ||
|
|
795b65b2ff | ||
|
|
ef2d6ed125 | ||
|
|
fd1bc95b48 | ||
|
|
d7a7e9a8c8 | ||
|
|
4a8647b6b8 | ||
|
|
4b5de33e89 | ||
|
|
9d2bcd2ef9 | ||
|
|
e392d6610d | ||
|
|
78d808c5bd | ||
|
|
b9ba9a898a | ||
|
|
e2a54bb1ca | ||
|
|
ec4a8bdb3d | ||
|
|
dc6f8d4a0a | ||
|
|
9024cb1685 | ||
|
|
5d74ba98ce | ||
|
|
87190969a7 | ||
|
|
367e9fb7f1 | ||
|
|
2ded41d2be | ||
|
|
062349ff5b | ||
|
|
eab92cec60 | ||
|
|
3759a3f40b | ||
|
|
d81b5690df | ||
|
|
8c2e299b84 | ||
|
|
469666b75b | ||
|
|
896a97c2e2 | ||
|
|
56c52bd2e1 | ||
|
|
355a145a48 | ||
|
|
3a21d71ad0 | ||
|
|
7ca4a453e0 | ||
|
|
52a0cbe456 | ||
|
|
5640ceb838 | ||
|
|
cfed84af2b | ||
|
|
259f219438 | ||
|
|
c7b73edc65 | ||
|
|
32cc190a55 | ||
|
|
39ae261cc5 | ||
|
|
78bcbe289e | ||
|
|
91a3bda6f5 | ||
|
|
7fd3dbcd52 | ||
|
|
d41f0a54b0 | ||
|
|
d4ee5e5a8a | ||
|
|
b2e5eb13bc | ||
|
|
088a0075c5 | ||
|
|
9f70117233 | ||
|
|
eccabeb81c | ||
|
|
0c4a73fa21 | ||
|
|
fa7beaad1f | ||
|
|
00091411ec | ||
|
|
0b144eac39 | ||
|
|
c30aa23401 | ||
|
|
efa9fe8edf | ||
|
|
3c3361217c | ||
|
|
356e703510 | ||
|
|
f3c35d8108 | ||
|
|
dea738a245 | ||
|
|
251299da4f | ||
|
|
2e3db648b5 | ||
|
|
08a4af5e6e | ||
|
|
0432ab8f09 | ||
|
|
0a1c87464b | ||
|
|
48f60e182a | ||
|
|
98af572484 | ||
|
|
5bcb22beea | ||
|
|
546f7472fb | ||
|
|
45687c17f7 | ||
|
|
2f986a5980 | ||
|
|
27b59e1940 | ||
|
|
aaf6e3b58d | ||
|
|
f95cde60ff | ||
|
|
3fe5ff99c6 | ||
|
|
f918ec4b2b | ||
|
|
c3e11f7b7c | ||
|
|
f3f4e048d8 | ||
|
|
46b60a3e72 | ||
|
|
f127869037 | ||
|
|
cb4342dda4 | ||
|
|
ebe798a333 | ||
|
|
dcfde2934d | ||
|
|
6bb48a10db | ||
|
|
3623cec7d2 | ||
|
|
d60db967bd | ||
|
|
99d9921f39 | ||
|
|
16d276308a | ||
|
|
b4acfcc8aa | ||
|
|
c6b83fec7d | ||
|
|
c0ef3cf9c2 | ||
|
|
8f51418ca4 | ||
|
|
7d01a16f64 | ||
|
|
f53c7f1a78 | ||
|
|
7531275f62 | ||
|
|
fde784129f | ||
|
|
bd9c0a10e5 | ||
|
|
e11a20a812 | ||
|
|
64cd0f4292 | ||
|
|
510c6a1be4 | ||
|
|
fb8deb69a6 | ||
|
|
77e1e8e38e | ||
|
|
79974e7ad9 | ||
|
|
ea18cb99ba | ||
|
|
c5361d12d8 | ||
|
|
358932a109 | ||
|
|
831fc4f012 | ||
|
|
f29bba1123 | ||
|
|
48f9ea7104 | ||
|
|
ae94f329a9 | ||
|
|
85488f6892 | ||
|
|
4a4a9f3302 | ||
|
|
588b2f48e5 | ||
|
|
e656889624 | ||
|
|
91a1b17104 | ||
|
|
6e45e1de77 | ||
|
|
d191d76e52 | ||
|
|
f53d73b9dc | ||
|
|
79f48500e2 | ||
|
|
b5bc93d009 | ||
|
|
a14f9249c4 | ||
|
|
8b47e5daa4 | ||
|
|
bf2e2bf3e8 | ||
|
|
43e811cb4b | ||
|
|
2d44dfaba9 | ||
|
|
f4d62dc4a0 | ||
|
|
374600cec0 | ||
|
|
5a6ba2a4fd | ||
|
|
e246c3f060 | ||
|
|
61afe3820a | ||
|
|
60048b7eb9 | ||
|
|
7be98ef1b2 | ||
|
|
2eac58eaa9 | ||
|
|
065bb635d9 | ||
|
|
c5827387fd | ||
|
|
1e00bcc14c | ||
|
|
7635438592 | ||
|
|
e518bf2c72 | ||
|
|
d47b708f00 | ||
|
|
2fb2f9b93a | ||
|
|
995c47da9a | ||
|
|
da9590d368 | ||
|
|
34a33ebd4d | ||
|
|
e58ecc3630 | ||
|
|
6d83455a95 | ||
|
|
35be389031 | ||
|
|
885e991312 | ||
|
|
26b6873ffd | ||
|
|
71a8aa2e9a | ||
|
|
16b48a429d | ||
|
|
f2110f582d | ||
|
|
8660b4c9f0 | ||
|
|
ab6fac43b4 | ||
|
|
5d2f1da7c5 | ||
|
|
771a8bf5c6 | ||
|
|
08cd50b84b | ||
|
|
0b1ef81498 | ||
|
|
fc76966d9e | ||
|
|
0fe62bc1d2 | ||
|
|
d72bcc8041 | ||
|
|
329a7147d0 | ||
|
|
4f4eea6b44 | ||
|
|
33e72ed451 | ||
|
|
6a3da439cd | ||
|
|
f98e1b0b96 | ||
|
|
7cd1a3c766 | ||
|
|
46f1ce1e2f | ||
|
|
be230d16e5 | ||
|
|
5b6cce2b73 | ||
|
|
923e3c7b20 | ||
|
|
d81fb97f45 | ||
|
|
3abf9c9204 | ||
|
|
ea23a86f49 | ||
|
|
8ed32e90c9 | ||
|
|
37cf92c07a | ||
|
|
59972fbfe1 | ||
|
|
f110552898 | ||
|
|
315e2e3da8 | ||
|
|
b57debe5b3 | ||
|
|
8c7f9dc1a8 | ||
|
|
823c353faa | ||
|
|
06219c7f88 | ||
|
|
5d47f9a565 | ||
|
|
1298c29dc7 | ||
|
|
76a73a0a5d | ||
|
|
b2ce00ed56 | ||
|
|
d057e7a142 | ||
|
|
3738bfb451 | ||
|
|
8523e1fd95 | ||
|
|
fd5098a409 | ||
|
|
c0f272fcd1 | ||
|
|
1b71cfccba | ||
|
|
549a9ea6fa | ||
|
|
614efea3e6 | ||
|
|
c57304a591 | ||
|
|
2d6da37b6b | ||
|
|
e40851ea22 | ||
|
|
9ed5dae480 | ||
|
|
c6953bfa8c | ||
|
|
deb1c3c7a8 | ||
|
|
9ca1592f3f | ||
|
|
17c5738cb4 | ||
|
|
d5615102d5 | ||
|
|
40c9b38023 | ||
|
|
dc9a5cbfd7 | ||
|
|
65499d863a | ||
|
|
6e6c32ed6c | ||
|
|
f9fe5d06ad | ||
|
|
f3b6966d14 | ||
|
|
740fdc139f | ||
|
|
247ec0cb84 | ||
|
|
13a63331b6 | ||
|
|
9278ce98f7 | ||
|
|
4ce53025cb | ||
|
|
3e58ce96ac | ||
|
|
acb74236ac | ||
|
|
c5094206ce | ||
|
|
24478a8a72 | ||
|
|
e081e46950 | ||
|
|
22c76e85db | ||
|
|
d1d864546c | ||
|
|
69e7b0d3ea | ||
|
|
ad56f2c46a | ||
|
|
393bb2a565 | ||
|
|
ea098d3462 | ||
|
|
1c3bbd9318 | ||
|
|
e911e208b8 | ||
|
|
5c74da4503 | ||
|
|
c071c30bcd | ||
|
|
b01855714b | ||
|
|
5a60f594e5 | ||
|
|
31b73d5ca0 | ||
|
|
b753c229f7 | ||
|
|
7ea22fe889 | ||
|
|
9d0af06811 | ||
|
|
6f9973618c | ||
|
|
b40d8fdfa3 | ||
|
|
22291a2587 | ||
|
|
fc50f3772c | ||
|
|
65280e47cd | ||
|
|
30adf280fd | ||
|
|
9bac0ea421 | ||
|
|
761eeacf54 | ||
|
|
7be37337f4 | ||
|
|
754ad1d730 | ||
|
|
0cf187caca | ||
|
|
07c11656e3 | ||
|
|
67a6203ce0 | ||
|
|
3df95ee57a | ||
|
|
61bc96518a | ||
|
|
47d9fbd133 | ||
|
|
fb7c5804bb | ||
|
|
0fd5a24ecb | ||
|
|
4a15bc6d73 | ||
|
|
9215a9e598 | ||
|
|
689cf04571 | ||
|
|
8e6704acdb | ||
|
|
28c7dec157 | ||
|
|
56058a95e5 | ||
|
|
f72eb5b394 | ||
|
|
454fff39ad | ||
|
|
8986b37fe7 | ||
|
|
d0bd39eb90 | ||
|
|
7ecac3ae6f | ||
|
|
a6074a3375 | ||
|
|
36f8d41702 | ||
|
|
1878a4655d | ||
|
|
5700da4a8f | ||
|
|
43e964e2d6 | ||
|
|
a30e727266 | ||
|
|
788929e4e0 | ||
|
|
3682df2dd5 | ||
|
|
81ec9cf596 | ||
|
|
b00eab525a | ||
|
|
972ea9ca59 | ||
|
|
3d844cddf8 | ||
|
|
0b082b2086 | ||
|
|
bb9aad097a | ||
|
|
9fcc517cee | ||
|
|
de4c74e5a4 | ||
|
|
9cc5507079 | ||
|
|
bea2e39721 | ||
|
|
2b1656b1ac | ||
|
|
1c5fd2d901 | ||
|
|
d38d26057a | ||
|
|
262ceca731 | ||
|
|
6fd7f5a111 | ||
|
|
b51957fab5 | ||
|
|
4123c9642d | ||
|
|
ff37d7efec | ||
|
|
54c48bd801 | ||
|
|
a15181e5df | ||
|
|
b906eb37ed | ||
|
|
7373158703 | ||
|
|
69d5be1ee9 | ||
|
|
bd9bba40ee | ||
|
|
aeb3df0e2c | ||
|
|
9c286d7472 | ||
|
|
68796fbdf5 | ||
|
|
5b5db336e9 | ||
|
|
38ac5d5e7b | ||
|
|
9bd886f449 | ||
|
|
393a069112 | ||
|
|
8ceb935bd8 | ||
|
|
88b584656d | ||
|
|
a9150a49d7 | ||
|
|
b3e74f652b | ||
|
|
c1cb04b6a3 | ||
|
|
ee337b47be | ||
|
|
b3a3ed00f0 | ||
|
|
65f5fbc5d4 | ||
|
|
352904fd03 | ||
|
|
2dfea67f3b | ||
|
|
a9ffe38aa1 | ||
|
|
026b75e7f5 | ||
|
|
2179879172 | ||
|
|
2d76c145c3 | ||
|
|
645a0d7791 | ||
|
|
62acbd795f | ||
|
|
e44882d792 | ||
|
|
29ea6e5e4a | ||
|
|
68c77d955f | ||
|
|
45c3e0f0d0 | ||
|
|
098ff4174c | ||
|
|
9009601c2d | ||
|
|
4853eca8c6 | ||
|
|
079f67268f | ||
|
|
15bb05d2fe | ||
|
|
48015009b7 | ||
|
|
899d3e55b3 | ||
|
|
256d9d2b89 | ||
|
|
b1dd3beda9 | ||
|
|
240ba92bed | ||
|
|
0348cd080f | ||
|
|
49f73eaf7b | ||
|
|
9eccedf447 | ||
|
|
9f42760538 | ||
|
|
f8df0528b5 | ||
|
|
72e94941c9 | ||
|
|
84fba1df21 | ||
|
|
ca87e623b5 | ||
|
|
306c554376 | ||
|
|
e5f49d9685 | ||
|
|
5c9f164caf | ||
|
|
fc18e1df62 | ||
|
|
68191fdca7 | ||
|
|
5353571de0 | ||
|
|
15a5d33b79 | ||
|
|
b9cc33de58 | ||
|
|
8ff96f0216 | ||
|
|
d941a57bd3 | ||
|
|
6bd021e4ee | ||
|
|
e293e411b5 | ||
|
|
335278b86e | ||
|
|
c4707cdee6 | ||
|
|
e76aa7e8db | ||
|
|
66079048ea | ||
|
|
049e3ccb65 | ||
|
|
80d702dce8 | ||
|
|
afcef4c25b | ||
|
|
75f373cc46 | ||
|
|
cc72a4e8c3 | ||
|
|
cc4a0571fa | ||
|
|
c06a92e09e | ||
|
|
4ff2a60632 | ||
|
|
4aa017e301 | ||
|
|
c4e8c36071 | ||
|
|
5a3c299ca1 | ||
|
|
364af7c630 | ||
|
|
981048e041 | ||
|
|
035297212a | ||
|
|
79afa347cc | ||
|
|
3deedef195 | ||
|
|
f0d7437412 | ||
|
|
e431a7bbfd | ||
|
|
05b3a8cd14 | ||
|
|
561630cb01 | ||
|
|
4e5f1ec00e | ||
|
|
b0d3c2ede7 | ||
|
|
b46eaa3b43 | ||
|
|
5f7bd411fb | ||
|
|
0b911792ca | ||
|
|
26b6d74484 | ||
|
|
a02d77ba0d | ||
|
|
39b64fb176 | ||
|
|
9b56bbecdf | ||
|
|
b18a598444 | ||
|
|
a2d58d9986 | ||
|
|
f00040b9fc | ||
|
|
09763bef83 | ||
|
|
8262a4dd52 | ||
|
|
ca09cd68c8 | ||
|
|
e73c83d3df | ||
|
|
e6f6ae94f3 | ||
|
|
3bdbf6274c | ||
|
|
b6c40953cb | ||
|
|
887b2cc30f | ||
|
|
1bc902336a | ||
|
|
51307e9f18 | ||
|
|
70df6aa375 | ||
|
|
155857dbe6 | ||
|
|
0c40eee479 | ||
|
|
dd99c1e3b4 | ||
|
|
56ed5bf1c7 | ||
|
|
320627d600 | ||
|
|
51603e472d | ||
|
|
e047cc0931 | ||
|
|
3cfe98c684 | ||
|
|
f81f486c68 | ||
|
|
965031ccd5 | ||
|
|
362015cd77 | ||
|
|
dfc7b59909 | ||
|
|
034faaa9d8 | ||
|
|
7c3d78fd82 | ||
|
|
d53142f9ba | ||
|
|
070edd7300 | ||
|
|
3d4889e91e | ||
|
|
32c10956e0 | ||
|
|
6ae7a555bf | ||
|
|
449b948ee8 | ||
|
|
77d50cb3cf | ||
|
|
3858d37b61 | ||
|
|
09e07e92c5 | ||
|
|
7d2c6d60da | ||
|
|
c9d8bc088c | ||
|
|
f7c81c03b2 | ||
|
|
5bd6b76f5c | ||
|
|
ee85473d4a | ||
|
|
a228bf54c8 | ||
|
|
0cd217b039 | ||
|
|
bd41a3ab62 | ||
|
|
84ccebd9b9 | ||
|
|
e68b510a94 | ||
|
|
c160e5f31d | ||
|
|
d4712af5af | ||
|
|
809d235208 | ||
|
|
01d04b100f | ||
|
|
fe540f4477 | ||
|
|
70b3561270 | ||
|
|
2ec26b8396 | ||
|
|
cb694ffd8e | ||
|
|
7983cda5bf | ||
|
|
fd82e14888 | ||
|
|
fde20d10bc | ||
|
|
bea7675307 | ||
|
|
1f6e71c7fa | ||
|
|
a3d416c3d3 | ||
|
|
4b3faf9dc1 | ||
|
|
12ae19c449 | ||
|
|
b0b01aa250 | ||
|
|
7e6e47b8c4 | ||
|
|
013d63b081 | ||
|
|
5d8d8618c4 | ||
|
|
da0bf27930 | ||
|
|
af0a8d7478 | ||
|
|
00c9bdf3f7 | ||
|
|
73aa7484f2 | ||
|
|
46fff8d31a | ||
|
|
ee4c00698f | ||
|
|
f99e48ae6a | ||
|
|
7d9eab15f0 | ||
|
|
2f8b4dd480 | ||
|
|
ababfcbfe3 | ||
|
|
8fe33a11e2 | ||
|
|
11406b9215 | ||
|
|
062493fa7e | ||
|
|
55846641ea | ||
|
|
7c5188c27e | ||
|
|
a93fb460cd | ||
|
|
eef271c8f4 | ||
|
|
7a75e7c002 | ||
|
|
605dd928d3 | ||
|
|
f56555b514 | ||
|
|
e5e1088c92 | ||
|
|
b2646911a9 | ||
|
|
833375f88d | ||
|
|
86b5f1463c | ||
|
|
9054460bba | ||
|
|
05c398fb6c | ||
|
|
485ae095f7 | ||
|
|
5be40f035b | ||
|
|
eefe7ad28b | ||
|
|
51e4c065ff | ||
|
|
8a26e139af | ||
|
|
f54e555d27 | ||
|
|
3ff3749b7d | ||
|
|
622bcb0e37 | ||
|
|
9fda504d18 | ||
|
|
ed695ce689 | ||
|
|
949247d6e6 | ||
|
|
70f64f3ca9 | ||
|
|
5c90352ae6 | ||
|
|
0826b78bc5 | ||
|
|
3a8058658d | ||
|
|
8d51f08010 | ||
|
|
64f9e48bf1 | ||
|
|
016eab2209 | ||
|
|
bda111018c | ||
|
|
0ed206abd9 | ||
|
|
d30634b104 | ||
|
|
304d9d58dd | ||
|
|
9d16837c99 | ||
|
|
757c8baf8c | ||
|
|
5df4ee55f1 | ||
|
|
9cb036f297 | ||
|
|
0b1ba577b1 | ||
|
|
dff33893f2 | ||
|
|
9896994688 | ||
|
|
3706918311 | ||
|
|
cf9b415173 | ||
|
|
d3a26272cd | ||
|
|
0c6c62cb17 | ||
|
|
da59726776 | ||
|
|
66a958bb4f | ||
|
|
dfe40f770d | ||
|
|
6798525251 | ||
|
|
74e3d11f21 | ||
|
|
dfa88271a2 | ||
|
|
8ffd2f1dd4 | ||
|
|
6a1814dfb8 | ||
|
|
7e3d8e7134 | ||
|
|
46e49a37be | ||
|
|
bb17df1f07 | ||
|
|
4f5509e1dd | ||
|
|
85416bc36a | ||
|
|
021cb2c387 | ||
|
|
e232f1a731 | ||
|
|
5800d1845f | ||
|
|
5f97bd4223 | ||
|
|
649e155938 | ||
|
|
47e7676e7e | ||
|
|
786f37ae1c | ||
|
|
abb3f80998 | ||
|
|
b1a8e8dba6 | ||
|
|
244eff9201 | ||
|
|
2b4123f078 | ||
|
|
1944a34c23 | ||
|
|
78346e9c9a | ||
|
|
60664bc00b | ||
|
|
834a236514 | ||
|
|
cd9ee1a853 | ||
|
|
61ac156d47 | ||
|
|
8dbc93a94c | ||
|
|
8db9206c54 | ||
|
|
503dada42f | ||
|
|
382bafcb13 | ||
|
|
ce153bdb42 | ||
|
|
2f5c7af914 | ||
|
|
611594e7e8 | ||
|
|
1f1117d0dd | ||
|
|
d797f7def9 | ||
|
|
44644e69f0 | ||
|
|
8e96e04999 | ||
|
|
9db1b7a001 | ||
|
|
050b17ca9c | ||
|
|
a77cbc504a | ||
|
|
798188cb33 | ||
|
|
d04aa5ef4e | ||
|
|
e6cdfdfa74 | ||
|
|
bbaedfd0c5 | ||
|
|
607ba5f235 | ||
|
|
1aa943d8ab | ||
|
|
b735c0e207 | ||
|
|
fbb5da0010 | ||
|
|
177f5d9e9c | ||
|
|
a9bd4535d2 | ||
|
|
8c02c92ab9 | ||
|
|
4ebac1e936 | ||
|
|
e13cc25a2c | ||
|
|
0467f5287d | ||
|
|
9a4120f21f | ||
|
|
e3e03d0f34 | ||
|
|
f4142ab9ad | ||
|
|
837b865c7f | ||
|
|
f5ff2656e0 | ||
|
|
49431626cb | ||
|
|
160497b8ff |
No files matched your search
@@ -0,0 +1,23 @@
|
||||
### mpv version and platform
|
||||
|
||||
If you're not using git master or the latest release, update.
|
||||
|
||||
### Reproduction steps
|
||||
|
||||
### Expected behavior
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log files
|
||||
|
||||
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.
|
||||
|
||||
Providing a log file is strongly encouraged. It is very helpful for reproducing
|
||||
bugs or getting important technical context. Without log it might not be
|
||||
possible to analyze and fix certain bugs.
|
||||
|
||||
### Sample files
|
||||
|
||||
Sample files needed to reproduce this issue can be uploaded to https://0x0.st/
|
||||
or similar sites. (Only needed if the issue cannot be reproduced without it.)
|
||||
@@ -0,0 +1,7 @@
|
||||
Ideally, you shouldn't need enter any text here, and your commit messages should
|
||||
explain your changes sufficiently (especially why they are needed). Read
|
||||
https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md for coding
|
||||
style and development conventions. Remove this text block, but if you haven't
|
||||
agreed to it before, leave the following sentence in place:
|
||||
|
||||
I agree that my changes can be relicensed to LGPL 2.1 or later.
|
||||
@@ -15,7 +15,6 @@
|
||||
/tags
|
||||
/TAGS
|
||||
/video/out/x11_icon.inc
|
||||
/video/out/opengl/nnedi3_weights.inc
|
||||
/demux/ebml_defs.c
|
||||
/demux/ebml_types.h
|
||||
/sub/osd_font.h
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ matrix:
|
||||
- os: linux
|
||||
compiler: clang
|
||||
|
||||
before_install: ./travis-deps libass-stable $LIBAV
|
||||
before_install: TOOLS/travis-deps libass-stable $LIBAV
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- ./waf configure
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
Reporting bugs
|
||||
==============
|
||||
|
||||
- State your platform and mpv version.
|
||||
- Make sure you're actually using the latest mpv release. Linux distributions
|
||||
in particular love redistributing ancient and unmaintained mpv releases.
|
||||
- Attach a log file made with ``-v`` or ``--log-file=output.txt``.
|
||||
- Avoid attaching text files packed in archive files (zip/rar/...), unless
|
||||
explicitly asked so. There are better means, such as using http://sprunge.us
|
||||
- If you think this is file specific, upload a sample file and link it.
|
||||
- Don't report multiple unrelated/barely related bugs in one issue.
|
||||
- If you don't get a reply after days, be sure to post a new message to the
|
||||
issue - it could have been overlooked or forgotten.
|
||||
- Read: https://mpv.io/bug-reports/
|
||||
|
||||
Asking questions
|
||||
================
|
||||
|
||||
Preferably, asking questions should be done via IRC (#mpv on irc.freenode.net),
|
||||
but asking questions by opening a github issue is also barely tolerated.
|
||||
|
||||
When asking on IRC (#mpv on irc.freenode.net), don't ask to ask, just state
|
||||
your problem. Stay long enough until someone gets a chance to reply. Sometimes
|
||||
it can take hours.
|
||||
|
||||
Sending patches
|
||||
===============
|
||||
|
||||
See: https://github.com/mpv-player/mpv/blob/master/DOCS/contribute.md
|
||||
|
||||
In particular, be aware that all changes to GPL code must come with the
|
||||
implicit agreement that they might be LGPLv2.1+ relicensed at a later point.
|
||||
@@ -12,9 +12,6 @@ the code to LGPLv2.1+ at a later point without asking the contributor. (This
|
||||
is a safeguard for making potential relicensing of the project to LGPLv2.1+
|
||||
easier.) Using a more liberal license compatible to LGPLv2.1+ is also ok.
|
||||
|
||||
Some GPLv3 or LGPLv3 code in mpv can be enabled by building with --enable-gpl3,
|
||||
and in this case, the resulting binaries must be distributed under GPLv3.
|
||||
|
||||
For information about authors and contributors, consult the git log, which
|
||||
contains the complete SVN and CVS history as well.
|
||||
|
||||
|
||||
@@ -32,6 +32,32 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.20.0
|
||||
--- mpv 0.19.0
|
||||
1.22 - add stream_cb API for custom protocols
|
||||
--- mpv 0.18.1 ---
|
||||
---- - remove "status" log level from mpv_request_log_messages() docs. This
|
||||
is 100% equivalent to "v". The behavior is still the same, thus no
|
||||
actual API change.
|
||||
--- mpv 0.18.0 ---
|
||||
1.21 - mpv_set_property() changes behavior with MPV_FORMAT_NODE. Before this
|
||||
change it rejected mpv_nodes with format==MPV_FORMAT_STRING if the
|
||||
property was not a string or did not have special mechanisms in place
|
||||
the function failed. Now it always invokes the option string parser,
|
||||
and mpv_node with a basic data type works exactly as if the function
|
||||
is invoked with that type directly. This new behavior is equivalent
|
||||
to mpv_set_option().
|
||||
This also affects the mp.set_property_native() Lua function.
|
||||
- generally, setting choice options/properties with "yes"/"no" options
|
||||
can now be set as MPV_FORMAT_FLAG
|
||||
- reading a choice property as MPV_FORMAT_NODE will now return a
|
||||
MPV_FORMAT_FLAG value if the choice is "yes" (true) or "no" (false)
|
||||
This implicitly affects Lua and JSON IPC interfaces as well.
|
||||
- big changes to vo-cmdline on vo_opengl and vo_opengl_hq (but not
|
||||
vo_opengl_cb): options are now normally not reset, but applied on top
|
||||
of the current options. The special undocumented value "-" still
|
||||
works, but now resets all options to before any vo-cmdline command
|
||||
has been called.
|
||||
--- mpv 0.12.0 ---
|
||||
1.20 - deprecate "GL_MP_D3D_interfaces"/"glMPGetD3DInterface", and introduce
|
||||
"GL_MP_MPGetNativeDisplay"/"glMPGetNativeDisplay" (this is a
|
||||
|
||||
@@ -173,6 +173,24 @@ sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc
|
||||
rmdir /mingw64/bin/pkgconfig
|
||||
```
|
||||
|
||||
Linking libmpv with MSVC programs
|
||||
---------------------------------
|
||||
|
||||
You can build C++ programs in Visual Studio and link them with libmpv. To do
|
||||
this, you need a Visual Studio which supports ``stdint.h`` (recent ones do),
|
||||
and you need to create a import library for the mpv DLL:
|
||||
|
||||
```bash
|
||||
lib /def:mpv.def /name:mpv-1.dll /out:mpv.lib /MACHINE:X64
|
||||
```
|
||||
|
||||
The string in the ``/name:`` parameter must match the filename of the DLL (this
|
||||
is simply the filename the MSVC linker will use). The ``mpv.def`` can be
|
||||
retrieved from the mpv build directory, or can be produced by MingGW's
|
||||
gendef.exe helper from the mpv DLL.
|
||||
|
||||
Static linking is not possible.
|
||||
|
||||
Running mpv
|
||||
-----------
|
||||
|
||||
|
||||
@@ -19,6 +19,47 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.20.0 ---
|
||||
- add --image-display-duration option - this also means that image duration
|
||||
is not influenced by --mf-fps anymore in the general case (this is an
|
||||
incompatible change)
|
||||
--- mpv 0.19.0 ---
|
||||
- deprecate "balance" option/property (no replacement)
|
||||
--- mpv 0.18.1 ---
|
||||
- deprecate --heartbeat-cmd
|
||||
- remove --softvol=no capability:
|
||||
- deprecate --softvol, it now does nothing
|
||||
- --volume, --mute, and the corresponding properties now always control
|
||||
softvol, and behave as expected without surprises (e.g. you can set
|
||||
them normally while no audio is initialized)
|
||||
- rename --softvol-max to --volume-max (deprecated alias is added)
|
||||
- the --volume-restore-data option and property are removed without
|
||||
replacement. They were _always_ internal, and used for watch-later
|
||||
resume/restore. Now --volume/--mute are saved directly instead.
|
||||
- the previous point means resuming files with older watch-later configs
|
||||
will print an error about missing --volume-restore-data (which you can
|
||||
ignore), and will not restore the previous value
|
||||
- as a consequence, volume controls will no longer control PulseAudio
|
||||
per-application value, or use the system mixer's per-application
|
||||
volume processing
|
||||
- system or per-application volume can still be controlled with the
|
||||
ao-volume and ao-mute properties (there are no command line options)
|
||||
--- mpv 0.18.0 ---
|
||||
- now ab-loops are active even if one of the "ab-loop-a"/"-b" properties is
|
||||
unset ("no"), in which case the start of the file is used if the A loop
|
||||
point is unset, and the end of the file for an unset B loop point
|
||||
- deprecate --sub-ass=no option by --ass-style-override=strip
|
||||
(also needs --embeddedfonts=no)
|
||||
- add "hwdec-interop" and "hwdec-current" properties
|
||||
- deprecated "hwdec-active" and "hwdec-detected" properties (to be removed
|
||||
in mpv 0.20.0)
|
||||
- choice option/property values that are "yes" or "no" will now be returned
|
||||
as booleans when using the mpv_node functions in the client API, the
|
||||
"native" property accessors in Lua, and the JSON API. They can be set as
|
||||
such as well.
|
||||
- the VO opengl fbo-format sub-option does not accept "rgb" or "rgba"
|
||||
anymore
|
||||
- all VO opengl prescalers have been removed (replaced by user scripts)
|
||||
--- mpv 0.17.0 ---
|
||||
- deprecate "track-list/N/audio-channels" property (use
|
||||
"track-list/N/demux-channel-count" instead)
|
||||
|
||||
+5
-29
@@ -70,7 +70,7 @@ Available filters are:
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
|
||||
``lavcac3enc[=tospdif[:bitrate[:minch]]]``
|
||||
``lavcac3enc[=options]``
|
||||
Encode multi-channel audio to AC-3 at runtime using libavcodec. Supports
|
||||
16-bit native-endian input format, maximum 6 channels. The output is
|
||||
big-endian when outputting a raw AC-3 stream, native-endian when
|
||||
@@ -103,6 +103,10 @@ Available filters are:
|
||||
If the input channel number is less than ``<minch>``, the filter will
|
||||
detach itself (default: 3).
|
||||
|
||||
``encoder=<name>``
|
||||
Select the libavcodec encoder used. Currently, this should be an AC-3
|
||||
encoder, and using another codec will fail horribly.
|
||||
|
||||
``equalizer=g1:g2:g3:...:g10``
|
||||
10 octave band graphic equalizer, implemented using 10 IIR band-pass
|
||||
filters. This means that it works regardless of what type of audio is
|
||||
@@ -308,34 +312,6 @@ Available filters are:
|
||||
``mpv '--af=format=channels=5.1' '--audio-channels=5.1'`` would always force
|
||||
remixing audio to 5.1 and output it like this.
|
||||
|
||||
``delay[=[ch1,ch2,...]]``
|
||||
Delays the sound to the loudspeakers such that the sound from the
|
||||
different channels arrives at the listening position simultaneously. It is
|
||||
only useful if you have more than 2 loudspeakers.
|
||||
|
||||
``[ch1,ch2,...]``
|
||||
The delay in ms that should be imposed on each channel (floating point
|
||||
number between 0 and 1000).
|
||||
|
||||
To calculate the required delay for the different channels, do as follows:
|
||||
|
||||
1. Measure the distance to the loudspeakers in meters in relation to your
|
||||
listening position, giving you the distances s1 to s5 (for a 5.1
|
||||
system). There is no point in compensating for the subwoofer (you will
|
||||
not hear the difference anyway).
|
||||
|
||||
2. Subtract the distances s1 to s5 from the maximum distance, i.e.
|
||||
``s[i] = max(s) - s[i]; i = 1...5``.
|
||||
|
||||
3. Calculate the required delays in ms as ``d[i] = 1000*s[i]/342; i =
|
||||
1...5``.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=delay=[10.5,10.5,0,0,7,0] media.avi``
|
||||
Would delay front left and right by 10.5 ms, the two rear channels
|
||||
and the subwoofer by 0 ms and the center channel by 7 ms.
|
||||
|
||||
``drc[=method:target]``
|
||||
Applies dynamic range compression. This maximizes the volume by compressing
|
||||
the audio signal's dynamic range. (Formerly called ``volnorm``.)
|
||||
|
||||
+148
-52
@@ -459,6 +459,8 @@ Input Commands that are Possibly Subject to Change
|
||||
Remove all filters. Note that like the other sub-commands, this does
|
||||
not control automatically inserted filters.
|
||||
|
||||
The argument is always needed. E.g. in case of ``clr`` use ``vf clr ""``.
|
||||
|
||||
You can assign labels to filter by prefixing them with ``@name:`` (where
|
||||
``name`` is a user-chosen arbitrary identifier). Labels can be used to
|
||||
refer to filters by name in all of the filter chain modification commands.
|
||||
@@ -524,7 +526,7 @@ Input Commands that are Possibly Subject to Change
|
||||
``disable-section "<section>"``
|
||||
Disable the named input section. Undoes ``enable-section``.
|
||||
|
||||
``define-section "<section>" "<contents>" [default|forced]``
|
||||
``define-section "<section>" "<contents>" [default|force]``
|
||||
Create a named input section, or replace the contents of an already existing
|
||||
input section. The ``contents`` parameter uses the same syntax as the
|
||||
``input.conf`` file (except that using the section syntax in it is not
|
||||
@@ -542,7 +544,7 @@ Input Commands that are Possibly Subject to Change
|
||||
<default> (also used if parameter omitted)
|
||||
Use a key binding defined by this section only if the user hasn't
|
||||
already bound this key to a command.
|
||||
<forced>
|
||||
<force>
|
||||
Always bind a key. (The input section that was made active most recently
|
||||
wins if there are ambiguities.)
|
||||
|
||||
@@ -561,29 +563,20 @@ Input Commands that are Possibly Subject to Change
|
||||
the resolution is reduced to that of the video's. You can read the
|
||||
``osd-width`` and ``osd-height`` properties. At least with ``--vo-xv`` and
|
||||
anamorphic video (such as DVD), ``osd-par`` should be read as well, and the
|
||||
overlay should be aspect-compensated. (Future directions: maybe mpv should
|
||||
take care of some of these things automatically, but it's hard to tell
|
||||
where to draw the line.)
|
||||
overlay should be aspect-compensated.
|
||||
|
||||
``id`` is an integer between 0 and 63 identifying the overlay element. The
|
||||
ID can be used to add multiple overlay parts, update a part by using this
|
||||
command with an already existing ID, or to remove a part with
|
||||
``overlay-remove``. Using a previously unused ID will add a new overlay,
|
||||
while reusing an ID will update it. (Future directions: there should be
|
||||
something to ensure different programs wanting to create overlays don't
|
||||
conflict with each others, should that ever be needed.)
|
||||
while reusing an ID will update it.
|
||||
|
||||
``x`` and ``y`` specify the position where the OSD should be displayed.
|
||||
|
||||
``file`` specifies the file the raw image data is read from. It can be
|
||||
either a numeric UNIX file descriptor prefixed with ``@`` (e.g. ``@4``),
|
||||
or a filename. The file will be mapped into memory with ``mmap()``. Some VOs
|
||||
will pass the mapped pointer directly to display APIs (e.g. opengl or
|
||||
vdpau), so no actual copying is involved. Truncating the source file while
|
||||
the overlay is active will crash the player. You shouldn't change the data
|
||||
while the overlay is active, because the data is essentially accessed at
|
||||
random points. Instead, call ``overlay-add`` again (preferably with a
|
||||
different memory region to prevent tearing).
|
||||
or a filename. The file will be mapped into memory with ``mmap()``,
|
||||
copied, and unmapped before the command returns (changed in mpv 0.18.1).
|
||||
|
||||
It is also possible to pass a raw memory address for use as bitmap memory
|
||||
by passing a memory address as integer prefixed with an ``&`` character.
|
||||
@@ -616,15 +609,14 @@ Input Commands that are Possibly Subject to Change
|
||||
(Technically, the minimum size would be ``stride * (h - 1) + w * 4``, but
|
||||
for simplicity, the player will access all ``stride * h`` bytes.)
|
||||
|
||||
.. admonition:: Warning
|
||||
.. note::
|
||||
|
||||
When updating the overlay, you should prepare a second shared memory
|
||||
region (e.g. make use of the offset parameter) and add this as overlay,
|
||||
instead of reusing the same memory every time. Otherwise, you might
|
||||
get the equivalent of tearing, when your application and mpv write/read
|
||||
the buffer at the same time. Also, keep in mind that mpv might access
|
||||
an overlay's memory at random times whenever it feels the need to do
|
||||
so, for example when redrawing the screen.
|
||||
Before mpv 0.18.1, you had to do manual "double buffering" when updating
|
||||
an overlay by replacing it with a different memory buffer. Since mpv
|
||||
0.18.1, the memory is simply copied and doesn't reference any of the
|
||||
memory indicated by the command's arguments after the commend returns.
|
||||
If you want to use this command before mpv 0.18.1, reads the old docs
|
||||
to see how to handle this correctly.
|
||||
|
||||
``overlay-remove <id>``
|
||||
Remove an overlay added with ``overlay-add`` and the same ID. Does nothing
|
||||
@@ -634,10 +626,10 @@ Input Commands that are Possibly Subject to Change
|
||||
Send a message to all clients, and pass it the following list of arguments.
|
||||
What this message means, how many arguments it takes, and what the arguments
|
||||
mean is fully up to the receiver and the sender. Every client receives the
|
||||
message, so be careful about name clashes (or use ``script_message_to``).
|
||||
message, so be careful about name clashes (or use ``script-message-to``).
|
||||
|
||||
``script-message-to "<target>" "<arg1>" "<arg2>" ...``
|
||||
Same as ``script_message``, but send it only to the client named
|
||||
Same as ``script-message``, but send it only to the client named
|
||||
``<target>``. Each client (scripts etc.) has a unique name. For example,
|
||||
Lua scripts can get their name via ``mp.get_script_name()``.
|
||||
|
||||
@@ -648,11 +640,11 @@ Input Commands that are Possibly Subject to Change
|
||||
The argument is the name of the binding.
|
||||
|
||||
It can optionally be prefixed with the name of the script, using ``/`` as
|
||||
separator, e.g. ``script_binding scriptname/bindingname``.
|
||||
separator, e.g. ``script-binding scriptname/bindingname``.
|
||||
|
||||
For completeness, here is how this command works internally. The details
|
||||
could change any time. On any matching key event, ``script_message_to``
|
||||
or ``script_message`` is called (depending on whether the script name is
|
||||
could change any time. On any matching key event, ``script-message-to``
|
||||
or ``script-message`` is called (depending on whether the script name is
|
||||
included), with the following arguments:
|
||||
|
||||
1. The string ``key-binding``.
|
||||
@@ -817,7 +809,7 @@ Input sections group a set of bindings, and enable or disable them at once.
|
||||
In ``input.conf``, each key binding is assigned to an input section, rather
|
||||
than actually having explicit text sections.
|
||||
|
||||
See also: ``enable_section`` and ``disable_section`` commands.
|
||||
See also: ``enable-section`` and ``disable-section`` commands.
|
||||
|
||||
Predefined bindings:
|
||||
|
||||
@@ -877,6 +869,12 @@ Property list
|
||||
looks better for display purposes. Use the ``path`` property to get an
|
||||
unmodified filename.)
|
||||
|
||||
This has a sub-property:
|
||||
|
||||
``filename/no-ext``
|
||||
Like the ``filename`` property, but if the text contains a ``.``, strip
|
||||
all text after the last ``.``. Usually this removes the file extension.
|
||||
|
||||
``file-size``
|
||||
Length in bytes of the source file/stream. (This is the same as
|
||||
``${stream-end}``. For ordered chapters and such, the
|
||||
@@ -1081,8 +1079,8 @@ Property list
|
||||
"default" MPV_FORMAT_FLAG
|
||||
|
||||
``ab-loop-a``, ``ab-loop-b`` (RW)
|
||||
Set/get A-B loop points. See corresponding options and ``ab_loop`` command.
|
||||
The special value ``no`` on either of these properties disables looping.
|
||||
Set/get A-B loop points. See corresponding options and ``ab-loop`` command
|
||||
for details.
|
||||
|
||||
``angle`` (RW)
|
||||
Current DVD angle.
|
||||
@@ -1242,29 +1240,31 @@ Property list
|
||||
See ``--hr-seek``.
|
||||
|
||||
``mixer-active``
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise. This has
|
||||
implications for ``--softvol=no`` mode: if the mixer is inactive, changing
|
||||
``volume`` doesn't actually change anything on the system mixer. If the
|
||||
``--volume`` or ``--mute`` option are used, these might not be applied
|
||||
properly until the mixer becomes active either. (The options, if set, will
|
||||
just overwrite the mixer state at audio initialization.)
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise.
|
||||
|
||||
While the behavior with ``mixer-active==yes`` is relatively well-defined,
|
||||
the ``no`` case will provide possibly wrong or insignificant values.
|
||||
|
||||
Note that an active mixer does not necessarily imply active audio output,
|
||||
although this is implied in the current implementation.
|
||||
This option is relatively useless. Before mpv 0.18.1, it could be used to
|
||||
infer behavior of the ``volume`` property.
|
||||
|
||||
``volume`` (RW)
|
||||
Current volume (see ``--volume`` for details). Also see ``mixer-active``
|
||||
property.
|
||||
Current volume (see ``--volume`` for details).
|
||||
|
||||
``volume-max``
|
||||
Current maximum value the volume property can be set to. (This may depend
|
||||
on the ``--softvol-max`` option.)
|
||||
``volume-max`` (RW)
|
||||
Current maximum value the volume property can be set to. (Equivalent to the
|
||||
``--volume-max`` option.)
|
||||
|
||||
``mute`` (RW)
|
||||
Current mute status (``yes``/``no``). Also see ``mixer-active`` property.
|
||||
Current mute status (``yes``/``no``).
|
||||
|
||||
``ao-volume`` (RW)
|
||||
System volume. This property is available only if mpv audio output is
|
||||
currently active, and only if the underlying implementation supports volume
|
||||
control. What this option does depends on the API. For example, on ALSA
|
||||
this usually changes system-wide audio, while with PulseAudio this controls
|
||||
per-application volume.
|
||||
|
||||
``ao-mute`` (RW)
|
||||
Similar to ``ao-volume``, but controls the mute state. May be unimplemented
|
||||
even if ``ao-volume`` works.
|
||||
|
||||
``audio-delay`` (RW)
|
||||
See ``--audio-delay``.
|
||||
@@ -1327,6 +1327,8 @@ Property list
|
||||
It doesn't change the volumes of each channel, but instead sets up a pan
|
||||
matrix to mix the left and right channels.)
|
||||
|
||||
Deprecated.
|
||||
|
||||
``fullscreen`` (RW)
|
||||
See ``--fullscreen``.
|
||||
|
||||
@@ -1349,6 +1351,9 @@ Property list
|
||||
``colormatrix-primaries`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``taskbar-progress`` (RW)
|
||||
See ``--taskbar-progress``.
|
||||
|
||||
``ontop`` (RW)
|
||||
See ``--ontop``.
|
||||
|
||||
@@ -1385,14 +1390,39 @@ Property list
|
||||
properties to see whether this was successful.
|
||||
|
||||
Unlike in mpv 0.9.x and before, this does not return the currently active
|
||||
hardware decoder.
|
||||
hardware decoder. Since mpv 0.18.0, ``hwdec-current`` is available for
|
||||
this purpose.
|
||||
|
||||
``hwdec-current``
|
||||
Return the current hardware decoding in use. If decoding is active, return
|
||||
one of the values used by the ``hwdec`` option/property. ``no`` indicates
|
||||
software decoding. If no decoder is loaded, the property is unavailable.
|
||||
|
||||
``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
|
||||
the decoder attempted to create the hw decoder successfully. (Using
|
||||
``--hwdec-preload`` can load it eagerly.) If there are multiple drivers
|
||||
loaded, they will be separated by ``,``.
|
||||
|
||||
If no VO is active or no interop driver is known, this property is
|
||||
unavailable.
|
||||
|
||||
This does not necessarily use the same values as ``hwdec``. There can be
|
||||
multiple interop drivers for the same hardware decoder, depending on
|
||||
platform and VO.
|
||||
|
||||
``hwdec-active``
|
||||
Deprecated. To be removed in mpv 0.20.0. Use ``hwdec-current`` instead.
|
||||
|
||||
Return ``yes`` or ``no``, depending on whether any type of hardware decoding
|
||||
is actually in use.
|
||||
|
||||
``hwdec-detected``
|
||||
If software decoding is active, this returns the hardware decoder in use.
|
||||
Deprecated. To be removed in mpv 0.20.0.
|
||||
|
||||
If hardware decoding is active, this returns the hardware decoder in use.
|
||||
Otherwise, it returns either ``no``, or if applicable, the currently loaded
|
||||
hardware decoding API. This is known only once the VO has opened (and
|
||||
possibly later). With some VOs (like ``opengl``), this is never known in
|
||||
@@ -1457,6 +1487,12 @@ Property list
|
||||
``video-params/gamma``
|
||||
The gamma function in use as string. (Exact values subject to change.)
|
||||
|
||||
``video-params/nom-peak``
|
||||
The video encoding's nominal peak brightness as float.
|
||||
|
||||
``video-params/sig-peak``
|
||||
The video file's tagged signal peak as float.
|
||||
|
||||
``video-params/chroma-location``
|
||||
Chroma location as string. (Exact values subject to change.)
|
||||
|
||||
@@ -1483,6 +1519,9 @@ Property list
|
||||
"colormatrix" MPV_FORMAT_STRING
|
||||
"colorlevels" MPV_FORMAT_STRING
|
||||
"primaries" MPV_FORMAT_STRING
|
||||
"gamma" MPV_FORMAT_STRING
|
||||
"nom-peak" MPV_FORMAT_DOUBLE
|
||||
"sig-peak" MPV_FORMAT_DOUBLE
|
||||
"chroma-location" MPV_FORMAT_STRING
|
||||
"rotate" MPV_FORMAT_INT64
|
||||
"stereo-in" MPV_FORMAT_STRING
|
||||
@@ -1563,7 +1602,7 @@ Property list
|
||||
|
||||
``osd-width``, ``osd-height``
|
||||
Last known OSD width (can be 0). This is needed if you want to use the
|
||||
``overlay_add`` command. It gives you the actual OSD size, which can be
|
||||
``overlay-add`` command. It gives you the actual OSD size, which can be
|
||||
different from the window size in some cases.
|
||||
|
||||
``osd-par``
|
||||
@@ -1646,6 +1685,9 @@ Property list
|
||||
Current position on playlist. The first entry is on position 0. Writing
|
||||
to the property will restart playback at the written entry.
|
||||
|
||||
``playlist-pos-1`` (RW)
|
||||
Same as ``playlist-pos``, but 1-based.
|
||||
|
||||
``playlist-count``
|
||||
Number of total playlist entries.
|
||||
|
||||
@@ -1771,6 +1813,16 @@ Property list
|
||||
``track-list/N/audio-channels`` (deprecated)
|
||||
Deprecated alias for ``track-list/N/demux-channel-count``.
|
||||
|
||||
``track-list/N/replaygain-track-peak``, ``track-list/N/replaygain-track-gain``
|
||||
Per-track replaygain values. Only available for audio tracks with
|
||||
corresponding information stored in the source file.
|
||||
|
||||
``track-list/N/replaygain-album-peak``, ``track-list/N/replaygain-album-gain``
|
||||
Per-album replaygain values. If the file has per-track but no per-album
|
||||
information, the per-album values will be copied from the per-track
|
||||
values currently. It's possible that future mpv versions will make
|
||||
these properties unavailable instead in this case.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
@@ -1787,9 +1839,11 @@ Property list
|
||||
"albumart" MPV_FORMAT_FLAG
|
||||
"default" MPV_FORMAT_FLAG
|
||||
"forced" MPV_FORMAT_FLAG
|
||||
"selected" MPV_FORMAT_FLAG
|
||||
"external" MPV_FORMAT_FLAG
|
||||
"external-filename" MPV_FORMAT_STRING
|
||||
"codec" MPV_FORMAT_STRING
|
||||
"ff-index" MPV_FORMAT_INT64
|
||||
"decoder-desc" MPV_FORMAT_STRING
|
||||
"demux-w" MPV_FORMAT_INT64
|
||||
"demux-h" MPV_FORMAT_INT64
|
||||
@@ -1798,6 +1852,10 @@ Property list
|
||||
"demux-samplerate" MPV_FORMAT_INT64
|
||||
"demux-fps" MPV_FORMAT_DOUBLE
|
||||
"audio-channels" MPV_FORMAT_INT64
|
||||
"replaygain-track-peak" MPV_FORMAT_DOUBLE
|
||||
"replaygain-track-gain" MPV_FORMAT_DOUBLE
|
||||
"replaygain-album-peak" MPV_FORMAT_DOUBLE
|
||||
"replaygain-album-gain" MPV_FORMAT_DOUBLE
|
||||
|
||||
``chapter-list``
|
||||
List of chapters, current entry marked. Currently, the raw property value
|
||||
@@ -1904,6 +1962,44 @@ Property list
|
||||
whether the video window is visible. If the ``--force-window`` option is
|
||||
used, this is usually always returns ``yes``.
|
||||
|
||||
``vo-performance``
|
||||
Some video output performance metrics. Not implemented by all VOs. This has
|
||||
a number of sup-properties, of the form ``vo-performance/<metric>-<value>``,
|
||||
all of them in milliseconds.
|
||||
|
||||
``<metric>`` refers to one of:
|
||||
|
||||
``upload``
|
||||
Time needed to make the frame available to the GPU (if necessary).
|
||||
``render``
|
||||
Time needed to perform all necessary video postprocessing and rendering
|
||||
passes (if necessary).
|
||||
``present``
|
||||
Time needed to present a rendered frame on-screen.
|
||||
|
||||
When a step is unnecessary or skipped, it will have the value 0.
|
||||
|
||||
``<value>`` refers to one of:
|
||||
|
||||
``last``
|
||||
Last measured value.
|
||||
``avg``
|
||||
Average over a fixed number of past samples. (The exact timeframe
|
||||
varies, but it should generally be a handful of seconds)
|
||||
``peak``
|
||||
The peak (highest value) within this averaging range.
|
||||
|
||||
When querying the property with the client API using ``MPV_FORMAT_NODE``,
|
||||
or with Lua ``mp.get_property_native``, this will return a mpv_node with
|
||||
the following contents:
|
||||
|
||||
::
|
||||
|
||||
MPV_FORMAT_NODE_MAP
|
||||
"<metric>-<value>" MPV_FORMAT_INT64
|
||||
|
||||
(One entry for each ``<metric>`` and ``<value>`` combination)
|
||||
|
||||
``video-bitrate``, ``audio-bitrate``, ``sub-bitrate``
|
||||
Bitrate values calculated on the packet level. This works by dividing the
|
||||
bit size of all packets between two keyframes by their presentation
|
||||
@@ -2063,7 +2159,7 @@ Property list
|
||||
|
||||
(Note that if an option is marked as file-local, even ``options/`` will
|
||||
access the local value, and the ``old`` value, which will be restored on
|
||||
end of playback, can not be read or written until end of playback.)
|
||||
end of playback, cannot be read or written until end of playback.)
|
||||
|
||||
``option-info/<name>``
|
||||
Additional per-option information.
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ It's also possible to send input.conf style text-only commands:
|
||||
|
||||
::
|
||||
|
||||
> echo 'show_text ${playback-time}' | socat - /tmp/mpvsocket
|
||||
> echo 'show-text ${playback-time}' | socat - /tmp/mpvsocket
|
||||
|
||||
But you won't get a reply over the socket. (This particular command shows the
|
||||
playback time on the player's OSD.)
|
||||
@@ -65,7 +65,7 @@ You can send commands from a command prompt:
|
||||
|
||||
::
|
||||
|
||||
echo show_text ${playback-time} >\\.\pipe\mpvsocket
|
||||
echo show-text ${playback-time} >\\.\pipe\mpvsocket
|
||||
|
||||
To be able to simultaneously read and write from the IPC pipe, like on Linux,
|
||||
it's necessary to write an external program that uses overlapped file I/O (or
|
||||
|
||||
+10
-6
@@ -175,7 +175,7 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
Similar to ``mp.set_property``, but set the given property using its native
|
||||
type.
|
||||
|
||||
Since there are several data types which can not represented natively in
|
||||
Since there are several data types which cannot represented natively in
|
||||
Lua, this might not always work as expected. For example, while the Lua
|
||||
wrapper can do some guesswork to decide whether a Lua table is an array
|
||||
or a map, this would fail with empty tables. Also, there are not many
|
||||
@@ -350,6 +350,10 @@ The ``mp`` module is preloaded, although it can be loaded manually with
|
||||
with ``add_timeout()``), this starts the timer from the beginning,
|
||||
using the initially configured timeout.
|
||||
|
||||
``is_enabled()``
|
||||
Whether the timer is currently enabled or was previously disabled
|
||||
(e.g. by ``stop()`` or ``kill()``).
|
||||
|
||||
``timeout`` (RW)
|
||||
This field contains the current timeout period. This value is not
|
||||
updated as time progresses. It's only used to calculate when the
|
||||
@@ -454,9 +458,9 @@ are useful only in special situations.
|
||||
The level is a string, see ``msg.log`` for allowed log levels.
|
||||
|
||||
``mp.register_script_message(name, fn)``
|
||||
This is a helper to dispatch ``script_message`` or ``script_message_to``
|
||||
invocations to Lua functions. ``fn`` is called if ``script_message`` or
|
||||
``script_message_to`` (with this script as destination) is run
|
||||
This is a helper to dispatch ``script-message`` or ``script-message-to``
|
||||
invocations to Lua functions. ``fn`` is called if ``script-message`` or
|
||||
``script-message-to`` (with this script as destination) is run
|
||||
with ``name`` as first parameter. The other parameters are passed to ``fn``.
|
||||
If a message with the given name is already registered, it's overwritten.
|
||||
|
||||
@@ -537,8 +541,8 @@ Example command-line::
|
||||
--script-opts=myscript-optionA=TEST,myscript-optionB=0,myscript-optionC=yes
|
||||
|
||||
|
||||
mp.utils options
|
||||
----------------
|
||||
mp.utils functions
|
||||
------------------
|
||||
|
||||
This built-in module provides generic helper functions for Lua, and have
|
||||
strictly speaking nothing to do with mpv or video/audio playback. They are
|
||||
|
||||
+16
-2
@@ -320,11 +320,22 @@ example, paths starting with ``-`` will be interpreted as options. Likewise,
|
||||
if a path contains the sequence ``://``, the string before that might be
|
||||
interpreted as protocol prefix, even though ``://`` can be part of a legal
|
||||
UNIX path. To avoid problems with arbitrary paths, you should be sure that
|
||||
absolute paths passed to mpv start with ``/``, and relative paths with ``./``.
|
||||
absolute paths passed to mpv start with ``/``, and prefix relative paths with
|
||||
``./``.
|
||||
|
||||
Using the ``file://`` pseudo-protocol is discouraged, because it involves
|
||||
strange URL unescaping rules.
|
||||
|
||||
The name ``-`` itself is interpreted as stdin, and will cause mpv to disable
|
||||
console controls. (Which makes it suitable for playing data piped to stdin.)
|
||||
|
||||
The special argument ``--`` can be used to stop mpv from interpreting the
|
||||
following arguments as options.
|
||||
|
||||
When using the client API, you should strictly avoid using ``mpv_command_string``
|
||||
for invoking the ``loadfile`` command, and instead prefer e.g. ``mpv_command``
|
||||
to avoid the need for filename escaping.
|
||||
|
||||
For paths passed to suboptions, the situation is further complicated by the
|
||||
need to escape special characters. To work this around, the path can be
|
||||
additionally wrapped in the fixed-length syntax, e.g. ``%n%string_of_length_n``
|
||||
@@ -715,6 +726,9 @@ PROTOCOLS
|
||||
``memory://data``
|
||||
Use the ``data`` part as source data.
|
||||
|
||||
``hex://data``
|
||||
Like ``memory://``, but the string is interpreted as hexdump.
|
||||
|
||||
PSEUDO GUI MODE
|
||||
===============
|
||||
|
||||
@@ -932,7 +946,7 @@ For Windows-specifics, see `FILES ON WINDOWS`_ section.
|
||||
``~/.config/mpv/watch_later/``
|
||||
Contains temporary config files needed for resuming playback of files with
|
||||
the watch later feature. See for example the ``Q`` key binding, or the
|
||||
``quit_watch_later`` input command.
|
||||
``quit-watch-later`` input command.
|
||||
|
||||
Each file is a small config file which is loaded if the corresponding media
|
||||
file is loaded. It contains the playback position and some (not necessarily
|
||||
|
||||
+243
-77
@@ -196,7 +196,7 @@ Playback Control
|
||||
position and so can take some time depending on decoding performance. For
|
||||
some video formats, precise seeks are disabled. This option selects the
|
||||
default choice to use for seeks; it is possible to explicitly override that
|
||||
default in the definition of key bindings and in slave mode commands.
|
||||
default in the definition of key bindings and in input commands.
|
||||
|
||||
:no: Never use precise seeks.
|
||||
:absolute: Use precise seeks if the seek is to an absolute position in the
|
||||
@@ -261,8 +261,13 @@ Playback Control
|
||||
``--ab-loop-a=<time>``, ``--ab-loop-b=<time>``
|
||||
Set loop points. If playback passes the ``b`` timestamp, it will seek to
|
||||
the ``a`` timestamp. Seeking past the ``b`` point doesn't loop (this is
|
||||
intentional). The loop-points can be adjusted at runtime with the
|
||||
corresponding properties. See also ``ab_loop`` command.
|
||||
intentional).
|
||||
|
||||
If both options are set to ``no``, looping is disabled. Otherwise, the
|
||||
start/end of the file is used if one of the options is set to ``no``.
|
||||
|
||||
The loop-points can be adjusted at runtime with the corresponding
|
||||
properties. See also ``ab-loop`` command.
|
||||
|
||||
``--ordered-chapters``, ``--no-ordered-chapters``
|
||||
Enabled by default.
|
||||
@@ -369,7 +374,7 @@ Program Behavior
|
||||
|
||||
``--idle=<no|yes|once>``
|
||||
Makes mpv wait idly instead of quitting when there is no file to play.
|
||||
Mostly useful in slave mode, where mpv can be controlled through input
|
||||
Mostly useful in input mode, where mpv can be controlled through input
|
||||
commands.
|
||||
|
||||
``once`` will only idle at start and let the player close once the
|
||||
@@ -399,7 +404,7 @@ Program Behavior
|
||||
``--no-resume-playback``
|
||||
Do not restore playback position from the ``watch_later`` configuration
|
||||
subdirectory (usually ``~/.config/mpv/watch_later/``).
|
||||
See ``quit_watch_later`` input command.
|
||||
See ``quit-watch-later`` input command.
|
||||
|
||||
``--profile=<profile1,profile2,...>``
|
||||
Use the given profile(s), ``--profile=help`` displays a list of the
|
||||
@@ -576,14 +581,19 @@ Video
|
||||
|
||||
:no: always use software decoding (default)
|
||||
:auto: see below
|
||||
:auto-copy: see below
|
||||
:vdpau: requires ``--vo=vdpau`` or ``--vo=opengl`` (Linux only)
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:videotoolbox: requires ``--vo=opengl`` (OS X 10.8 and up only)
|
||||
:videotoolbox-copy: copies video back into system RAM (OS X 10.8 and up only)
|
||||
:dxva2: requires ``--vo=opengl:backend=angle`` or
|
||||
|
||||
``--vo=opengl:backend=dxinterop`` (Windows only)
|
||||
:dxva2-copy: copies video back to system RAM (Windows only)
|
||||
:d3d11va-copy: experimental (Windows only)
|
||||
:d3d11va: requires ``--vo=opengl:backend=angle`` (Windows only)
|
||||
:d3d11va-copy: copies video back to system RAM (Windows only)
|
||||
:mediacodec: copies video back to system RAM (Android only)
|
||||
:rpi: requires ``--vo=rpi`` (Raspberry Pi only - default if available)
|
||||
|
||||
``auto`` tries to automatically enable hardware decoding using the first
|
||||
@@ -593,6 +603,13 @@ Video
|
||||
work, it will always fall back to software decoding, instead of trying the
|
||||
next method (might matter on some Linux systems).
|
||||
|
||||
``auto-copy`` selects only modes that copy the video data back to system
|
||||
memory after decoding. Currently, this selects only one of the following
|
||||
modes: ``vaapi-copy``, ``dxva2-copy``, ``d3d11va-copy``, ``mediacodec``.
|
||||
If none of these work, hardware decoding is disabled. This mode is always
|
||||
guaranteed to incur no additional loss compared to software decoding, and
|
||||
will allow CPU processing with video filters.
|
||||
|
||||
The ``vaapi`` mode, if used with ``--vo=opengl``, requires Mesa 11 and most
|
||||
likely works with Intel GPUs only. It also requires the opengl EGL backend
|
||||
(automatically used if available). You can also try the old GLX backend by
|
||||
@@ -614,6 +631,46 @@ Video
|
||||
codecs. See ``--hwdec-codecs`` to enable hardware decoding for more
|
||||
codecs.
|
||||
|
||||
.. admonition:: Quality reduction with hardware decoding
|
||||
|
||||
Normally, hardware decoding does not reduce video quality (at least for
|
||||
the codecs h264 and HEVC). However, due to restrictions in video output
|
||||
APIs, there can be some loss, or blatantly incorrect results.
|
||||
|
||||
In some cases, RGB conversion is forced, which means the RGB conversion
|
||||
is performed by the hardware decoding API, instead of the OpenGL code
|
||||
used by ``--vo=opengl``. This means certain obscure colorspaces may
|
||||
not display correctly, not certain filtering (such as debanding)
|
||||
cannot be applied in an ideal way.
|
||||
|
||||
``vdpau`` is usually safe. If deinterlacing enabled (or the ``vdpaupp``
|
||||
video filter is active in general), it forces RGB conversion. The latter
|
||||
currently does not treat certain colorspaces like BT.2020 correctly
|
||||
(which is mostly a mpv-specific restriction). The ``vdpauprb`` video
|
||||
filter retrieves image data without RGB conversion and is safe (but
|
||||
precludes use of vdpau postprocessing).
|
||||
|
||||
``vaapi`` is safe if the ``vaapi-egl`` backend is indicated in the logs.
|
||||
If ``vaapi-glx`` is indicated, and the video colorspace is either BT.601
|
||||
or BT.709, a forced but correct RGB conversion is performed. Otherwise,
|
||||
the result will be incorrect.
|
||||
|
||||
``d3d11va`` is usually safe (if used with ANGLE builds that support
|
||||
``EGL_KHR_stream path`` - otherwise, it converts to RGB), except that
|
||||
10 bit input (HEVC main 10 profiles) will be rounded down to 8 bits.
|
||||
|
||||
``dxva2`` is not safe. It appears to always use BT.601 for forced RGB
|
||||
conversion, but actual behavior depends on the GPU drivers. Some drivers
|
||||
appear to convert to limited range RGB, which gives a faded appearance.
|
||||
In addition to driver-specific behavior, global system settings might
|
||||
affect this additionally. This can give incorrect results even with
|
||||
completely ordinary video sources.
|
||||
|
||||
All other methods, in particular the copy-back methods (like
|
||||
``dxva2-copy`` etc.) are either fully safe, or not worse than software
|
||||
decoding. In particular, ``auto-copy`` will only select safe modes
|
||||
(although potentially slower than other methods).
|
||||
|
||||
``--hwdec-preload=<api>``
|
||||
This is useful for the ``opengl`` and ``opengl-cb`` VOs for creating the
|
||||
hardware decoding OpenGL interop context, but without actually enabling
|
||||
@@ -635,8 +692,8 @@ Video
|
||||
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. ``rgb0`` also
|
||||
works.
|
||||
while ``uyvy422`` appears to be better for old hardware. ``rgb0`` and
|
||||
``yuv420p`` also work.
|
||||
|
||||
``--panscan=<0.0-1.0>``
|
||||
Enables pan-and-scan functionality (cropping the sides of e.g. a 16:9
|
||||
@@ -644,6 +701,8 @@ Video
|
||||
controls how much of the image is cropped. May not work with all video
|
||||
output drivers.
|
||||
|
||||
This option has no effect if ``--video-unscaled`` option is used.
|
||||
|
||||
``--video-aspect=<ratio>``
|
||||
Override video aspect ratio, in case aspect information is incorrect or
|
||||
missing in the file being played. See also ``--no-video-aspect``.
|
||||
@@ -681,10 +740,12 @@ Video
|
||||
choices if you encounter video that has the wrong aspect ratio in mpv,
|
||||
but seems to be correct in other players.
|
||||
|
||||
``--video-unscaled``
|
||||
``--video-unscaled=<no|yes|downscale-big>``
|
||||
Disable scaling of the video. If the window is larger than the video,
|
||||
black bars are added. Otherwise, the video is cropped. The video still
|
||||
can be influenced by the other ``--video-...`` options.
|
||||
black bars are added. Otherwise, the video is cropped, unless the option
|
||||
is set to ``downscale-big``, in which case the video is fit to window. The
|
||||
video still can be influenced by the other ``--video-...`` options. This
|
||||
option disables the effect of ``--panscan``.
|
||||
|
||||
Note that the scaler algorithm may still be used, even if the video isn't
|
||||
scaled. For example, this can influence chroma conversion. The video will
|
||||
@@ -828,9 +889,9 @@ Video
|
||||
You can get the list of allowed codecs with ``mpv --vd=help``. Remove the
|
||||
prefix, e.g. instead of ``lavc:h264`` use ``h264``.
|
||||
|
||||
By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video``. Note that the
|
||||
hardware acceleration special codecs like ``h264_vdpau`` are not relevant
|
||||
anymore, and in fact have been removed from Libav in this form.
|
||||
By default, this is set to ``h264,vc1,wmv3,hevc,mpeg2video,vp9``. Note that
|
||||
the hardware acceleration special codecs like ``h264_vdpau`` are not
|
||||
relevant anymore, and in fact have been removed from Libav in this form.
|
||||
|
||||
This is usually only needed with broken GPUs, where a codec is reported
|
||||
as supported, but decoding causes more problems than it solves.
|
||||
@@ -1017,14 +1078,15 @@ Audio
|
||||
|
||||
``--volume=<value>``
|
||||
Set the startup volume. 0 means silence, 100 means no volume reduction or
|
||||
amplification. A value of -1 (the default) will not change the volume. See
|
||||
also ``--softvol``.
|
||||
amplification. Negative values can be passed for compatibility, but are
|
||||
treated as 0.
|
||||
|
||||
.. note::
|
||||
Since mpv 0.18.1, this always controls the internal mixer (aka "softvol").
|
||||
|
||||
This was changed after the mpv 0.9 release. Before that, 100 actually
|
||||
meant maximum volume. At the same time, the volume scale was made cubic,
|
||||
so the old values won't match up with the new ones anyway.
|
||||
``--balance=<value>``
|
||||
How much left/right channels contribute to the audio.
|
||||
|
||||
Deprecated.
|
||||
|
||||
``--audio-delay=<sec>``
|
||||
Audio delay in seconds (positive or negative float value). Positive values
|
||||
@@ -1039,20 +1101,17 @@ Audio
|
||||
|
||||
See also: ``--volume``.
|
||||
|
||||
``--softvol=<mode>``
|
||||
Control whether to use the volume controls of the audio output driver or
|
||||
the internal mpv volume filter.
|
||||
``--softvol=<no|yes|auto>``
|
||||
Deprecated/unfunctional. Before mpv 0.18.1, this used to control whether
|
||||
to use the volume controls of the audio output driver or the internal mpv
|
||||
volume filter.
|
||||
|
||||
:no: prefer audio driver controls, use the volume filter only if
|
||||
absolutely needed
|
||||
:yes: always use the volume filter
|
||||
:auto: prefer the volume filter if the audio driver uses the system mixer
|
||||
(default)
|
||||
The current behavior is as if this option was set to ``yes``. The other
|
||||
behaviors are not available anymore, although ``auto`` almost matches
|
||||
current behavior in most cases.
|
||||
|
||||
The intention of ``auto`` is to avoid changing system mixer settings from
|
||||
within mpv with default settings. mpv is a video player, not a mixer panel.
|
||||
On the other hand, mixer controls are enabled for sound servers like
|
||||
PulseAudio, which provide per-application volume.
|
||||
The ``no`` behavior is still partially available through the ``ao-volume``
|
||||
and ``ao-mute`` properties. But there are no options to reset these.
|
||||
|
||||
``--audio-demuxer=<[+]name>``
|
||||
Use this audio demuxer type when using ``--audio-file``. Use a '+' before
|
||||
@@ -1095,30 +1154,51 @@ Audio
|
||||
This and enabling passthrough via ``--ad`` are deprecated in favor of
|
||||
using ``--audio-spdif=dts-hd``.
|
||||
|
||||
``--audio-channels=<auto|number|layout>``
|
||||
Request a channel layout for audio output (default: stereo). This will ask
|
||||
the AO to open a device with the given channel layout. It's up to the AO
|
||||
to accept this layout, or to pick a fallback or to error out if the
|
||||
requested layout is not supported.
|
||||
``--audio-channels=<auto-safe|auto|layouts>``
|
||||
Control which audio channels are output (e.g. surround vs. stereo). There
|
||||
are the following possibilities:
|
||||
|
||||
The ``--audio-channels`` option either takes a channel number or an explicit
|
||||
channel layout. Channel numbers refer to default layouts, e.g. 2 channels
|
||||
refer to stereo, 6 refers to 5.1.
|
||||
- ``--audio-channels=auto-safe``
|
||||
Use the system's preferred channel layout. If there is none (such
|
||||
as when accessing a hardware device instead of the system mixer),
|
||||
force stereo. Some audio outputs might simply accept any layout and
|
||||
do downmixing on their own.
|
||||
|
||||
This is the default.
|
||||
- ``--audio-channels=auto``
|
||||
Send the audio device whatever it accepts, preferring the audio's
|
||||
original channel layout. Can cause issues with HDMI (see the warning
|
||||
below).
|
||||
- ``--audio-channels=layout1,layout2,...``
|
||||
List of ``,``-separated channel layouts which should be allowed.
|
||||
Technically, this only adjusts the filter chain output to the best
|
||||
matching layout in the list, and passes the result to the audio API.
|
||||
It's possible that the audio API will select a different channel
|
||||
layout.
|
||||
|
||||
Using this mode is recommended for direct hardware output, especially
|
||||
over HDMI (see HDMI warning below).
|
||||
- ``--audio-channels=stereo``
|
||||
Force a plain stereo downmix. This is a special-case of the previous
|
||||
item. (See paragraphs below for implications.)
|
||||
|
||||
If a list of layouts is given, each item can be either an explicit channel
|
||||
layout name (like ``5.1``), or a channel number. Channel numbers refer to
|
||||
default layouts, e.g. 2 channels refer to stereo, 6 refers to 5.1.
|
||||
|
||||
See ``--audio-channels=help`` output for defined default layouts. This also
|
||||
lists speaker names, which can be used to express arbitrary channel
|
||||
layouts (e.g. ``fl-fr-lfe`` is 2.1).
|
||||
|
||||
``--audio-channels=auto`` tries to play audio using the input file's
|
||||
channel layout. There is no guarantee that the audio API handles this
|
||||
correctly. See the HDMI warning below.
|
||||
(``empty`` is an accepted obsolete alias for ``auto``.)
|
||||
|
||||
This will also request the channel layout from the decoder. If the decoder
|
||||
does not support the layout, it will fall back to its native channel layout.
|
||||
(You can use ``--ad-lavc-downmix=no`` to make the decoder always output
|
||||
its native layout.) Note that only some decoders support remixing audio.
|
||||
Some that do include AC-3, AAC or DTS audio.
|
||||
If the list of channel layouts has only 1 item, the decoder is asked to
|
||||
produce according output. This sometimes triggers decoder-downmix, which
|
||||
might be different from the normal mpv downmix. (Only some decoders support
|
||||
remixing audio, like AC-3, AAC or DTS. You can use ``--ad-lavc-downmix=no``
|
||||
to make the decoder always output its native layout.) One consequence is
|
||||
that ``--audio-channels=stereo`` triggers decoder downmix, while ``auto``
|
||||
or ``auto-safe`` never will, even if they end up selecting stereo. This
|
||||
happens because the decision whether to use decoder downmix happens long
|
||||
before the audio device is opened.
|
||||
|
||||
If the channel layout of the media file (i.e. the decoder) and the AO's
|
||||
channel layout don't match, mpv will attempt to insert a conversion filter.
|
||||
@@ -1131,6 +1211,10 @@ Audio
|
||||
channel layout, random things can happen, such as dropping the
|
||||
additional channels, or adding noise.
|
||||
|
||||
You are recommended to set an explicit whitelist of the layouts you
|
||||
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
|
||||
@@ -1209,10 +1293,12 @@ Audio
|
||||
their start timestamps differ, and then video timing is gradually adjusted
|
||||
if necessary to reach correct synchronization later.
|
||||
|
||||
``--softvol-max=<100.0-1000.0>``
|
||||
``--volume-max=<100.0-1000.0>``, ``--softvol-max=<...>``
|
||||
Set the maximum amplification level in percent (default: 130). A value of
|
||||
130 will allow you to adjust the volume up to about double the normal level.
|
||||
|
||||
``--softvol-max`` is a deprecated alias and should not be used.
|
||||
|
||||
``--audio-file-auto=<no|exact|fuzzy|all>``, ``--no-audio-file-auto``
|
||||
Load additional audio files matching the video filename. The parameter
|
||||
specifies how external audio files are matched. ``exact`` is enabled by
|
||||
@@ -1221,7 +1307,7 @@ Audio
|
||||
:no: Don't automatically load external audio files.
|
||||
:exact: Load the media filename with audio file extension (default).
|
||||
:fuzzy: Load all audio files containing media filename.
|
||||
:all: Load all aufio files in the current and ``--audio-file-paths``
|
||||
:all: Load all audio files in the current and ``--audio-file-paths``
|
||||
directories.
|
||||
|
||||
``--audio-file-paths=<path1:path2:...>``
|
||||
@@ -1233,7 +1319,7 @@ Audio
|
||||
or to set your own application name when using libmpv.
|
||||
|
||||
``--volume-restore-data=<string>``
|
||||
Used internally for use by playback resume (e.g. with ``quit_watch_later``).
|
||||
Used internally for use by playback resume (e.g. with ``quit-watch-later``).
|
||||
Restoring value has to be done carefully, because different AOs as well as
|
||||
softvol can have different value ranges, and we don't want to restore
|
||||
volume if setting the volume changes it system wide. The normal options
|
||||
@@ -1259,13 +1345,32 @@ Audio
|
||||
|
||||
Default: 0.2 (200 ms).
|
||||
|
||||
``--audio-stream-silence=<yes|no>``
|
||||
Cash-grab consumer audio hardware (such as A/V receivers) often ignore
|
||||
initial audio sent over HDMI. This can happen every time audio over HDMI
|
||||
is stopped and resumed. In order to compensate for this, you can enable
|
||||
this option to not to stop and restart audio on seeks, and fill the gaps
|
||||
with silence. Likewise, when pausing playback, audio is not stopped, and
|
||||
silence is played while paused. Note that if no audio track is selected,
|
||||
the audio device will still be closed immediately.
|
||||
|
||||
Not all AOs support this.
|
||||
|
||||
``--audio-wait-open=<secs>``
|
||||
This makes sense for use with ``--audio-stream-silence=yes``. If this option
|
||||
is given, the player will wait for the given amount of seconds after opening
|
||||
the audio device before sending actual audio data to it. Useful if your
|
||||
expensive hardware discards the first 1 or 2 seconds of audio data sent to
|
||||
it. If ``--audio-stream-silence=yes`` is not set, this option will likely
|
||||
just waste time.
|
||||
|
||||
Subtitles
|
||||
---------
|
||||
|
||||
.. note::
|
||||
|
||||
Changing styling and position does not work with all subtitles. Image-based
|
||||
subtitles (DVD, Bluray/PGS, DVB) can not changed for fundamental reasons.
|
||||
subtitles (DVD, Bluray/PGS, DVB) cannot changed for fundamental reasons.
|
||||
Subtitles in ASS format are normally not changed intentionally, but
|
||||
overriding them can be controlled with ``--ass-style-override``.
|
||||
|
||||
@@ -1423,7 +1528,7 @@ Subtitles
|
||||
|
||||
Using this option may lead to incorrect subtitle rendering.
|
||||
|
||||
``--ass-style-override=<yes|no|force>``
|
||||
``--ass-style-override=<yes|no|force|signfs|strip>``
|
||||
Control whether user style overrides should be applied.
|
||||
|
||||
:yes: Apply all the ``--ass-*`` style override options. Changing the default
|
||||
@@ -1433,6 +1538,8 @@ Subtitles
|
||||
:no: Render subtitles as forced by subtitle scripts.
|
||||
:force: Try to force the font style as defined by the ``--sub-text-*``
|
||||
options. Can break rendering easily.
|
||||
:strip: Radically strip all ASS tags and styles from the subtitle. This
|
||||
is equivalent to the old ``--no-ass`` / ``--no-sub-ass`` options.
|
||||
|
||||
``--ass-force-margins``
|
||||
Enables placing toptitles and subtitles in black borders when they are
|
||||
@@ -1529,6 +1636,13 @@ Subtitles
|
||||
``--sub-ass``, ``--no-sub-ass``
|
||||
Render ASS subtitles natively (enabled by default).
|
||||
|
||||
.. note::
|
||||
|
||||
This has been deprecated by ``--ass-style-override=strip``. You also
|
||||
may need ``--embeddedfonts=no`` to get the same behavior. Also,
|
||||
using ``--ass-style-override=force`` should give better results
|
||||
without breaking subtitles too much.
|
||||
|
||||
If ``--no-sub-ass`` is specified, all tags and style declarations are
|
||||
stripped and ignored on display. The subtitle renderer uses the font style
|
||||
as specified by the ``--sub-text-`` options instead.
|
||||
@@ -1539,10 +1653,6 @@ Subtitles
|
||||
rendering of ASS/SSA subtitles. It can sometimes be useful to forcibly
|
||||
override the styling of ASS subtitles, but should be avoided in general.
|
||||
|
||||
.. note::
|
||||
|
||||
Try using ``--ass-style-override=force`` instead.
|
||||
|
||||
``--sub-auto=<no|exact|fuzzy|all>``, ``--no-sub-auto``
|
||||
Load additional subtitle files matching the video filename. The parameter
|
||||
specifies how external subtitle files are matched. ``exact`` is enabled by
|
||||
@@ -1767,6 +1877,23 @@ Window
|
||||
file.mkv normally, then fail to open ``/dev/null``, then exit). (In
|
||||
mpv 0.8.0, ``always`` was introduced, which restores the old behavior.)
|
||||
|
||||
``--image-display-duration=<seconds|inf>``
|
||||
If the current file is an image, play the image for the given amount of
|
||||
seconds (default: 1). ``inf`` means the file is kept open forever (until
|
||||
the user stops playback manually).
|
||||
|
||||
Unlike ``--keep-open``, the player is not paused, but simply continues
|
||||
playback until the time has elapsed. (It should not use any resources
|
||||
during "playback".)
|
||||
|
||||
This affects image files, which are defined as having only 1 video frame
|
||||
and no audio. The player may recognize certain non-images as images, for
|
||||
example if ``--length`` is used to reduce the length to 1 frame, or if
|
||||
you seek to the last frame.
|
||||
|
||||
This option does not affect the framerate used for ``mf://`` or
|
||||
``--merge-files``. For that, use ``--mf-fps`` instead.
|
||||
|
||||
``--force-window=<yes|no|immediate>``
|
||||
Create a video output window even if there is no video. This can be useful
|
||||
when pretending that mpv is a GUI application. Currently, the window
|
||||
@@ -1783,6 +1910,12 @@ Window
|
||||
mode can be used to create the window always on program start, but this
|
||||
may cause other issues.
|
||||
|
||||
``--taskbar-progress``, ``--no-taskbar-progress``
|
||||
(Windows only)
|
||||
Enable/disable playback progress rendering in taskbar (Windows 7 and above).
|
||||
|
||||
Enabled by default.
|
||||
|
||||
``--ontop``
|
||||
Makes the player window stay on top of other windows.
|
||||
|
||||
@@ -1794,6 +1927,12 @@ Window
|
||||
Play video with window border and decorations. Since this is on by
|
||||
default, use ``--no-border`` to disable the standard window decorations.
|
||||
|
||||
``--fit-border``, ``--no-fit-border``
|
||||
(Windows only) Fit the whole window with border and decorations on the
|
||||
screen. Since this is on by default, use ``--no-fit-border`` to make mpv
|
||||
try to only fit client area with video on the screen. This behavior only
|
||||
applied to window/video with size exceeding size of the screen.
|
||||
|
||||
``--on-all-workspaces``
|
||||
(X11 only)
|
||||
Show the video window on all virtual desktops.
|
||||
@@ -1885,7 +2024,7 @@ Window
|
||||
Make the window width 70% of the screen size, keeping aspect ratio.
|
||||
``1000``
|
||||
Set the window width to 1000 pixels, keeping aspect ratio.
|
||||
``70%:60%``
|
||||
``70%x60%``
|
||||
Make the window as large as possible, without being wider than 70%
|
||||
of the screen width, or higher than 60% of the screen height.
|
||||
|
||||
@@ -1947,6 +2086,14 @@ Window
|
||||
be the default behavior. Currently only affects X11 VOs.
|
||||
|
||||
``--heartbeat-cmd=<command>``
|
||||
|
||||
.. warning::
|
||||
|
||||
This option is redundant with Lua scripting. Further, it shouldn't be
|
||||
needed for disabling screensaver anyway, since mpv will call
|
||||
``xdg-screensaver`` when using X11 backend. As a consequence this
|
||||
option has been deprecated with no direct replacement.
|
||||
|
||||
Command that is executed every 30 seconds during playback via *system()* -
|
||||
i.e. using the shell. The time between the commands can be customized with
|
||||
the ``--heartbeat-interval`` option. The command is not run while playback
|
||||
@@ -1972,7 +2119,7 @@ Window
|
||||
|
||||
.. admonition:: Example for GNOME screensaver
|
||||
|
||||
``mpv --heartbeat-cmd="gnome-screensaver-command -p" file``
|
||||
``mpv --heartbeat-cmd="gnome-screensaver-command --deactivate" file``
|
||||
|
||||
|
||||
``--heartbeat-interval=<sec>``
|
||||
@@ -2068,9 +2215,17 @@ Window
|
||||
|
||||
This option might be removed in the future.
|
||||
|
||||
``--x11-bypass-compositor=<yes|no>``
|
||||
``--x11-bypass-compositor=<yes|no|fs-only|never>``
|
||||
If set to ``yes``, then ask the compositor to unredirect the mpv window
|
||||
(default: no). This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
(default: ``fs-only``). This uses the ``_NET_WM_BYPASS_COMPOSITOR`` hint.
|
||||
|
||||
``fs-only`` asks the window manager to disable the compositor only in
|
||||
fullscreen mode.
|
||||
|
||||
``no`` sets ``_NET_WM_BYPASS_COMPOSITOR`` to 0, which is the default value
|
||||
as declared by the EWMH specification, i.e. no change is done.
|
||||
|
||||
``never`` asks the window manager to never disable the compositor.
|
||||
|
||||
|
||||
Disc Devices
|
||||
@@ -2358,7 +2513,7 @@ Demuxer
|
||||
stop reading additional packets as soon as one of the limits is reached.
|
||||
(The limits still can be slightly overstepped due to technical reasons.)
|
||||
|
||||
Set these limits highher if you get a packet queue overflow warning, and
|
||||
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.
|
||||
@@ -2383,7 +2538,7 @@ Demuxer
|
||||
|
||||
``--force-seekable=<yes|no>``
|
||||
If the player thinks that the media is not seekable (e.g. playing from a
|
||||
pipe, or it's a http stream with a server that doesn't support range
|
||||
pipe, or it's an http stream with a server that doesn't support range
|
||||
requests), seeking will be disabled. This option can forcibly enable it.
|
||||
For seeks within the cache, there's a good chance of success.
|
||||
|
||||
@@ -2453,7 +2608,7 @@ Input
|
||||
automatically enabled when ``-`` is found on the command line. There are
|
||||
situations where you have to set it manually, e.g. if you open
|
||||
``/dev/stdin`` (or the equivalent on your system), use stdin in a playlist
|
||||
or intend to read from stdin later on via the loadfile or loadlist slave
|
||||
or intend to read from stdin later on via the loadfile or loadlist input
|
||||
commands.
|
||||
|
||||
``--input-ipc-server=<filename>``
|
||||
@@ -2575,7 +2730,7 @@ OSD
|
||||
(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
|
||||
This is also used for the ``show-progress`` command (by default mapped to
|
||||
``P``), or in some non-default cases when seeking.
|
||||
|
||||
``--osd-status-msg`` is a legacy equivalent (but with a minor difference).
|
||||
@@ -2583,7 +2738,7 @@ OSD
|
||||
``--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
|
||||
``show-progress`` command (by default mapped to ``P``), or in some
|
||||
non-default cases when seeking. Expands properties. See
|
||||
`Property Expansion`_.
|
||||
|
||||
@@ -2844,7 +2999,7 @@ Screenshot
|
||||
insert the number of the current month as number. You have to use
|
||||
multiple ``%tX`` specifiers to build a full date/time string.
|
||||
``%{prop[:fallback text]}``
|
||||
Insert the value of the slave property 'prop'. E.g. ``%{filename}`` is
|
||||
Insert the value of the input property 'prop'. E.g. ``%{filename}`` is
|
||||
the same as ``%f``. If the property does not exist or is not available,
|
||||
an error text is inserted, unless a fallback is specified.
|
||||
``%%``
|
||||
@@ -3093,7 +3248,7 @@ TV
|
||||
If <chan> is an integer greater than 1000, it will be treated as
|
||||
frequency (in kHz) rather than channel name from frequency table.
|
||||
Use _ for spaces in names (or play with quoting ;-) ). The channel
|
||||
names will then be written using OSD, and the slave commands
|
||||
names will then be written using OSD, and the input commands
|
||||
``tv_step_channel``, ``tv_set_channel`` and ``tv_last_channel``
|
||||
will be usable for a remote control. Not compatible with
|
||||
the ``frequency`` parameter.
|
||||
@@ -3210,7 +3365,7 @@ Cache
|
||||
With ``auto``, the cache will usually be enabled for network streams,
|
||||
using the size set by ``--cache-default``. With ``yes``, the cache will
|
||||
always be enabled with the size set by ``--cache-default`` (unless the
|
||||
stream can not be cached, or ``--cache-default`` disables caching).
|
||||
stream cannot be cached, or ``--cache-default`` disables caching).
|
||||
|
||||
May be useful when playing files from slow media, but can also have
|
||||
negative effects, especially with file formats that require a lot of
|
||||
@@ -3516,7 +3671,7 @@ Miscellaneous
|
||||
``--video-sync-max-audio-change=<value>``
|
||||
Maximum *additional* speed difference in percent that is applied to audio
|
||||
with ``--video-sync=display-...`` (default: 0.125). Normally, the player
|
||||
play the audio at the speed of the video. But if the difference between
|
||||
plays the audio at the speed of the video. But if the difference between
|
||||
audio and video position is too high, e.g. due to drift or other timing
|
||||
errors, it will attempt to speed up or slow down audio by this additional
|
||||
factor. Too low values could lead to video frame dropping or repeating if
|
||||
@@ -3524,7 +3679,7 @@ Miscellaneous
|
||||
frame dropping due to the audio "overshooting" and skipping multiple video
|
||||
frames before the sync logic can react.
|
||||
|
||||
``--video-sync-adrop-size=<value``
|
||||
``--video-sync-adrop-size=<value>``
|
||||
For the ``--video-sync=display-adrop`` mode. This mode duplicates/drops
|
||||
audio data to keep audio in sync with video. To avoid audio artifacts on
|
||||
jitter (which would add/remove samples all the time), this is done in
|
||||
@@ -3542,7 +3697,7 @@ Miscellaneous
|
||||
``--stream-capture=<filename>``
|
||||
Allows capturing the primary stream (not additional audio tracks or other
|
||||
kind of streams) into the given file. Capturing can also be started and
|
||||
stopped by changing the filename with the ``stream-capture`` slave property.
|
||||
stopped by changing the filename with the ``stream-capture`` property.
|
||||
Generally this will not produce usable results for anything else than MPEG
|
||||
or raw streams, unless capturing includes the file headers and is not
|
||||
interrupted. Note that, due to cache latencies, captured data may begin and
|
||||
@@ -3586,6 +3741,16 @@ Miscellaneous
|
||||
``--audio-file``, this includes all tracks, and does not cause default
|
||||
stream selection over the "proper" file.
|
||||
|
||||
``--autoload-files=<yes|no>``
|
||||
Automatically load/select external files (default: yes).
|
||||
|
||||
If set to ``no``, then do not automatically load external files as specified
|
||||
by ``--sub-auto`` and ``--audio-file-auto``. If external files are forcibly
|
||||
added (like with ``--sub-file``), they will not be auto-selected.
|
||||
|
||||
This does not affect playlist expansion, redirection, or other loading of
|
||||
referenced files like with ordered chapters.
|
||||
|
||||
``--lavfi-complex=<string>``
|
||||
Set a "complex" libavfilter filter, which means a single filter graph can
|
||||
take input from multiple source audio and video tracks. The graph can result
|
||||
@@ -3597,7 +3762,7 @@ Miscellaneous
|
||||
- A label of the form ``aidN`` selects audio track N as input (e.g.
|
||||
``aid1``).
|
||||
- A label of the form ``vidN`` selects video track N as input.
|
||||
- A label named ``ao`` will be connected to the audio input.
|
||||
- A label named ``ao`` will be connected to the audio output.
|
||||
- A label named ``vo`` will be connected to the video output.
|
||||
|
||||
Each label can be used only once. If you want to use e.g. an audio stream
|
||||
@@ -3605,7 +3770,7 @@ Miscellaneous
|
||||
video or audio outputs are not possible, but you can use filters to merge
|
||||
them into one.
|
||||
|
||||
The complex filter can not be changed yet during playback. It's also not
|
||||
The complex filter cannot be changed yet during playback. It's also not
|
||||
possible to change the tracks connected to the filter at runtime. Other
|
||||
tracks, as long as they're not connected to the filter, and the
|
||||
corresponding output is not connected to the filter, can still be freely
|
||||
@@ -3634,4 +3799,5 @@ Miscellaneous
|
||||
- ``null:// --lavfi-complex='life [vo]'``
|
||||
Conways' Life Game.
|
||||
|
||||
See the FFmpeg libavfilter documentation for details on the filter.
|
||||
See the FFmpeg libavfilter documentation for details on the available
|
||||
filters.
|
||||
+2
-2
@@ -266,6 +266,6 @@ Example
|
||||
You could put this into ``input.conf`` to hide the OSC with the ``a`` key and
|
||||
to set auto mode (the default) with ``b``::
|
||||
|
||||
a script_message osc-visibility never
|
||||
b script_message osc-visibility auto
|
||||
a script-message osc-visibility never
|
||||
b script-message osc-visibility auto
|
||||
|
||||
+30
-2
@@ -214,7 +214,7 @@ Available filters are:
|
||||
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 subsamplimg, it will fail.
|
||||
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
|
||||
@@ -288,6 +288,8 @@ Available filters are:
|
||||
:adobe: Adobe RGB (1998)
|
||||
:prophoto: ProPhoto RGB (ROMM)
|
||||
:cie1931: CIE 1931 RGB
|
||||
:dci-p3: DCI-P3 (Digital Cinema)
|
||||
:v-gamut: Panasonic V-Gamut primaries
|
||||
|
||||
``<gamma>``
|
||||
Gamma function the source file was encoded with. Normally this should be set
|
||||
@@ -303,13 +305,24 @@ Available filters are:
|
||||
Available gamma functions are:
|
||||
|
||||
:auto: automatic selection (default)
|
||||
:bt.1886: ITU-R BT.1886 (approximation of BT.601/BT.709/BT.2020 curve)
|
||||
:bt.1886: ITU-R BT.1886 (EOTF corresponding to BT.601/BT.709/BT.2020)
|
||||
:srgb: IEC 61966-2-4 (sRGB)
|
||||
:linear: Linear light
|
||||
:gamma1.8: Pure power curve (gamma 1.8)
|
||||
:gamma2.2: Pure power curve (gamma 2.2)
|
||||
:gamma2.8: Pure power curve (gamma 2.8)
|
||||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:st2084: SMPTE ST2084 (HDR) curve
|
||||
:std-b67: ARIB STD-B67 (Hybrid Log-gamma) curve
|
||||
:v-log: Panasonic V-Log transfer curve
|
||||
|
||||
``<peak>``
|
||||
Reference peak illumination for the video file. This is mostly
|
||||
interesting for HDR, but it can also be used tone map SDR content
|
||||
to a darker or brighter exposure.
|
||||
|
||||
The default of 0.0 will default to the display's reference brightness
|
||||
for SDR and the source's reference brightness for HDR.
|
||||
|
||||
``<stereo-in>``
|
||||
Set the stereo mode the video is assumed to be encoded in. Takes the
|
||||
@@ -800,6 +813,21 @@ Available filters are:
|
||||
This filter must be specified before ``vdpaupp`` in the filter chain if
|
||||
``vdpaupp`` is used.
|
||||
|
||||
``d3d11vpp``
|
||||
Direct3D 11 video post processing. Currently requires D3D11 hardware
|
||||
decoding for use.
|
||||
|
||||
``deint=<yes|no>``
|
||||
Whether deinterlacing is enabled (default: no).
|
||||
``interlaced-only=<yes|no>``
|
||||
If ``yes`` (default), only deinterlace frames marked as interlaced.
|
||||
``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
|
||||
which algorithm is actually selected. ``none`` always falls back. On
|
||||
most if not all hardware, this option will probably do nothing, because
|
||||
a video processor usually supports all modes or none.
|
||||
|
||||
``buffer=<num>``
|
||||
Buffer ``<num>`` frames in the filter chain. This filter is probably pretty
|
||||
useless, except for debugging. (Note that this won't help to smooth out
|
||||
|
||||
+242
-75
@@ -362,6 +362,9 @@ Available video output drivers are:
|
||||
exchange for adding some blur. This filter is good at temporal
|
||||
interpolation, and also known as "smoothmotion" (see ``tscale``).
|
||||
|
||||
``linear``
|
||||
A ``tscale`` filter.
|
||||
|
||||
``custom``
|
||||
A user-defined custom shader (see ``scale-shader``).
|
||||
|
||||
@@ -465,7 +468,7 @@ Available video output drivers are:
|
||||
8
|
||||
Dither to 8 bit output.
|
||||
|
||||
Note that the depth of the connected video display device can not be
|
||||
Note that the depth of the connected video display device cannot be
|
||||
detected. Often, LCD panels will do dithering on their own, which
|
||||
conflicts with ``opengl``'s dithering and leads to ugly output.
|
||||
|
||||
@@ -582,68 +585,6 @@ Available video output drivers are:
|
||||
better than without it) since it will extend the size to match only the
|
||||
milder of the scale factors between the axes.
|
||||
|
||||
``prescale-luma=<filter>``
|
||||
Apply additional pre-scaling (image doubling) on the luma plane
|
||||
(if present). As the name implies, these will run before the main
|
||||
upscaling pass.
|
||||
|
||||
``none``
|
||||
Disable all prescalers. This is the default.
|
||||
|
||||
``superxbr``
|
||||
A relatively fast prescaler originally developed for pixel art.
|
||||
|
||||
Some parameters can be tuned with ``superxbr-sharpness`` and
|
||||
``superxbr-edge-strength`` options.
|
||||
|
||||
``nnedi3``
|
||||
An artificial neural network based deinterlacer, which can be used
|
||||
to upscale images.
|
||||
|
||||
Extremely slow and requires a recent mid or high end graphics card
|
||||
to work smoothly (as of 2015).
|
||||
|
||||
``prescale-passes=<1..5>``
|
||||
The number of passes to apply the prescaler (defaults to be 1). Setting
|
||||
it to 2 will perform a 4x upscaling.
|
||||
|
||||
``prescale-downscaling-threshold=<0..32>``
|
||||
This option prevents "overkill" use of prescalers, which can be caused
|
||||
by misconfiguration, or user trying to play a video with much larger
|
||||
size. With this option, user can specify the maximal allowed downscaling
|
||||
ratio in both dimension. To satisfy it, the number of passes for
|
||||
prescaler will be reduced, and if necessary prescaler could also be
|
||||
disabled.
|
||||
|
||||
The default value is 2.0, and should be able to prevent most seemingly
|
||||
unreasonable use of prescalers. Most user would probably want to set it
|
||||
to a smaller value between 1.0 and 1.5 for better performance.
|
||||
|
||||
A value less than 1.0 will disable the check.
|
||||
|
||||
``nnedi3-neurons=<16|32|64|128>``
|
||||
Specify the neurons for nnedi3 prescaling (defaults to be 32). The
|
||||
rendering time is expected to be linear to the number of neurons.
|
||||
|
||||
``nnedi3-window=<8x4|8x6>``
|
||||
Specify the size of local window for sampling in nnedi3 prescaling
|
||||
(defaults to be ``8x4``). The ``8x6`` window produces sharper images,
|
||||
but is also slower.
|
||||
|
||||
``nnedi3-upload=<ubo|shader>``
|
||||
Specify how to upload the NN weights to GPU. Depending on the graphics
|
||||
card, driver, shader compiler and nnedi3 settings, both method can be
|
||||
faster or slower.
|
||||
|
||||
``ubo``
|
||||
Upload these weights via uniform buffer objects. This is the
|
||||
default. (requires OpenGL 3.1 / GLES 3.0)
|
||||
|
||||
``shader``
|
||||
Hard code all the weights into the shader source code. (requires
|
||||
OpenGL 3.3 / GLES 3.0)
|
||||
|
||||
|
||||
``pre-shaders=<files>``, ``post-shaders=<files>``, ``scale-shader=<file>``
|
||||
Custom GLSL fragment shaders.
|
||||
|
||||
@@ -691,6 +632,9 @@ Available video output drivers are:
|
||||
never resets (regardless of seeks).
|
||||
vec2 image_size
|
||||
The size in pixels of the input image.
|
||||
vec2 target_size
|
||||
The size in pixels of the visible part of the scaled (and possibly
|
||||
cropped) image.
|
||||
|
||||
For example, a shader that inverts the colors could look like this::
|
||||
|
||||
@@ -700,6 +644,152 @@ Available video output drivers are:
|
||||
return vec4(1.0 - color.rgb, color.a);
|
||||
}
|
||||
|
||||
``user-shaders=<files>``
|
||||
Custom GLSL hooks. These are similar to ``post-shaders`` etc., but more
|
||||
flexible: They can be injected at almost arbitrary points in the
|
||||
rendering pipeline, and access all previous intermediate textures.
|
||||
|
||||
.. admonition:: Warning
|
||||
|
||||
The syntax is not stable yet and may change any time.
|
||||
|
||||
The general syntax of a user shader looks like this::
|
||||
|
||||
//!METADATA ARGS...
|
||||
//!METADATA ARGS...
|
||||
|
||||
vec4 hook() {
|
||||
...
|
||||
return something;
|
||||
}
|
||||
|
||||
//!METADATA ARGS...
|
||||
//!METADATA ARGS...
|
||||
|
||||
...
|
||||
|
||||
Each block of metadata, along with the non-metadata lines after it,
|
||||
defines a single pass. Each pass can set the following metadata:
|
||||
|
||||
HOOK <name> (required)
|
||||
The texture which to hook into. May occur multiple times within a
|
||||
metadata block, up to a predetermined limit. See below for a list
|
||||
of hookable textures.
|
||||
|
||||
BIND <name>
|
||||
Loads a texture and makes it available to the pass, and sets up
|
||||
macros to enable accessing it. See below for a list of set macros.
|
||||
By default, no textures are bound. The special name HOOKED can be
|
||||
used to refer to the texture that triggered this pass.
|
||||
|
||||
SAVE <name>
|
||||
Gives the name of the texture to save the result of this pass
|
||||
into. By default, this is set to the special name HOOKED which has
|
||||
the effect of overwriting the hooked texture.
|
||||
|
||||
WIDTH <szexpr>, HEIGHT <szexpr>
|
||||
Specifies the size of the resulting texture for this pass.
|
||||
``szexpr`` refers to an expression in RPN (reverse polish
|
||||
notation), using the operators + - * / > < !, floating point
|
||||
literals, and references to sizes of existing texture and OUTPUT
|
||||
(such as MAIN.width or CHROMA.height). By default, these are set to
|
||||
HOOKED.w and HOOKED.h, respectively.
|
||||
|
||||
WHEN <szexpr>
|
||||
Specifies a condition that needs to be true (non-zero) for the
|
||||
shader stage to be evaluated. If it fails, it will silently be
|
||||
omitted. (Note that a shader stage like this which has a dependency
|
||||
on an optional hook point can still cause that hook point to be
|
||||
saved, which has some minor overhead)
|
||||
|
||||
OFFSET ox oy
|
||||
Indicates a pixel shift (offset) introduced by this pass. These
|
||||
pixel offsets will be accumulated and corrected during the
|
||||
next scaling pass (``cscale`` or ``scale``). The default values
|
||||
are 0 0 which correspond to no shift. Note that offsets are ignored
|
||||
when not overwriting the hooked texture.
|
||||
|
||||
COMPONENTS n
|
||||
Specifies how many components of this pass's output are relevant
|
||||
and should be stored in the texture, up to 4 (rgba). By default,
|
||||
this value is equal to the number of components in HOOKED.
|
||||
|
||||
Each bound texture (via ``BIND``) will make available the following
|
||||
definitions to that shader pass, where NAME is the name of the bound
|
||||
texture:
|
||||
|
||||
vec4 NAME_tex(vec2 pos)
|
||||
The sampling function to use to access the texture at a certain
|
||||
spot (in texture coordinate space, range [0,1]). This takes care
|
||||
of any necessary normalization conversions.
|
||||
vec4 NAME_texOff(vec2 offset)
|
||||
Sample the texture at a certain offset in pixels. This works like
|
||||
NAME_tex but additionally takes care of necessary rotations, so
|
||||
that sampling at e.g. vec2(-1,0) is always one pixel to the left.
|
||||
vec2 NAME_pos
|
||||
The local texture coordinate of that texture, range [0,1].
|
||||
vec2 NAME_size
|
||||
The (rotated) size in pixels of the texture.
|
||||
mat2 NAME_rot
|
||||
The rotation matrix associated with this texture. (Rotates
|
||||
pixel space to texture coordinates)
|
||||
vec2 NAME_pt
|
||||
The (unrotated) size of a single pixel, range [0,1].
|
||||
sampler NAME_raw
|
||||
The raw bound texture itself. The use of this should be
|
||||
avoided unless absolutely necessary.
|
||||
|
||||
In addition, the global uniforms described in ``post-shaders`` are
|
||||
also available.
|
||||
|
||||
Internally, vo_opengl may generate any number of the following
|
||||
textures. Whenever a texture is rendered and saved by vo_opengl, all of
|
||||
the passes that have hooked into it will run, in the order they were
|
||||
added by the user. This is a list of the legal hook points:
|
||||
|
||||
RGB, LUMA, CHROMA, ALPHA, XYZ (resizable)
|
||||
Source planes (raw). Which of these fire depends on the image
|
||||
format of the source.
|
||||
|
||||
CHROMA_SCALED, ALPHA_SCALED (fixed)
|
||||
Source planes (upscaled). These only fire on subsampled content.
|
||||
|
||||
NATIVE (resizable)
|
||||
The combined image, in the source colorspace, before conversion
|
||||
to RGB.
|
||||
|
||||
MAINPRESUB (resizable)
|
||||
The image, after conversion to RGB, but before
|
||||
``blend-subtitles=video`` is applied.
|
||||
|
||||
MAIN (resizable)
|
||||
The main image, after conversion to RGB but before upscaling.
|
||||
|
||||
LINEAR (fixed)
|
||||
Linear light image, before scaling. This only fires when
|
||||
``linear-scaling`` is in effect.
|
||||
|
||||
SIGMOID (fixed)
|
||||
Sigmoidized light, before scaling. This only fires when
|
||||
``sigmoid-upscaling`` is in effect.
|
||||
|
||||
PREKERNEL (fixed)
|
||||
The image immediately before the scaler kernel runs.
|
||||
|
||||
POSTKERNEL (fixed)
|
||||
The image immediately after the scaler kernel runs.
|
||||
|
||||
SCALED (fixed)
|
||||
The final upscaled image, before color management.
|
||||
|
||||
OUTPUT (fixed)
|
||||
The final output image, after color management but before
|
||||
dithering and drawing to screen.
|
||||
|
||||
Only the textures labelled with ``resizable`` may be transformed by the
|
||||
pass. When overwriting a texture marked ``fixed``, the WIDTH, HEIGHT
|
||||
and OFFSET must be left at their default values.
|
||||
|
||||
``deband``
|
||||
Enable the debanding algorithm. This greatly reduces the amount of
|
||||
visible banding, blocking and other quantization artifacts, at the
|
||||
@@ -791,6 +881,16 @@ Available video output drivers are:
|
||||
|
||||
Windows only.
|
||||
|
||||
``dcomposition=<yes|no>``
|
||||
Allows DirectComposition when using the ANGLE backend (default: yes).
|
||||
DirectComposition implies flip-model presentation, which can improve
|
||||
rendering efficiency on Windows 8+ by avoiding a copy of the video frame.
|
||||
mpv uses it by default where possible, but it can cause poor behaviour
|
||||
with some drivers, such as a black screen or graphical corruption when
|
||||
leaving full-screen mode. Use "no" to disable it.
|
||||
|
||||
Windows with ANGLE only.
|
||||
|
||||
``sw``
|
||||
Continue even if a software renderer is detected.
|
||||
|
||||
@@ -807,8 +907,8 @@ Available video output drivers are:
|
||||
Win32/WGL
|
||||
angle
|
||||
Direct3D11 through the OpenGL ES translation layer ANGLE. This
|
||||
supports almost everything the ``win`` backend does, except ICC
|
||||
profiles, and the ``nnedi3`` prescaler.
|
||||
supports almost everything the ``win`` backend does (if the ANGLE
|
||||
build is new enough).
|
||||
dxinterop (experimental)
|
||||
Win32, using WGL for rendering and Direct3D 9Ex for presentation.
|
||||
Works on Nvidia and AMD. Newer Intel chips with the latest drivers
|
||||
@@ -835,10 +935,11 @@ Available video output drivers are:
|
||||
``fbo-format=<fmt>``
|
||||
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: rgb, rgba, rgb8, rgb10, rgb10_a2, rgb16, rgb16f,
|
||||
``fmt`` can be one of: rgb8, rgb10, rgb10_a2, rgb16, rgb16f,
|
||||
rgb32f, rgba12, rgba16, rgba16f, rgba32f.
|
||||
Default: ``auto``, which maps to rgba16 on desktop GL, and rgba16f or
|
||||
rgb10_a2 on GLES (e.g. ANGLE).
|
||||
rgb10_a2 on GLES (e.g. ANGLE), unless GL_EXT_texture_norm16 is
|
||||
available.
|
||||
|
||||
``gamma=<0.1..2.0>``
|
||||
Set a gamma value (default: 1.0). If gamma is adjusted in other ways
|
||||
@@ -854,6 +955,10 @@ Available video output drivers are:
|
||||
0.8
|
||||
Pitch black room
|
||||
|
||||
NOTE: Typical movie content (Blu-ray etc.) already contains a gamma
|
||||
drop of about 0.8, so specifying it here as well will result in even
|
||||
even darker image than intended!
|
||||
|
||||
``gamma-auto``
|
||||
Automatically corrects the gamma value depending on ambient lighting
|
||||
conditions (adding a gamma boost for dark rooms).
|
||||
@@ -864,8 +969,9 @@ Available video output drivers are:
|
||||
NOTE: Only implemented on OS X.
|
||||
|
||||
``target-prim=<value>``
|
||||
Specifies the primaries of the display. Video colors will be adapted
|
||||
to this colorspace if necessary. Valid values are:
|
||||
Specifies the primaries of the display. Video colors will be adapted to
|
||||
this colorspace when ICC color management is not being used. Valid
|
||||
values are:
|
||||
|
||||
auto
|
||||
Disable any adaptation (default)
|
||||
@@ -889,15 +995,18 @@ Available video output drivers are:
|
||||
CIE 1931 RGB (not to be confused with CIE XYZ)
|
||||
dci-p3
|
||||
DCI-P3 (Digital Cinema Colorspace), SMPTE RP431-2
|
||||
v-gamut
|
||||
Panasonic V-Gamut (VARICAM) primaries
|
||||
|
||||
``target-trc=<value>``
|
||||
Specifies the transfer characteristics (gamma) of the display. Video
|
||||
colors will be adjusted to this curve. Valid values are:
|
||||
colors will be adjusted to this curve when ICC color management is
|
||||
not being used. Valid values are:
|
||||
|
||||
auto
|
||||
Disable any adaptation (default)
|
||||
bt.1886
|
||||
ITU-R BT.1886 curve, without the brightness drop (approx. 1.961)
|
||||
ITU-R BT.1886 curve (assuming infinite contrast)
|
||||
srgb
|
||||
IEC 61966-2-4 (sRGB)
|
||||
linear
|
||||
@@ -910,9 +1019,61 @@ Available video output drivers are:
|
||||
Pure power curve (gamma 2.8), also used for BT.470-BG
|
||||
prophoto
|
||||
ProPhoto RGB (ROMM)
|
||||
st2084
|
||||
SMPTE ST2084 (HDR) curve, PQ OETF
|
||||
std-b67
|
||||
ARIB STD-B67 (Hybrid Log-gamma) curve, also known as BBC/NHK HDR
|
||||
v-log
|
||||
Panasonic V-Log (VARICAM) curve
|
||||
|
||||
NOTE: When using HDR output formats, mpv will encode to the specified
|
||||
curve but it will not set any HDMI flags or other signalling that
|
||||
might be required for the target device to correctly display the
|
||||
HDR signal. The user should independently guarantee this before
|
||||
using these signal formats for display.
|
||||
|
||||
``target-brightness=<1..100000>``
|
||||
Specifies the display's approximate brightness in cd/m^2. When playing
|
||||
HDR content on a SDR display (or SDR content on an HDR display), video
|
||||
colors will be tone mapped to this target brightness using the
|
||||
algorithm specified by ``hdr-tone-mapping``. The default of 250 cd/m^2
|
||||
corresponds to a typical consumer display.
|
||||
|
||||
``hdr-tone-mapping=<value>``
|
||||
Specifies the algorithm used for tone-mapping HDR images onto the
|
||||
target display. Valid values are:
|
||||
|
||||
clip
|
||||
Hard-clip any out-of-range values.
|
||||
reinhard
|
||||
Reinhard tone mapping algorithm. Very simple continuous curve.
|
||||
Preserves dynamic range and peak but uses nonlinear contrast.
|
||||
hable
|
||||
Similar to ``reinhard`` but preserves dark contrast better
|
||||
(slightly sigmoidal). Developed by John Hable for use in video
|
||||
games. (default)
|
||||
gamma
|
||||
Fits a logarithmic transfer between the tone curves.
|
||||
linear
|
||||
Linearly stretches the entire reference gamut to (a linear multiple
|
||||
of) the display.
|
||||
|
||||
``tone-mapping-param=<value>``
|
||||
Set tone mapping parameters. Ignored if the tone mapping algorithm is
|
||||
not tunable. This affects the following tone mapping algorithms:
|
||||
|
||||
reinhard
|
||||
Specifies the local contrast coefficient at the display peak.
|
||||
Defaults to 0.5, which means that in-gamut values will be about
|
||||
half as bright as when clipping.
|
||||
gamma
|
||||
Specifies the exponent of the function. Defaults to 1.8.
|
||||
linear
|
||||
Specifies the scale factor to use while stretching. Defaults to
|
||||
1.0.
|
||||
|
||||
``icc-profile=<file>``
|
||||
Load an ICC profile and use it to transform linear RGB to screen output.
|
||||
Load an ICC profile and use it to transform video RGB to screen output.
|
||||
Needs LittleCMS 2 support compiled in. This option overrides the
|
||||
``target-prim``, ``target-trc`` and ``icc-profile-auto`` options.
|
||||
|
||||
@@ -947,8 +1108,14 @@ Available video output drivers are:
|
||||
|
||||
``3dlut-size=<r>x<g>x<b>``
|
||||
Size of the 3D LUT generated from the ICC profile in each dimension.
|
||||
Default is 128x256x64.
|
||||
Sizes must be a power of two, and 512 at most.
|
||||
Default is 64x64x64. Sizes may range from 2 to 512.
|
||||
|
||||
``icc-contrast=<0-100000>``
|
||||
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 result, video may appear darker than intended. This
|
||||
only affects BT.1886 content. The default of 0 means no limit.
|
||||
|
||||
``blend-subtitles=<yes|video|no>``
|
||||
Blend subtitles directly onto upscaled video frames, before
|
||||
@@ -1145,7 +1312,7 @@ Available video output drivers are:
|
||||
This also supports many of the suboptions the ``opengl`` VO has. Run
|
||||
``mpv --vo=opengl-cb:help`` for a list.
|
||||
|
||||
This also supports the ``vo_cmdline`` command.
|
||||
This also supports the ``vo-cmdline`` command.
|
||||
|
||||
``rpi`` (Raspberry Pi)
|
||||
Native video output on the Raspberry Pi using the MMAL API.
|
||||
|
||||
@@ -30,7 +30,7 @@ Player
|
||||
* Slave mode compatibility broken (see below).
|
||||
* Re-enable screensaver while the player is paused.
|
||||
* Allow resuming playback at a later point with ``Shift+q``, also see the
|
||||
``quit_watch_later`` input command.
|
||||
``quit-watch-later`` input command.
|
||||
* ``--keep-open`` option to stop the player from closing the window and
|
||||
exiting after playback ends.
|
||||
* A client API, that allows embedding **mpv** into applications
|
||||
@@ -79,7 +79,8 @@ Video
|
||||
using most of FFmpeg's filters, which improve greatly on the old MPlayer
|
||||
filters in features, performance, and correctness.
|
||||
* More correct color reproduction (color matrix generation), including support
|
||||
for BT.2020 (Ultra HD) and linear XYZ (Digital Cinema) inputs.
|
||||
for BT.2020 (Ultra HD). linear XYZ (Digital Cinema) and SMPTE ST2084 (HDR)
|
||||
inputs.
|
||||
* Support for color managed displays, via ICC profiles.
|
||||
* High-quality image resamplers (see the ``opengl`` ``scale`` suboption).
|
||||
* Support for scaling in (sigmoidized) linear light.
|
||||
@@ -311,11 +312,11 @@ input.conf and Slave Commands
|
||||
+--------------------------------+----------------------------------------+
|
||||
| Old | New |
|
||||
+================================+========================================+
|
||||
| ``pt_step 1 [0|1]`` | ``playlist_next [weak|force]`` |
|
||||
| ``pt_step 1 [0|1]`` | ``playlist-next [weak|force]`` |
|
||||
| | (translation layer cannot deal with |
|
||||
| | whitespace) |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``pt_step -1 [0|1]`` | ``playlist_prev [weak|force] (same)`` |
|
||||
| ``pt_step -1 [0|1]`` | ``playlist-prev [weak|force] (same)`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``switch_ratio [<ratio>]`` | ``set video-aspect <ratio>`` |
|
||||
| | |
|
||||
@@ -330,7 +331,7 @@ input.conf and Slave Commands
|
||||
| ``<step> <dir>`` | ``no-osd``: ``no-osd cycle <prop>`` |
|
||||
| | ``<step>`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``osd_show_property_text`` | ``show_text <text>`` |
|
||||
| ``osd_show_property_text`` | ``show-text <text>`` |
|
||||
| ``<text>`` | The property expansion format string |
|
||||
| | syntax slightly changed. |
|
||||
+--------------------------------+----------------------------------------+
|
||||
@@ -339,9 +340,9 @@ input.conf and Slave Commands
|
||||
| | ``raw`` prefix to disable property |
|
||||
| | expansion. |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``show_tracks`` | ``show_text ${track-list}`` |
|
||||
| ``show_tracks`` | ``show-text ${track-list}`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``show_chapters`` | ``show_text ${chapter-list}`` |
|
||||
| ``show_chapters`` | ``show-text ${chapter-list}`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
| ``af_switch``, ``af_add``, ... | ``af set|add|...`` |
|
||||
+--------------------------------+----------------------------------------+
|
||||
|
||||
@@ -25,7 +25,8 @@ Release procedure
|
||||
|
||||
- Create and/or update the `VERSION` file.
|
||||
|
||||
- Update `DOCS/client-api-changes.rst` (on major releases).
|
||||
- Update `DOCS/client-api-changes.rst` and `DOCS/interface-changes.rst`
|
||||
(in particular, update the last version numbers if necessary)
|
||||
|
||||
- Create tag v0.X.Y.
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ options/options.h, options/options.c
|
||||
parser-mpcmd.c, and uses the option table in options.c.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input comming from VOs and other sources (such
|
||||
This translates keyboard input coming from VOs and other sources (such
|
||||
as remote control devices like Apple IR or client API commands) to the
|
||||
key bindings listed in the user's (or the builtin) input.conf and turns
|
||||
them into items of type struct mp_cmd. These commands are queued, and read
|
||||
|
||||
@@ -137,6 +137,20 @@ most likely the best maintenance out of all stable releases. Older releases
|
||||
are for distros, and at best receive basic changes like fixing critical security
|
||||
issues or build fixes, and at worst are completely abandoned.
|
||||
|
||||
## FFmpeg ABI compatibility
|
||||
|
||||
mpv does not support linking against FFmpeg versions it was not built with, even
|
||||
if the linked version is supposedly ABI-compatible with the version it was
|
||||
compiled against. Expect malfunctions, crashes, and security issues if you
|
||||
do it anyway.
|
||||
|
||||
The reason for not supporting this is because it creates far too much complexity
|
||||
with little to no benefit, coupled with absurd and unusable FFmpeg API
|
||||
artifacts.
|
||||
|
||||
Newer mpv versions will refuse to start if runtime and compile time FFmpeg
|
||||
library versions mismatch.
|
||||
|
||||
## Release cycle
|
||||
|
||||
Every other month, an arbitrary git snapshot is made, and is assigned
|
||||
|
||||
+388
@@ -1,3 +1,391 @@
|
||||
Release 0.20.0
|
||||
==============
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- aspect: add --video-unscaled=downscale-big
|
||||
- player: add --image-display-duration option to control duration of image display (#3425)
|
||||
- vo_opengl: angle: new flag (dcomposition) to control DirectComposition
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- af_lavrresample: fix error if resampler could not be recreated
|
||||
- audio: avoid missed wakeups with ab-loops
|
||||
- audio: do not apply --audio-channels if spdif passthrough is in use (#3445)
|
||||
- cache: don't use a backbuffer if the cache is as large as the file
|
||||
- command: prevent O(n^2) behaviour for playlist property
|
||||
- demux: close underlying stream if it's fully read anyway (#3456)
|
||||
- demux: fix undefined behavior with ogg metadata update (#3451)
|
||||
- player: make looping slightly more seamless
|
||||
- player: refresh very low framerate video on filter changes (#3435)
|
||||
- stream_memory: disable stream cache
|
||||
- vf_rotate: allow arbitrary rotation (#3434)
|
||||
- vo: be more trusting to estimated display FPS (#3433)
|
||||
- w32_common: use hooks to detect parent window resize
|
||||
- x11: work around mutter fullscreen issue (#2072)
|
||||
|
||||
|
||||
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.19.0..v0.20.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.19.0...v0.20.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.20.0..v0.20.0
|
||||
|
||||
|
||||
Release 0.19.0
|
||||
==============
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- build: add --htmldir option
|
||||
- build: always require atomics
|
||||
- wscript: add proper unversioned SONAME for Android
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- client API: add stream_cb API for user-defined stream implementations (bumps client API version to 1.22)
|
||||
- vf_d3d11vpp: add video processor selection
|
||||
- videotoolbox: add --hwdec=videotoolbox-copy for h/w accelerated decoding with video filters
|
||||
- vo_opengl: add a tscale=linear direct implementation
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- audio/filter: remove delay audio filter
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- command: add filename/no-ext sub-property that returns filename without extension (#3404)
|
||||
- command: add properties for HDR metadata
|
||||
- command: add replaygain information properties to track-list
|
||||
- options: add vp9 to --hwdec-codecs
|
||||
- player: add --audio-stream-silence
|
||||
- player: add --audio-wait-open
|
||||
- player: add --no-autoload-files
|
||||
- videotoolbox: add yuv420p to --videotoolbox-format
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- options: un-restrict --audio-delay
|
||||
- use - as command-name separator everywhere
|
||||
- vo_opengl: reduce default 3dlut-size to 64x64x64 (since accuracy is improved)
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- deprecate "balance" option/property (no replacement)
|
||||
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- Windows: don't wait for GUI thread when polling for events (#3393)
|
||||
- af_lavcac3enc: error out properly if encoding fails
|
||||
- af_volume: don't let softvol overwrite af_volume volumedb sub-option
|
||||
- ao_pulse: fix some volume control rounding issues
|
||||
- ao_wasapi: in exclusive mode, do not output multichannel by default
|
||||
- audio: add heuristic to move auto-downmixing before other filters
|
||||
- audio: show an osd bar when changing ao-volume
|
||||
- demux: make ALBUM replaygain tags optional (#3405)
|
||||
- demux_raw: fix small typo to add s16be support
|
||||
- demux_timeline: restore mkv edition switching
|
||||
- libarchive: sanitize non-UTF8 archive entries
|
||||
- macOS/vo_opengl: fix crash when glctx is NULL during init (#3360)
|
||||
- player: disable display-sync with spdif transcoding
|
||||
- player: do not cut off terminal status line if it contains newlines (#3340)
|
||||
- player: fix display-sync timing if audio resumes slowly
|
||||
- player: improve instant track switching (#3392)
|
||||
- player: improve non-hr seeking with external audio tracks
|
||||
- player: offset demuxer on start/seek properly with audio/sub delay
|
||||
- player: sync audio as well when enabling it mid-stream
|
||||
- stream/stream_bluray: display list of available titles in verbose mode
|
||||
- sub: don't potentially discard too many subtitles on seek
|
||||
- video: respect --deinterlace=auto
|
||||
- vo_direct3d: add missing header (fixes Cygwin build)
|
||||
- vo_opengl: angle: try D3D9 when D3D11 fails eglInitialize
|
||||
- vo_opengl: angle: use WARP if there are no hw adapters (makes it work on Windows 7 without hardware-accelerated graphics)
|
||||
- vo_opengl: increase 3DLUT accuracy at smaller LUT sizes
|
||||
- vo_opengl: remove the 3dlut-size npot2 restriction
|
||||
- vo_wayland: fix high CPU usage due to busy polling
|
||||
- wayland_common: clip window size to the display output size
|
||||
- wayland_common: fix crashes when switching to fullscreen before the video output is fully initialized
|
||||
- wayland_common: fix fullscreen image switching bug
|
||||
- wayland_common: prevent black bars on most non-native aspect ratios
|
||||
- wayland_common: remove untested/unusable wayland dnd code
|
||||
- win32: mpv.rc: re-add version info
|
||||
- x11: skip ICC update on every window move
|
||||
- ytdl: Error out with http_dash_segments (unsupported for now)
|
||||
|
||||
|
||||
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.18.1..v0.19.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.18.1...v0.19.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.1..v0.19.0
|
||||
|
||||
|
||||
Release 0.18.1
|
||||
==============
|
||||
|
||||
Note: Running mpv with different versions of the FFmpeg/Libav libraries than
|
||||
it was compiled with is no longer supported. Even supposedly ABI-compatible
|
||||
versions have been a source of trouble, and it creates far too much
|
||||
complexity with little to no benefit, coupled with absurd and unusable FFmpeg
|
||||
API artifacts.
|
||||
|
||||
Instead, mpv will exit with an error when such a situation is detected.
|
||||
This simply means that mpv needs to be rebuilt whenever FFmpeg libraries change.
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- d3d: implement screenshots for --hwdec=d3d11va
|
||||
- vo_opengl: add output_size uniform to custom shader
|
||||
- vo_opengl: implement the Panasonic V-Log function (#3157)
|
||||
- vo_opengl: implement ARIB STD-B68 (HLG) HDR TRC
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
- command: pack sub image data in overlay-add command
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate --heartbeat-cmd
|
||||
- audio: deprecate --softvol
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- audio: drop --softvol=no and --softvol=auto (#3322)
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- video: fix deinterlace filter handling on pixel format changes
|
||||
- x11: silence xdg-screensaver
|
||||
- vo_opengl: angle: update the swapchain on resize (#3301)
|
||||
- vo_opengl: error out gracefully when trying to use FBOs without FBO API
|
||||
- vd_lavc: expose mastering display side data reference peak (improves results with HDR content)
|
||||
- vo_opengl: generalize HDR tone mapping mechanism (#3293)
|
||||
- vo_opengl: don't constantly resize the output FBO
|
||||
- vo_opengl: use ringbuffer of PBOs
|
||||
- Windows: make WM_NCHITTEST simpler and more accurate
|
||||
- ao_oss: do not add an entry to audio-device-list if device file missing
|
||||
- dec_audio: fix segment boudnary switching
|
||||
- ao_lavc, vo_lavc: Migrate to new FFmpeg encoding API
|
||||
- vo_opengl: explicitly use main framebuffer when reading window content (#3284)
|
||||
- vo_xv: fix behavior with odd sizes
|
||||
- audio: insert auto-inserted filters at end of chain
|
||||
- x11: add missing FocusChangeMask (disables key repeat when losing focus while a key is down)
|
||||
- ao_coreaudio: error out when selecting invalid device
|
||||
- ad_lavc: work around misbehavior of some FFmpeg decoders like wmapro (#3297)
|
||||
- player: cut off status line on terminal width
|
||||
|
||||
|
||||
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.18.0..v0.18.1`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.18.0...v0.18.1 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.18.0..v0.18.1
|
||||
|
||||
|
||||
Release 0.18.0
|
||||
==============
|
||||
|
||||
|
||||
Build System Changes
|
||||
--------------------
|
||||
|
||||
- build: Do not link to libGL for egl-drm
|
||||
- build: also use the iconv check on FreeBSD
|
||||
- build: don't install tests, only build them
|
||||
- build: re-enable encoding mode by default
|
||||
- vo_opengl: hwdec: remove build-dependency on dxva2 (#3150)
|
||||
- wscript: make at least 1 OpenGL output mandatory
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
New
|
||||
~~~
|
||||
|
||||
- csputils: add SMPTE ST2084 support
|
||||
- demux_mkv: support Matroska webvtt (#3247)
|
||||
- demux_playlist: read directories recursively
|
||||
- stream_memory: add hex:// protocol
|
||||
- vf_crop: support opaque hardware decoding formats
|
||||
- vf_d3d11vpp: add a D3D11 video processor filter
|
||||
- vo_opengl: D3D11VA + ANGLE interop
|
||||
- vo_opengl: add an angle-es2 backend
|
||||
- vo_opengl: angle: dynamically load ANGLE
|
||||
- vo_opengl: d3d11egl: native NV12 sampling support
|
||||
- vo_opengl: enable color management on GLES
|
||||
- vo_opengl: implement HDR (SMPTE ST2084)
|
||||
- vo_opengl: implement tone mapping algorithms
|
||||
- vo_opengl: make PBOs work on GLES 3.x
|
||||
- vo_opengl: support external user hooks, enhancing the flexibility of user shaders
|
||||
- vo_opengl: vdpau interop without RGB conversion
|
||||
- wayland: implement HIDPI support
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove nnedi3 prescaler (replaced by user shaders)
|
||||
- vo_opengl: remove prescaling framework with superxbr prescaler (replaced by user shaders)
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- Windows: make taskbar progress indication optional (#2535)
|
||||
- af_lavcac3enc: make encoder configurable
|
||||
- command: add playlist-pos-1 property (#2828)
|
||||
- command: introduce hwdec-current and hwdec-interop properties.
|
||||
- options: add --fit-border video option (currently Windows only)
|
||||
- video: add --hwdec=auto-copy mode
|
||||
- vo_opengl: always autoselect ANGLE as backend if available
|
||||
- vo_opengl: expose performance timers as properties
|
||||
- x11: add --x11-bypass-compositor=never
|
||||
- x11: extend --x11-bypass-compositor with fs-only option (#2582)
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- command: allow setting panscan etc. properties if no video is active
|
||||
- command: don't seek immediately when setting a-b loop while paused
|
||||
- command: if only ab-loop-b is set, loop from start of file
|
||||
- options: --geometry: center window position after applying size (#2397)
|
||||
- player: loop on end of file if ab-loop-b is unset
|
||||
- sd_add: replace --sub-ass=no with --ass-style-override=strip
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove non-working rgb/rgba FBO formats
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- TOOLS/zsh.pl: add .f4v extension in zsh completions
|
||||
- TOOLS/zsh.pl: complete --audio-device
|
||||
- Windows: center window on original window center on resize to fit screen
|
||||
- Windows: fix size calculations for window resize (#2935)
|
||||
- Windows: fix wrong behavior with window-scale when window size exceeds screen size
|
||||
- Windows: make VOCTRL_SET_UNFS_WINDOW_SIZE resize the window around its center (#3164)
|
||||
- af_lavcac3enc: fix custom bitrates
|
||||
- ao_alsa: add more workarounds for hardware with broken drivers (e.g. ODROID-C2)
|
||||
- ao_opensles: remove 32-bit audio formats (not supported by Android)
|
||||
- cocoa: fix actual display refresh rate retrieval
|
||||
- cocoa: use displaylink without manually tracking the display id (#2392)
|
||||
- command: improve playlist* properties change notifications (#3267)
|
||||
- command: slightly nicer OSD list formatting
|
||||
- compatibility with recent FFmpeg APIs
|
||||
- d3d: fix hardware decoding of most MPEG2 things
|
||||
- d3dva: move Intel_H264_NoFGT_ClearVideo to lower priority (#3059)
|
||||
- demux_mkv: better resync behavior for broken google-created webms
|
||||
- demux_mkv: fix seeking with files that miss the first index entry
|
||||
- demux_playlist: recognize m3u8 as playlist extension (#3154)
|
||||
- input: fix parsing multiple input command prefixes
|
||||
- lcms: don't warn/error on 3dlut cache misses
|
||||
- lcms: improve black point handling (especially BT.1886)
|
||||
- macOS: handle multiple dropped files on the window (#3076)
|
||||
- player: always show the first frame in DS mode
|
||||
- player: assume video forwards timestamps jumps only with some formats (#3027)
|
||||
- player: do not update OSD all the time when paused
|
||||
- player: eagerly redraw OSD when seeking with coverart
|
||||
- player: fix use-after-free with --screenshot-directory (#3049)
|
||||
- player: force VO reconfig when unselecting video track
|
||||
- player: really start audio only once video is ready
|
||||
- sd_lavc: work around bug in older FFmpeg releases (#3109)
|
||||
- stream_cdda: enable cache by default
|
||||
- sub: fix --sub-gauss
|
||||
- vd_lavc: better hwdec wrapper decoder selection
|
||||
- vo_opengl: EGL: fix hwdec probing
|
||||
- vo_opengl: angle: avoid fullscreen FBO copy for flipping
|
||||
- vo_opengl: angle: enable DirectComposition (lowers vsync jitter)
|
||||
- vo_opengl: angle: prevent DXGI hooking Alt+Enter
|
||||
- vo_opengl: avoid outputting ultra-wide-gamut by default
|
||||
- vo_opengl: correctly disable interpolation if tscale can't be used
|
||||
- vo_opengl: fix bicubic_fast in ES mode
|
||||
- vo_opengl: fix d3d11 hardware decoding probing on Windows 7
|
||||
- vo_opengl: improve scale=oversample performance
|
||||
- vo_opengl: make the screen blue on shader errors
|
||||
- vo_opengl: partially fix 0bgr format support
|
||||
- vo_opengl: possibly update icc profile after changing options
|
||||
- vo_opengl: request core profile on X11/EGL too
|
||||
- vo_opengl: require at least ES 3.0 for float textures
|
||||
- vo_opengl: vdpau: fix certain cases of preemption recovery failures
|
||||
- vo_rpi: attempt to survive display mode changes
|
||||
- vo_rpi: fix destroying overlays (#3100)
|
||||
- vo_rpi: wait for vsync with a timeout
|
||||
- vo_sdl: fix pixel formats.
|
||||
- vo_xv: Handle incorrect size returned by Xv(Shm)CreateImage (#320)
|
||||
- wayland: correctly report display refresh rate
|
||||
- wayland: use the advertised size in fullscreen (#3021, #2657)
|
||||
- x11: tell GNOME to use dark window decorations
|
||||
- ytdl_hook: fix brightcove urls
|
||||
- ytdl_hook: just check if protocol is rtmp (#3090)
|
||||
- ytdl_hook: support multi-arc subtitles
|
||||
|
||||
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.17.0..v0.18.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.17.0...v0.18.0 or
|
||||
http://git.srsfckn.biz/mpv/log/?qt=range&q=v0.17.0..v0.18.0
|
||||
|
||||
|
||||
Release 0.17.0
|
||||
==============
|
||||
|
||||
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
export DEST_OS=win32
|
||||
export CC=gcc
|
||||
export PKG_CONFIG=/usr/bin/pkg-config
|
||||
export PERL=/usr/bin/perl
|
||||
export PYTHON=/usr/bin/python3
|
||||
|
||||
"$PYTHON" bootstrap.py
|
||||
"$PYTHON" waf configure \
|
||||
--check-c-compiler=gcc \
|
||||
--disable-cdda \
|
||||
--enable-egl-angle \
|
||||
--enable-jpeg \
|
||||
--enable-lcms2 \
|
||||
--enable-libarchive \
|
||||
--enable-libass \
|
||||
--enable-lua \
|
||||
--enable-rubberband \
|
||||
--enable-uchardet
|
||||
"$PYTHON" waf build
|
||||
Executable
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/bash
|
||||
set -e
|
||||
|
||||
case $MSYSTEM in
|
||||
MINGW32)
|
||||
export MINGW_PACKAGE_PREFIX=mingw-w64-i686
|
||||
;;
|
||||
MINGW64)
|
||||
export MINGW_PACKAGE_PREFIX=mingw-w64-x86_64
|
||||
;;
|
||||
esac
|
||||
|
||||
# Write an empty fonts.conf to speed up fc-cache
|
||||
export FONTCONFIG_FILE=/dummy-fonts.conf
|
||||
cat >"$FONTCONFIG_FILE" <<EOF
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig></fontconfig>
|
||||
EOF
|
||||
|
||||
# Install build dependencies for mpv
|
||||
pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-gcc \
|
||||
$MINGW_PACKAGE_PREFIX-angleproject-git \
|
||||
$MINGW_PACKAGE_PREFIX-ffmpeg \
|
||||
$MINGW_PACKAGE_PREFIX-lcms2 \
|
||||
$MINGW_PACKAGE_PREFIX-libarchive \
|
||||
$MINGW_PACKAGE_PREFIX-libass \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
$MINGW_PACKAGE_PREFIX-uchardet-git
|
||||
|
||||
# Delete unused packages to reduce space used in the Appveyor cache
|
||||
pacman -Sc --noconfirm
|
||||
@@ -25,7 +25,7 @@ function add_files_at(index, files)
|
||||
local oldcount = mp.get_property_number("playlist-count", 1)
|
||||
for i = 1, #files do
|
||||
mp.commandv("loadfile", files[i], "append")
|
||||
mp.commandv("playlist_move", oldcount + i - 1, index + i - 1)
|
||||
mp.commandv("playlist-move", oldcount + i - 1, index + i - 1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
File renamed without changes.
+23
-7
@@ -36,7 +36,8 @@ chomp $vf_str;
|
||||
|
||||
$protos_str = join(' ', @protos);
|
||||
|
||||
my $profile_comp = <<'EOS';
|
||||
my $runtime_completions = <<'EOS';
|
||||
profile|show-profile)
|
||||
local -a profiles
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --profile=help)}"; do
|
||||
@@ -63,8 +64,25 @@ my $profile_comp = <<'EOS';
|
||||
profiles=($profiles 'help[list profiles]')
|
||||
_values -s , 'profile(s)' $profiles && rc=0
|
||||
fi
|
||||
;;
|
||||
|
||||
audio-device)
|
||||
local -a audio_devices
|
||||
local current
|
||||
for current in "${(@f)$($words[1] --audio-device=help)}"; do
|
||||
current=${current//\*/\\\*}
|
||||
current=${current//\:/\\\:}
|
||||
current=${current//\[/\\\[}
|
||||
current=${current//\]/\\\]}
|
||||
if [[ $current =~ ' '\'([^\']*)\'' \('(.*)'\)' ]]; then
|
||||
audio_devices=($audio_devices "$match[1][$match[2]]")
|
||||
fi
|
||||
done
|
||||
audio_devices=($audio_devices 'help[list audio devices]')
|
||||
_values 'audio device' $audio_devices && rc=0
|
||||
;;
|
||||
EOS
|
||||
chomp $profile_comp;
|
||||
chomp $runtime_completions;
|
||||
|
||||
my $tmpl = <<"EOS";
|
||||
#compdef mpv
|
||||
@@ -111,9 +129,7 @@ $vf_str
|
||||
&& rc=0
|
||||
;;
|
||||
|
||||
profile|show-profile)
|
||||
$profile_comp
|
||||
;;
|
||||
$runtime_completions
|
||||
|
||||
files)
|
||||
compset -P '*,'
|
||||
@@ -126,7 +142,7 @@ $profile_comp
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#i)(asf|asx|avi|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv|wv)(-.)" && rc=0
|
||||
"*.(#i)(asf|asx|avi|f4v|flac|flv|m1v|m2p|m2v|m4v|mjpg|mka|mkv|mov|mp3|mp4|mpe|mpeg|mpg|ogg|ogm|ogv|opus|qt|rm|ts|vob|wav|webm|wma|wmv|wv)(-.)" && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
@@ -189,7 +205,7 @@ sub parse_main_opts {
|
||||
}
|
||||
} elsif ($line =~ /\[file\]/) {
|
||||
$entry .= '->files';
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile)$/) {
|
||||
} elsif ($name =~ /^--(ao|vo|af|vf|profile|show-profile|audio-device)$/) {
|
||||
$entry .= "->$1";
|
||||
}
|
||||
push @list, $entry;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- appveyor
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- MSYSTEM: MINGW64
|
||||
- MSYSTEM: MINGW32
|
||||
|
||||
shallow_clone: true
|
||||
test: off
|
||||
|
||||
install:
|
||||
# Update core packages
|
||||
- C:\msys64\usr\bin\pacman -Syyuu --noconfirm
|
||||
# Update non-core packages
|
||||
- C:\msys64\usr\bin\pacman -Suu --noconfirm
|
||||
# Install required MSYS2 packages
|
||||
- C:\msys64\usr\bin\pacman -S --noconfirm --needed 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 \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-install.sh"
|
||||
build_script:
|
||||
- C:\msys64\usr\bin\bash -lc "cd \"$APPVEYOR_BUILD_FOLDER\" && exec ./TOOLS/appveyor-build.sh"
|
||||
on_failure:
|
||||
- type "%APPVEYOR_BUILD_FOLDER%\build\config.log"
|
||||
|
||||
cache:
|
||||
- C:\msys64\var\cache\pacman\pkg
|
||||
+44
-13
@@ -138,6 +138,9 @@ static void mp_audio_destructor(void *ptr)
|
||||
* 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
|
||||
@@ -153,6 +156,12 @@ 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)
|
||||
@@ -171,7 +180,7 @@ void mp_audio_realloc(struct mp_audio *mpa, int samples)
|
||||
// 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)) {
|
||||
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
|
||||
@@ -257,13 +266,21 @@ void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
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)
|
||||
{
|
||||
if (f->pts == MP_NOPTS_VALUE || f->rate < 1)
|
||||
double f_end = mp_audio_end_pts(f);
|
||||
if (f_end == MP_NOPTS_VALUE)
|
||||
return;
|
||||
double f_end = f->pts + f->samples / (double)f->rate;
|
||||
if (end != MP_NOPTS_VALUE) {
|
||||
if (f_end >= end) {
|
||||
if (f->pts >= end) {
|
||||
@@ -339,9 +356,9 @@ struct mp_audio *mp_audio_from_avframe(struct AVFrame *avframe)
|
||||
mp_chmap_from_lavc(&lavc_chmap, avframe->channel_layout);
|
||||
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being special again
|
||||
if (lavc_chmap.num != avframe->channels)
|
||||
mp_chmap_from_channels(&lavc_chmap, avframe->channels);
|
||||
// FFmpeg being stupid POS again
|
||||
if (lavc_chmap.num != av_frame_get_channels(avframe))
|
||||
mp_chmap_from_channels(&lavc_chmap, av_frame_get_channels(avframe));
|
||||
#endif
|
||||
|
||||
new->rate = avframe->sample_rate;
|
||||
@@ -386,12 +403,9 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Returns NULL on failure. The input is always unreffed.
|
||||
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 AVFrame *avframe = av_frame_alloc();
|
||||
if (!avframe)
|
||||
goto fail;
|
||||
av_frame_unref(avframe);
|
||||
|
||||
avframe->nb_samples = frame->samples;
|
||||
avframe->format = af_to_avformat(frame->format);
|
||||
@@ -402,8 +416,8 @@ struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
if (!avframe->channel_layout)
|
||||
goto fail;
|
||||
#if LIBAVUTIL_VERSION_MICRO >= 100
|
||||
// FFmpeg being a stupid POS (but I respect it)
|
||||
avframe->channels = frame->channels.num;
|
||||
// FFmpeg being a stupid POS again
|
||||
av_frame_set_channels(avframe, frame->channels.num);
|
||||
#endif
|
||||
avframe->sample_rate = frame->rate;
|
||||
|
||||
@@ -449,6 +463,23 @@ struct AVFrame *mp_audio_to_avframe_and_unref(struct mp_audio *frame)
|
||||
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;
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
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);
|
||||
@@ -80,6 +81,7 @@ 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_audio_pool;
|
||||
struct mp_audio_pool *mp_audio_pool_create(void *ta_parent);
|
||||
|
||||
@@ -230,44 +230,6 @@ void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels)
|
||||
}
|
||||
}
|
||||
|
||||
// Return channel index of the given speaker, or -1.
|
||||
static int mp_chmap_find_speaker(const struct mp_chmap *map, int speaker)
|
||||
{
|
||||
for (int n = 0; n < map->num; n++) {
|
||||
if (map->speaker[n] == speaker)
|
||||
return n;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void mp_chmap_remove_speaker(struct mp_chmap *map, int speaker)
|
||||
{
|
||||
int index = mp_chmap_find_speaker(map, speaker);
|
||||
if (index >= 0) {
|
||||
for (int n = index; n < map->num - 1; n++)
|
||||
map->speaker[n] = map->speaker[n + 1];
|
||||
map->num--;
|
||||
}
|
||||
}
|
||||
|
||||
// Some decoders output additional, redundant channels, which are usually
|
||||
// useless and will mess up proper audio output channel handling.
|
||||
// map: channel map from which the channels should be removed
|
||||
// requested: if not NULL, and if it contains any of the "useless" channels,
|
||||
// don't remove them (this is for convenience)
|
||||
void mp_chmap_remove_useless_channels(struct mp_chmap *map,
|
||||
const struct mp_chmap *requested)
|
||||
{
|
||||
if (requested &&
|
||||
mp_chmap_find_speaker(requested, MP_SPEAKER_ID_DL) >= 0)
|
||||
return;
|
||||
|
||||
if (map->num > 2) {
|
||||
mp_chmap_remove_speaker(map, MP_SPEAKER_ID_DL);
|
||||
mp_chmap_remove_speaker(map, MP_SPEAKER_ID_DR);
|
||||
}
|
||||
}
|
||||
|
||||
// Return the ffmpeg/libav channel layout as in <libavutil/channel_layout.h>.
|
||||
// Speakers not representable by ffmpeg/libav are dropped.
|
||||
// Warning: this ignores the order of the channels, and will return a channel
|
||||
|
||||
@@ -109,9 +109,6 @@ void mp_chmap_fill_na(struct mp_chmap *map, int num);
|
||||
void mp_chmap_from_channels(struct mp_chmap *dst, int num_channels);
|
||||
void mp_chmap_set_unknown(struct mp_chmap *dst, int num_channels);
|
||||
|
||||
void mp_chmap_remove_useless_channels(struct mp_chmap *map,
|
||||
const struct mp_chmap *requested);
|
||||
|
||||
uint64_t mp_chmap_to_lavc(const struct mp_chmap *src);
|
||||
uint64_t mp_chmap_to_lavc_unchecked(const struct mp_chmap *src);
|
||||
void mp_chmap_from_lavc(struct mp_chmap *dst, uint64_t src);
|
||||
|
||||
@@ -374,3 +374,16 @@ void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev)
|
||||
if (s->allow_any)
|
||||
mp_msg(log, lev, " - anything\n");
|
||||
}
|
||||
|
||||
// Select a channel map from the given list that fits best to c. Don't change
|
||||
// *c if there's no match, or the list is empty.
|
||||
void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps)
|
||||
{
|
||||
// This is a separate function to keep messing with mp_chmap_sel internals
|
||||
// within this source file.
|
||||
struct mp_chmap_sel sel = {
|
||||
.chmaps = maps,
|
||||
.num_chmaps = num_maps,
|
||||
};
|
||||
mp_chmap_sel_fallback(&sel, c);
|
||||
}
|
||||
@@ -47,4 +47,6 @@ bool mp_chmap_sel_get_def(const struct mp_chmap_sel *s, struct mp_chmap *map,
|
||||
struct mp_log;
|
||||
void mp_chmal_sel_log(const struct mp_chmap_sel *s, struct mp_log *log, int lev);
|
||||
|
||||
void mp_chmap_sel_list(struct mp_chmap *c, struct mp_chmap *maps, int num_maps);
|
||||
|
||||
#endif
|
||||
+17
-3
@@ -45,6 +45,7 @@ struct priv {
|
||||
uint32_t skip_samples, trim_samples;
|
||||
bool preroll_done;
|
||||
double next_pts;
|
||||
bool needs_reset;
|
||||
AVRational codec_timebase;
|
||||
};
|
||||
|
||||
@@ -85,7 +86,7 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
da->priv = ctx;
|
||||
|
||||
ctx->codec_timebase = (AVRational){0};
|
||||
ctx->codec_timebase = mp_get_codec_timebase(da->codec);
|
||||
|
||||
ctx->force_channel_map = c->force_channels;
|
||||
|
||||
@@ -102,10 +103,11 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
lavc_context->refcounted_frames = 1;
|
||||
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
lavc_context->codec_id = lavc_codec->id;
|
||||
lavc_context->time_base = ctx->codec_timebase;
|
||||
|
||||
if (opts->downmix) {
|
||||
if (opts->downmix && mpopts->audio_output_channels.num_chmaps == 1) {
|
||||
lavc_context->request_channel_layout =
|
||||
mp_chmap_to_lavc(&mpopts->audio_output_channels);
|
||||
mp_chmap_to_lavc(&mpopts->audio_output_channels.chmaps[0]);
|
||||
}
|
||||
|
||||
// Always try to set - option only exists for AC3 at the moment
|
||||
@@ -173,6 +175,7 @@ static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
ctx->trim_samples = 0;
|
||||
ctx->preroll_done = false;
|
||||
ctx->next_pts = MP_NOPTS_VALUE;
|
||||
ctx->needs_reset = false;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
@@ -184,6 +187,12 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
// If the decoder discards the timestamp for some reason, we use the
|
||||
// interpolated PTS. Initialize it so that it works for the initial
|
||||
// packet as well.
|
||||
if (mpkt && priv->next_pts == MP_NOPTS_VALUE)
|
||||
priv->next_pts = mpkt->pts;
|
||||
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
@@ -192,6 +201,9 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
int got_frame = 0;
|
||||
av_frame_unref(priv->avframe);
|
||||
|
||||
if (priv->needs_reset)
|
||||
control(da, ADCTRL_RESET, NULL);
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
int ret = avcodec_send_packet(avctx, &pkt);
|
||||
if (ret >= 0 || ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
|
||||
@@ -200,6 +212,8 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
ret = avcodec_receive_frame(avctx, priv->avframe);
|
||||
if (ret >= 0)
|
||||
got_frame = 1;
|
||||
if (ret == AVERROR_EOF)
|
||||
priv->needs_reset = true;
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
@@ -116,9 +116,16 @@ static int determine_codec_profile(struct dec_audio *da, AVPacket *pkt)
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
if (avcodec_send_packet(ctx, pkt) < 0)
|
||||
goto done;
|
||||
if (avcodec_receive_frame(ctx, frame) < 0)
|
||||
goto done;
|
||||
#else
|
||||
int got_frame = 0;
|
||||
if (avcodec_decode_audio4(ctx, frame, &got_frame, pkt) < 1 || !got_frame)
|
||||
goto done;
|
||||
#endif
|
||||
|
||||
profile = ctx->profile;
|
||||
|
||||
|
||||
@@ -200,7 +200,9 @@ void audio_work(struct dec_audio *da)
|
||||
if (da->current_frame)
|
||||
return;
|
||||
|
||||
if (!da->packet && demux_read_packet_async(da->header, &da->packet) == 0) {
|
||||
if (!da->packet && !da->new_segment &&
|
||||
demux_read_packet_async(da->header, &da->packet) == 0)
|
||||
{
|
||||
da->current_state = DATA_WAIT;
|
||||
return;
|
||||
}
|
||||
@@ -211,6 +213,7 @@ void audio_work(struct dec_audio *da)
|
||||
da->packet = NULL;
|
||||
}
|
||||
|
||||
bool had_input_packet = !!da->packet;
|
||||
bool had_packet = da->packet || da->new_segment;
|
||||
|
||||
int ret = da->ad_driver->decode_packet(da, da->packet, &da->current_frame);
|
||||
@@ -233,12 +236,12 @@ void audio_work(struct dec_audio *da)
|
||||
|
||||
fix_audio_pts(da);
|
||||
|
||||
bool segment_end = true;
|
||||
bool segment_end = !da->current_frame && !had_input_packet;
|
||||
|
||||
if (da->current_frame) {
|
||||
mp_audio_clip_timestamps(da->current_frame, da->start, da->end);
|
||||
if (da->current_frame->pts != MP_NOPTS_VALUE && da->start != MP_NOPTS_VALUE)
|
||||
segment_end = da->current_frame->pts >= da->start;
|
||||
segment_end = da->current_frame->pts >= da->end;
|
||||
if (da->current_frame->samples == 0) {
|
||||
talloc_free(da->current_frame);
|
||||
da->current_frame = NULL;
|
||||
|
||||
+151
-174
@@ -31,7 +31,6 @@
|
||||
#include "af.h"
|
||||
|
||||
// Static list of filters
|
||||
extern const struct af_info af_info_delay;
|
||||
extern const struct af_info af_info_channels;
|
||||
extern const struct af_info af_info_format;
|
||||
extern const struct af_info af_info_volume;
|
||||
@@ -46,7 +45,6 @@ extern const struct af_info af_info_lavfi;
|
||||
extern const struct af_info af_info_rubberband;
|
||||
|
||||
static const struct af_info *const filter_list[] = {
|
||||
&af_info_delay,
|
||||
&af_info_channels,
|
||||
&af_info_format,
|
||||
&af_info_volume,
|
||||
@@ -210,29 +208,6 @@ static struct af_instance *af_prepend(struct af_stream *s,
|
||||
return new;
|
||||
}
|
||||
|
||||
/* Create and insert a new filter of type name after the filter in the
|
||||
argument. This function can be called during runtime, the return
|
||||
value is the new filter */
|
||||
static struct af_instance *af_append(struct af_stream *s,
|
||||
struct af_instance *af,
|
||||
char *name, char **args)
|
||||
{
|
||||
if (!af)
|
||||
af = s->first;
|
||||
if (af == s->last)
|
||||
af = s->last->prev;
|
||||
// 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->prev = af;
|
||||
new->next = af->next;
|
||||
af->next = new;
|
||||
new->next->prev = new;
|
||||
return new;
|
||||
}
|
||||
|
||||
// Uninit and remove the filter "af"
|
||||
static void af_remove(struct af_stream *s, struct af_instance *af)
|
||||
{
|
||||
@@ -275,8 +250,12 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
while (af) {
|
||||
char b[128] = {0};
|
||||
mp_snprintf_cat(b, sizeof(b), " [%s] ", af->info->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);
|
||||
@@ -287,169 +266,156 @@ static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
MP_MSG(s, msg_level, " [ao] %s\n", mp_audio_config_to_str(&s->output));
|
||||
}
|
||||
|
||||
static bool af_is_conversion_filter(struct af_instance *af)
|
||||
{
|
||||
return af && strcmp(af->info->name, "lavrresample") == 0;
|
||||
}
|
||||
|
||||
// in is what af can take as input - insert a conversion filter if the actual
|
||||
// input format doesn't match what af expects.
|
||||
// Returns:
|
||||
// AF_OK: must call af_reinit() or equivalent, format matches (or is closer)
|
||||
// AF_FALSE: nothing was changed, format matches
|
||||
// else: error
|
||||
static int af_fix_format_conversion(struct af_stream *s,
|
||||
struct af_instance **p_af,
|
||||
struct mp_audio in)
|
||||
{
|
||||
int rv;
|
||||
struct af_instance *af = *p_af;
|
||||
struct af_instance *prev = af->prev;
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (actual.format == in.format)
|
||||
return AF_FALSE;
|
||||
int dstfmt = in.format;
|
||||
char *filter = "lavrresample";
|
||||
if (!af_lavrresample_test_conversion(actual.format, dstfmt))
|
||||
return AF_ERROR;
|
||||
if (strcmp(filter, prev->info->name) == 0) {
|
||||
if (prev->control(prev, AF_CONTROL_SET_FORMAT, &dstfmt) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_ERROR;
|
||||
}
|
||||
struct af_instance *new = af_prepend(s, af, filter, NULL);
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_FORMAT, &dstfmt))) {
|
||||
af_remove(s, new);
|
||||
return rv;
|
||||
}
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// same as af_fix_format_conversion - only wrt. channels
|
||||
static int af_fix_channels(struct af_stream *s, struct af_instance **p_af,
|
||||
struct mp_audio in)
|
||||
{
|
||||
int rv;
|
||||
struct af_instance *af = *p_af;
|
||||
struct af_instance *prev = af->prev;
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (mp_chmap_equals(&actual.channels, &in.channels))
|
||||
return AF_FALSE;
|
||||
if (prev->control(prev, AF_CONTROL_SET_CHANNELS, &in.channels) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
char *filter = "lavrresample";
|
||||
struct af_instance *new = af_prepend(s, af, filter, NULL);
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_CHANNELS, &in.channels)))
|
||||
return rv;
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
static int af_fix_rate(struct af_stream *s, struct af_instance **p_af,
|
||||
struct mp_audio in)
|
||||
{
|
||||
int rv;
|
||||
struct af_instance *af = *p_af;
|
||||
struct af_instance *prev = af->prev;
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (actual.rate == in.rate)
|
||||
return AF_FALSE;
|
||||
if (prev->control(prev, AF_CONTROL_SET_RESAMPLE_RATE, &in.rate) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
char *filter = "lavrresample";
|
||||
struct af_instance *new = af_prepend(s, af, filter, NULL);
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_RESAMPLE_RATE, &in.rate)))
|
||||
return rv;
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
static void reset_formats(struct af_stream *s)
|
||||
{
|
||||
struct mp_audio none = {0};
|
||||
for (struct af_instance *af = s->first; af; af = af->next) {
|
||||
af->control(af, AF_CONTROL_SET_RESAMPLE_RATE, &(int){0});
|
||||
af->control(af, AF_CONTROL_SET_CHANNELS, &(struct mp_chmap){0});
|
||||
af->control(af, AF_CONTROL_SET_FORMAT, &(int){0});
|
||||
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.
|
||||
// Warning:
|
||||
// A failed af_reinit() leaves the audio chain behind in a useless, broken
|
||||
// state (for example, format filters that were tentatively inserted stay
|
||||
// inserted).
|
||||
// In that case, you should always rebuild the filter chain, or abort.
|
||||
static int af_reinit(struct af_stream *s)
|
||||
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;
|
||||
// Up to 4 retries per filter (channel, rate, format conversions)
|
||||
int max_retry = 4;
|
||||
int retry = 0;
|
||||
while (af) {
|
||||
if (retry >= max_retry)
|
||||
goto negotiate_error;
|
||||
int rv = filter_reinit_with_conversion(s, af);
|
||||
|
||||
// Check if this is the first filter
|
||||
struct mp_audio in = *af->prev->data;
|
||||
// Reset just in case...
|
||||
mp_audio_set_null_data(&in);
|
||||
|
||||
if (!mp_audio_config_valid(&in))
|
||||
goto error;
|
||||
|
||||
af->fmt_in = in;
|
||||
int rv = af->control(af, AF_CONTROL_REINIT, &in);
|
||||
if (rv == AF_OK && !mp_audio_config_equals(&in, af->prev->data))
|
||||
rv = AF_FALSE; // conversion filter needed
|
||||
switch (rv) {
|
||||
case AF_OK:
|
||||
if (!mp_audio_config_valid(af->data))
|
||||
goto error;
|
||||
af->fmt_out = *af->data;
|
||||
af = af->next;
|
||||
break;
|
||||
case AF_FALSE: { // Configuration filter is needed
|
||||
if (af_fix_channels(s, &af, in) == AF_OK) {
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
if (af_fix_rate(s, &af, in) == AF_OK) {
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
// Do this last, to prevent "format->lavrresample" being added to
|
||||
// the filter chain when output formats not supported by
|
||||
// af_lavrresample are in use.
|
||||
if (af_fix_format_conversion(s, &af, in) == AF_OK) {
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
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 = in.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);
|
||||
@@ -460,7 +426,6 @@ static int af_reinit(struct af_stream *s)
|
||||
struct af_instance *aft = af->prev;
|
||||
af_remove(s, af);
|
||||
af = aft->next;
|
||||
retry++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -478,8 +443,6 @@ static int af_reinit(struct af_stream *s)
|
||||
af->info->name, rv);
|
||||
goto error;
|
||||
}
|
||||
if (af && !af->auto_inserted)
|
||||
retry = 0;
|
||||
}
|
||||
|
||||
/* Set previously unset fields in s->output to those of the filter chain
|
||||
@@ -503,6 +466,19 @@ error:
|
||||
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)
|
||||
{
|
||||
@@ -557,7 +533,7 @@ void af_destroy(struct af_stream *s)
|
||||
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 prefered output parameters is 0 the one that needs
|
||||
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)
|
||||
@@ -602,12 +578,7 @@ struct af_instance *af_add(struct af_stream *s, char *name, char *label,
|
||||
if (af_find_by_label(s, label))
|
||||
return NULL;
|
||||
|
||||
struct af_instance *new;
|
||||
// Insert the filter somewhere nice
|
||||
if (af_is_conversion_filter(s->first->next))
|
||||
new = af_append(s, s->first->next, name, args);
|
||||
else
|
||||
new = af_prepend(s, s->first->next, name, args);
|
||||
struct af_instance *new = af_prepend(s, s->last, name, args);
|
||||
if (!new)
|
||||
return NULL;
|
||||
new->label = talloc_strdup(new, label);
|
||||
@@ -808,6 +779,12 @@ struct mp_audio *af_read_output_frame(struct af_stream *s)
|
||||
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)
|
||||
|
||||
+1
-5
@@ -112,9 +112,6 @@ struct af_stream {
|
||||
enum af_control {
|
||||
AF_CONTROL_REINIT = 1,
|
||||
AF_CONTROL_RESET,
|
||||
AF_CONTROL_SET_RESAMPLE_RATE,
|
||||
AF_CONTROL_SET_FORMAT,
|
||||
AF_CONTROL_SET_CHANNELS,
|
||||
AF_CONTROL_SET_VOLUME,
|
||||
AF_CONTROL_GET_VOLUME,
|
||||
AF_CONTROL_SET_PAN_LEVEL,
|
||||
@@ -151,6 +148,7 @@ 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);
|
||||
@@ -160,6 +158,4 @@ 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);
|
||||
|
||||
bool af_lavrresample_test_conversion(int src_format, int dst_format);
|
||||
|
||||
#endif /* MPLAYER_AF_H */
|
||||
@@ -1,194 +0,0 @@
|
||||
/*
|
||||
* This audio filter delays the output signal for the different
|
||||
* channels and can be used for simple position panning.
|
||||
* An extension for this filter would be a reverb.
|
||||
*
|
||||
* Original author: Anders
|
||||
*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define L 65536
|
||||
|
||||
#define UPDATEQI(qi) qi=(qi+1)&(L-1)
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_delay_s
|
||||
{
|
||||
void* q[AF_NCH]; // Circular queues used for delaying audio signal
|
||||
int wi[AF_NCH]; // Write index
|
||||
int ri; // Read index
|
||||
float d[AF_NCH]; // Delay [ms]
|
||||
char *delaystr;
|
||||
}af_delay_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_delay_t* s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
int i;
|
||||
struct mp_audio *in = arg;
|
||||
|
||||
if (in->bps != 1 && in->bps != 2 && in->bps != 4) {
|
||||
MP_FATAL(af, "Sample format not supported\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
// Free prevous delay queues
|
||||
for(i=0;i<af->data->nch;i++)
|
||||
free(s->q[i]);
|
||||
|
||||
mp_audio_force_interleaved_format(in);
|
||||
mp_audio_copy_config(af->data, in);
|
||||
|
||||
// Allocate new delay queues
|
||||
for(i=0;i<af->data->nch;i++){
|
||||
s->q[i] = calloc(L,af->data->bps);
|
||||
if(NULL == s->q[i])
|
||||
MP_FATAL(af, "Out of memory\n");
|
||||
}
|
||||
|
||||
if(AF_OK != af_from_ms(AF_NCH, s->d, s->wi, af->data->rate, 0.0, 1000.0))
|
||||
return AF_ERROR;
|
||||
s->ri = 0;
|
||||
for(i=0;i<AF_NCH;i++){
|
||||
MP_DBG(af, "Channel %i delayed by %0.3fms\n",
|
||||
i,MPCLAMP(s->d[i],0.0,1000.0));
|
||||
MP_TRACE(af, "Channel %i delayed by %i samples\n",
|
||||
i,s->wi[i]);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i=0;i<AF_NCH;i++)
|
||||
free(((af_delay_t*)(af->priv))->q[i]);
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
if (!c)
|
||||
return 0;
|
||||
af_delay_t* s = af->priv; // Setup for this instance
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = mp_audio_psize(c)/c->bps; // Number of sample in data chunk
|
||||
int ri = 0;
|
||||
int ch,i;
|
||||
if (af_make_writeable(af, c) < 0) {
|
||||
talloc_free(c);
|
||||
return -1;
|
||||
}
|
||||
for(ch=0;ch<nch;ch++){
|
||||
switch(c->bps){
|
||||
case 1:{
|
||||
int8_t* a = c->planes[0];
|
||||
int8_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
for(i=ch;i<len;i+=nch){
|
||||
q[wi] = a[i];
|
||||
a[i] = q[ri];
|
||||
UPDATEQI(wi);
|
||||
UPDATEQI(ri);
|
||||
}
|
||||
s->wi[ch] = wi;
|
||||
break;
|
||||
}
|
||||
case 2:{
|
||||
int16_t* a = c->planes[0];
|
||||
int16_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
for(i=ch;i<len;i+=nch){
|
||||
q[wi] = a[i];
|
||||
a[i] = q[ri];
|
||||
UPDATEQI(wi);
|
||||
UPDATEQI(ri);
|
||||
}
|
||||
s->wi[ch] = wi;
|
||||
break;
|
||||
}
|
||||
case 4:{
|
||||
int32_t* a = c->planes[0];
|
||||
int32_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
for(i=ch;i<len;i+=nch){
|
||||
q[wi] = a[i];
|
||||
a[i] = q[ri];
|
||||
UPDATEQI(wi);
|
||||
UPDATEQI(ri);
|
||||
}
|
||||
s->wi[ch] = wi;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
s->ri = ri;
|
||||
af_add_output_frame(af, c);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter_frame = filter_frame;
|
||||
af_delay_t *s = af->priv;
|
||||
int n = 1;
|
||||
int i = 0;
|
||||
char* cl = s->delaystr;
|
||||
while(cl && n && i < AF_NCH ){
|
||||
sscanf(cl,"%f%n",&s->d[i],&n);
|
||||
if(n==0 || cl[n-1] == '\0')
|
||||
break;
|
||||
cl=&cl[n];
|
||||
if (*cl != ',')
|
||||
break;
|
||||
cl++;
|
||||
i++;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_delay_t
|
||||
const struct af_info af_info_delay = {
|
||||
.info = "Delay audio filter",
|
||||
.name = "delay",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_delay_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("delays", delaystr, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -29,10 +29,10 @@ struct priv {
|
||||
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct mp_chmap in_channels;
|
||||
struct m_channels in_channels;
|
||||
int out_format;
|
||||
int out_srate;
|
||||
struct mp_chmap out_channels;
|
||||
struct m_channels out_channels;
|
||||
|
||||
int fail;
|
||||
};
|
||||
@@ -44,8 +44,8 @@ static void force_in_params(struct af_instance *af, struct mp_audio *in)
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
|
||||
if (priv->in_channels.num)
|
||||
mp_audio_set_channels(in, &priv->in_channels);
|
||||
if (priv->in_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(in, &priv->in_channels.chmaps[0]);
|
||||
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
@@ -58,8 +58,8 @@ static void force_out_params(struct af_instance *af, struct mp_audio *out)
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
|
||||
if (priv->out_channels.num)
|
||||
mp_audio_set_channels(out, &priv->out_channels);
|
||||
if (priv->out_channels.num_chmaps > 0)
|
||||
mp_audio_set_channels(out, &priv->out_channels.chmaps[0]);
|
||||
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
@@ -124,10 +124,10 @@ const struct af_info af_info_format = {
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHMAP("channels", in_channels, CONF_MIN, .min = 0),
|
||||
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_CHMAP("out-channels", out_channels, CONF_MIN, .min = 0),
|
||||
OPT_CHANNELS("out-channels", out_channels, 0, .min = 1),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
|
||||
+152
-54
@@ -31,9 +31,13 @@
|
||||
#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/chmap_sel.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
|
||||
@@ -54,13 +58,52 @@ typedef struct af_ac3enc_s {
|
||||
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
|
||||
int in_sampleformat;
|
||||
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)
|
||||
{
|
||||
int formats[AF_FORMAT_COUNT];
|
||||
af_get_best_sample_formats(fmt->format, formats);
|
||||
|
||||
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: ;
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -76,23 +119,17 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (!af_fmt_is_pcm(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
mp_audio_set_format(in, s->in_sampleformat);
|
||||
// At least currently, the AC3 encoder doesn't export sample rates.
|
||||
in->rate = 48000;
|
||||
select_encode_format(s->lavc_actx, in);
|
||||
|
||||
if (in->rate != 48000 && in->rate != 44100 && in->rate != 32000)
|
||||
in->rate = 48000;
|
||||
af->data->rate = in->rate;
|
||||
|
||||
mp_chmap_reorder_to_lavc(&in->channels);
|
||||
if (in->nch > AC3_MAX_CHANNELS)
|
||||
mp_audio_set_num_channels(in, AC3_MAX_CHANNELS);
|
||||
|
||||
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;
|
||||
|
||||
s->in_samples = AC3_FRAME_SIZE;
|
||||
if (s->cfg_add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
@@ -100,14 +137,11 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
}
|
||||
|
||||
mp_audio_copy_config(s->input, in);
|
||||
mp_audio_realloc(s->input, s->in_samples);
|
||||
s->input->samples = 0;
|
||||
|
||||
talloc_free(s->pending);
|
||||
s->pending = NULL;
|
||||
|
||||
MP_DBG(af, "af_lavcac3enc reinit: %d, %d, %d.\n",
|
||||
in->nch, in->rate, s->in_samples);
|
||||
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];
|
||||
|
||||
@@ -118,6 +152,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
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;
|
||||
@@ -127,14 +162,26 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (s->lavc_actx->frame_size != AC3_FRAME_SIZE) {
|
||||
MP_ERR(af, "lavcac3enc: unexpected ac3 "
|
||||
"encoder frame size %d\n", s->lavc_actx->frame_size);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
@@ -156,8 +203,8 @@ static void uninit(struct af_instance* af)
|
||||
static void update_delay(struct af_instance *af)
|
||||
{
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples) /
|
||||
(double)s->input->rate;
|
||||
af->delay = ((s->pending ? s->pending->samples : 0) + s->input->samples +
|
||||
s->encoder_buffered) / (double)s->input->rate;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *audio)
|
||||
@@ -190,6 +237,9 @@ static bool fill_buffer(struct af_instance *af)
|
||||
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);
|
||||
@@ -199,12 +249,27 @@ static bool fill_buffer(struct af_instance *af)
|
||||
return s->input->samples >= s->in_samples;
|
||||
}
|
||||
|
||||
static int filter_out(struct af_instance *af)
|
||||
// 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");
|
||||
@@ -212,29 +277,59 @@ static int filter_out(struct af_instance *af)
|
||||
}
|
||||
int err = -1;
|
||||
|
||||
frame->nb_samples = s->in_samples;
|
||||
frame->format = s->lavc_actx->sample_fmt;
|
||||
frame->channel_layout = s->lavc_actx->channel_layout;
|
||||
assert(s->input->num_planes <= AV_NUM_DATA_POINTERS);
|
||||
frame->extended_data = frame->data;
|
||||
for (int n = 0; n < s->input->num_planes; n++)
|
||||
frame->data[n] = s->input->planes[n];
|
||||
frame->linesize[0] = s->input->samples * s->input->sstride;
|
||||
|
||||
AVPacket pkt = {0};
|
||||
av_init_packet(&pkt);
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
// Send input as long as it wants.
|
||||
while (1) {
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
if (err == 0)
|
||||
break;
|
||||
err = -1;
|
||||
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");
|
||||
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;
|
||||
}
|
||||
#else
|
||||
err = read_input_frame(af, frame);
|
||||
if (err < 0)
|
||||
goto done;
|
||||
if (err == 0)
|
||||
goto done;
|
||||
err = -1;
|
||||
int ok;
|
||||
int lavc_ret = avcodec_encode_audio2(s->lavc_actx, &pkt, frame, &ok);
|
||||
av_frame_free(&frame);
|
||||
s->input->samples = 0;
|
||||
if (lavc_ret < 0 || !ok) {
|
||||
MP_FATAL(af, "Encode failed.\n");
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
pkt.size, s->pending->samples);
|
||||
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);
|
||||
@@ -271,11 +366,12 @@ static int filter_out(struct af_instance *af)
|
||||
swap_16((uint16_t *)(buf + header_len), pkt.size / 2);
|
||||
out->samples = frame_size / out->sstride;
|
||||
af_add_output_frame(af, out);
|
||||
update_delay(af);
|
||||
|
||||
err = 0;
|
||||
done:
|
||||
av_packet_unref(&pkt);
|
||||
av_frame_free(&frame);
|
||||
update_delay(af);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -287,9 +383,9 @@ static int af_open(struct af_instance* af){
|
||||
af->filter_frame = filter_frame;
|
||||
af->filter_out = filter_out;
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name("ac3");
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name(s->cfg_encoder);
|
||||
if (!s->lavc_acodec) {
|
||||
MP_ERR(af, "Audio LAVC, couldn't find encoder for codec %s.\n", "ac3");
|
||||
MP_ERR(af, "Couldn't find encoder %s.\n", s->cfg_encoder);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
@@ -298,35 +394,34 @@ static int af_open(struct af_instance* af){
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
const enum AVSampleFormat *fmts = s->lavc_acodec->sample_fmts;
|
||||
for (int i = 0; fmts[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
s->in_sampleformat = af_from_avformat(fmts[i]);
|
||||
if (s->in_sampleformat) {
|
||||
s->lavc_actx->sample_fmt = fmts[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!s->in_sampleformat) {
|
||||
MP_ERR(af, "Audio LAVC, encoder doesn't "
|
||||
"support expected sample formats!\n");
|
||||
|
||||
if (mp_set_avopts(af->log, s->lavc_actx, s->cfg_avopts) < 0)
|
||||
return AF_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
|
||||
// in do.)
|
||||
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_VERBOSE(af, "[af_lavcac3enc]: in sample format: %s\n",
|
||||
af_fmt_to_str(s->in_sampleformat));
|
||||
|
||||
s->input = talloc_zero(s, struct mp_audio);
|
||||
|
||||
if (s->cfg_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->cfg_bit_rate) {
|
||||
s->bit_rate = ac3_bitrate_tab[i] * 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= 19) {
|
||||
MP_WARN(af, "af_lavcac3enc unable set unsupported "
|
||||
"bitrate %d, use default bitrate (check manpage to see "
|
||||
"supported bitrates).\n", s->cfg_bit_rate);
|
||||
s->cfg_bit_rate = 0;
|
||||
MP_WARN(af, "unable set unsupported bitrate %d, use default "
|
||||
"bitrate (check manpage to see supported bitrates).\n",
|
||||
s->cfg_bit_rate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,12 +439,15 @@ const struct af_info af_info_lavcac3enc = {
|
||||
.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}
|
||||
},
|
||||
};
|
||||
@@ -99,10 +99,6 @@ static double get_delay(struct af_resample *s)
|
||||
return avresample_get_delay(s->avrctx) / (double)s->in_rate +
|
||||
avresample_available(s->avrctx) / (double)s->out_rate;
|
||||
}
|
||||
static void drop_all_output(struct af_resample *s)
|
||||
{
|
||||
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
return avresample_get_out_samples(s->avrctx, in_samples);
|
||||
@@ -113,10 +109,6 @@ static double get_delay(struct af_resample *s)
|
||||
int64_t base = s->in_rate * (int64_t)s->out_rate;
|
||||
return swr_get_delay(s->avrctx, base) / (double)base;
|
||||
}
|
||||
static void drop_all_output(struct af_resample *s)
|
||||
{
|
||||
while (swr_drop_output(s->avrctx, 1000) > 0) {}
|
||||
}
|
||||
static int get_out_samples(struct af_resample *s, int in_samples)
|
||||
{
|
||||
#if LIBSWRESAMPLE_VERSION_MAJOR > 1 || LIBSWRESAMPLE_VERSION_MINOR >= 2
|
||||
@@ -173,12 +165,6 @@ static int check_output_conversion(int mp_format)
|
||||
return af_to_avformat(mp_format);
|
||||
}
|
||||
|
||||
bool af_lavrresample_test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
return af_to_avformat(src_format) != AV_SAMPLE_FMT_NONE &&
|
||||
check_output_conversion(dst_format) != AV_SAMPLE_FMT_NONE;
|
||||
}
|
||||
|
||||
static struct mp_chmap fudge_pairs[][2] = {
|
||||
{MP_CHMAP2(BL, BR), MP_CHMAP2(SL, SR)},
|
||||
{MP_CHMAP2(SL, SR), MP_CHMAP2(BL, BR)},
|
||||
@@ -407,28 +393,22 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
r = configure_lavrr(af, in, out, true);
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_SET_FORMAT: {
|
||||
int format = *(int *)arg;
|
||||
if (format && check_output_conversion(format) == AV_SAMPLE_FMT_NONE)
|
||||
return AF_FALSE;
|
||||
|
||||
mp_audio_set_format(af->data, format);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SET_CHANNELS: {
|
||||
mp_audio_set_channels(af->data, (struct mp_chmap *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SET_RESAMPLE_RATE:
|
||||
af->data->rate = *(int *)arg;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED_RESAMPLE: {
|
||||
s->playback_speed = *(double *)arg;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESET:
|
||||
if (s->avrctx)
|
||||
drop_all_output(s);
|
||||
if (s->avrctx) {
|
||||
#if HAVE_LIBSWRESAMPLE
|
||||
swr_close(s->avrctx);
|
||||
if (swr_init(s->avrctx) < 0) {
|
||||
close_lavrr(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
#else
|
||||
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
|
||||
#endif
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
@@ -500,19 +480,20 @@ static void reorder_planes(struct mp_audio *mpa, int *reorder,
|
||||
static int filter_resample(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
struct mp_audio *out = NULL;
|
||||
|
||||
if (!s->avrctx)
|
||||
goto error;
|
||||
|
||||
int samples = get_out_samples(s, in ? in->samples : 0);
|
||||
|
||||
struct mp_audio out_format = s->pool_fmt;
|
||||
struct mp_audio *out = mp_audio_pool_get(af->out_pool, &out_format, samples);
|
||||
out = mp_audio_pool_get(af->out_pool, &out_format, samples);
|
||||
if (!out)
|
||||
goto error;
|
||||
if (in)
|
||||
mp_audio_copy_attributes(out, in);
|
||||
|
||||
if (!s->avrctx)
|
||||
goto error;
|
||||
|
||||
if (out->samples) {
|
||||
out->samples = resample_frame(s->avrctx, out, in);
|
||||
if (out->samples < 0)
|
||||
|
||||
@@ -71,15 +71,19 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (af_fmt_is_planar(in->format))
|
||||
mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
|
||||
s->rgain = 1.0;
|
||||
if ((s->rgain_track || s->rgain_album) && af->replaygain_data) {
|
||||
float gain, peak;
|
||||
struct replaygain_data *rg = af->replaygain_data;
|
||||
if ((s->rgain_track || s->rgain_album) && rg) {
|
||||
MP_VERBOSE(af, "Replaygain: Track=%f/%f Album=%f/%f\n",
|
||||
rg->track_gain, rg->track_peak,
|
||||
rg->album_gain, rg->album_peak);
|
||||
|
||||
float gain, peak;
|
||||
if (s->rgain_track) {
|
||||
gain = af->replaygain_data->track_gain;
|
||||
peak = af->replaygain_data->track_peak;
|
||||
gain = rg->track_gain;
|
||||
peak = rg->track_peak;
|
||||
} else {
|
||||
gain = af->replaygain_data->album_gain;
|
||||
peak = af->replaygain_data->album_peak;
|
||||
gain = rg->album_gain;
|
||||
peak = rg->album_peak;
|
||||
}
|
||||
|
||||
gain += s->rgain_preamp;
|
||||
@@ -115,7 +119,7 @@ static void filter_plane(struct af_instance *af, struct mp_audio *data, int p)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
|
||||
float level = s->level * s->rgain;
|
||||
float level = s->level * s->rgain * from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
|
||||
int num_samples = data->samples * data->spf;
|
||||
|
||||
if (af_fmt_from_planar(af->data->format) == AF_FORMAT_S16) {
|
||||
@@ -158,7 +162,7 @@ static int af_open(struct af_instance *af)
|
||||
struct priv *s = af->priv;
|
||||
af->control = control;
|
||||
af->filter_frame = filter;
|
||||
s->level = from_dB(s->cfg_volume, 20.0, -200.0, 60.0);
|
||||
s->level = 1.0;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
|
||||
-403
@@ -1,403 +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 <string.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "mixer.h"
|
||||
|
||||
struct mixer {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct ao *ao;
|
||||
struct af_stream *af;
|
||||
// Static, dependent on ao/softvol settings
|
||||
bool softvol; // use AO (false) or af_volume (true)
|
||||
bool persistent_volume; // volume does not need to be restored
|
||||
bool emulate_mute; // if true, emulate mute with volume=0
|
||||
// Last known values (possibly out of sync with reality)
|
||||
float vol_l, vol_r;
|
||||
bool muted;
|
||||
// Used to decide whether we should unmute on uninit
|
||||
bool muted_by_us;
|
||||
/* Contains ao driver name or "softvol" if volume is not persistent
|
||||
* and needs to be restored after the driver is reinitialized. */
|
||||
const char *driver;
|
||||
// Other stuff
|
||||
float balance;
|
||||
};
|
||||
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global)
|
||||
{
|
||||
struct mixer *mixer = talloc_ptrtype(talloc_ctx, mixer);
|
||||
*mixer = (struct mixer) {
|
||||
.log = mp_log_new(mixer, global->log, "mixer"),
|
||||
.opts = global->opts,
|
||||
.vol_l = 100,
|
||||
.vol_r = 100,
|
||||
.driver = "",
|
||||
};
|
||||
return mixer;
|
||||
}
|
||||
|
||||
bool mixer_audio_initialized(struct mixer *mixer)
|
||||
{
|
||||
return !!mixer->ao;
|
||||
}
|
||||
|
||||
float mixer_getmaxvolume(struct mixer *mixer)
|
||||
{
|
||||
// gain == 1
|
||||
return mixer->softvol ? mixer->opts->softvol_max : 100;
|
||||
}
|
||||
|
||||
static void checkvolume(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->ao)
|
||||
return;
|
||||
|
||||
ao_control_vol_t vol = {mixer->vol_l, mixer->vol_r};
|
||||
if (mixer->softvol) {
|
||||
float gain;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_GET_VOLUME, &gain))
|
||||
gain = 1.0;
|
||||
vol.left = gain * 100.0;
|
||||
vol.right = gain * 100.0;
|
||||
} else {
|
||||
MP_DBG(mixer, "Reading volume from AO.\n");
|
||||
// Rely on the values not changing if the query is not supported
|
||||
ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol);
|
||||
ao_control(mixer->ao, AOCONTROL_GET_MUTE, &mixer->muted);
|
||||
}
|
||||
float l = mixer->vol_l;
|
||||
float r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
/* Try to detect cases where the volume has been changed by some external
|
||||
* action (such as something else changing a shared system-wide volume).
|
||||
* We don't test for exact equality, as some AOs may round the value
|
||||
* we last set to some nearby supported value. 3 has been the default
|
||||
* volume step for increase/decrease keys, and is apparently big enough
|
||||
* to step to the next possible value in most setups.
|
||||
*/
|
||||
if (FFABS(vol.left - l) >= 3 || FFABS(vol.right - r) >= 3) {
|
||||
mixer->vol_l = vol.left;
|
||||
mixer->vol_r = vol.right;
|
||||
if (mixer->emulate_mute)
|
||||
mixer->muted = false;
|
||||
}
|
||||
mixer->muted_by_us &= mixer->muted;
|
||||
}
|
||||
|
||||
void mixer_getvolume(struct mixer *mixer, float *l, float *r)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
*l = mixer->vol_l;
|
||||
*r = mixer->vol_r;
|
||||
}
|
||||
|
||||
static void setvolume_internal(struct mixer *mixer)
|
||||
{
|
||||
float l = mixer->vol_l, r = mixer->vol_r;
|
||||
if (mixer->emulate_mute && mixer->muted)
|
||||
l = r = 0;
|
||||
if (!mixer->softvol) {
|
||||
MP_DBG(mixer, "Setting volume on AO.\n");
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
MP_ERR(mixer, "Failed to change audio output volume.\n");
|
||||
return;
|
||||
}
|
||||
float gain = (l + r) / 2.0 / 100.0;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)) {
|
||||
if (gain == 1.0)
|
||||
return;
|
||||
MP_VERBOSE(mixer, "Inserting volume filter.\n");
|
||||
if (!(af_add(mixer->af, "volume", "softvol", NULL)
|
||||
&& af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)))
|
||||
MP_ERR(mixer, "No volume control available.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void mixer_setvolume(struct mixer *mixer, float l, float r)
|
||||
{
|
||||
checkvolume(mixer); // to check mute status
|
||||
|
||||
float max = mixer_getmaxvolume(mixer);
|
||||
mixer->vol_l = MPCLAMP(l, 0, max);
|
||||
mixer->vol_r = MPCLAMP(r, 0, max);
|
||||
if (mixer->ao)
|
||||
setvolume_internal(mixer);
|
||||
}
|
||||
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b)
|
||||
{
|
||||
float mixer_l, mixer_r;
|
||||
mixer_getvolume(mixer, &mixer_l, &mixer_r);
|
||||
*b = (mixer_l + mixer_r) / 2;
|
||||
}
|
||||
|
||||
void mixer_setmute(struct mixer *mixer, bool mute)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
if (mute == mixer->muted)
|
||||
return;
|
||||
if (mixer->ao) {
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
if (mixer->emulate_mute) {
|
||||
setvolume_internal(mixer);
|
||||
} else {
|
||||
ao_control(mixer->ao, AOCONTROL_SET_MUTE, &mute);
|
||||
}
|
||||
checkvolume(mixer);
|
||||
} else {
|
||||
mixer->muted = mute;
|
||||
mixer->muted_by_us = mute;
|
||||
}
|
||||
}
|
||||
|
||||
bool mixer_getmute(struct mixer *mixer)
|
||||
{
|
||||
checkvolume(mixer);
|
||||
return mixer->muted;
|
||||
}
|
||||
|
||||
void mixer_addvolume(struct mixer *mixer, float step)
|
||||
{
|
||||
float vol_l, vol_r;
|
||||
mixer_getvolume(mixer, &vol_l, &vol_r);
|
||||
mixer_setvolume(mixer, vol_l + step, vol_r + step);
|
||||
}
|
||||
|
||||
void mixer_getbalance(struct mixer *mixer, float *val)
|
||||
{
|
||||
if (mixer->af)
|
||||
af_control_any_rev(mixer->af, AF_CONTROL_GET_PAN_BALANCE, &mixer->balance);
|
||||
*val = mixer->balance;
|
||||
}
|
||||
|
||||
/* NOTE: Currently the balance code is seriously buggy: it always changes
|
||||
* the af_pan mapping between the first two input channels and first two
|
||||
* output channels to particular values. These values make sense for an
|
||||
* af_pan instance that was automatically inserted for balance control
|
||||
* only and is otherwise an identity transform, but if the filter was
|
||||
* there for another reason, then ignoring and overriding the original
|
||||
* values is completely wrong.
|
||||
*/
|
||||
|
||||
void mixer_setbalance(struct mixer *mixer, float val)
|
||||
{
|
||||
struct af_instance *af_pan_balance;
|
||||
|
||||
mixer->balance = val;
|
||||
|
||||
if (!mixer->af)
|
||||
return;
|
||||
|
||||
if (af_control_any_rev(mixer->af, AF_CONTROL_SET_PAN_BALANCE, &val))
|
||||
return;
|
||||
|
||||
if (val == 0)
|
||||
return;
|
||||
|
||||
if (!(af_pan_balance = af_add(mixer->af, "pan", "autopan", NULL))) {
|
||||
MP_ERR(mixer, "No balance control available.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* make all other channels pass through since by default pan blocks all */
|
||||
for (int i = 2; i < AF_NCH; i++) {
|
||||
float level[AF_NCH] = {0};
|
||||
level[i] = 1.f;
|
||||
af_control_ext_t arg_ext = { .ch = i, .arg = level };
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_LEVEL,
|
||||
&arg_ext);
|
||||
}
|
||||
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
|
||||
}
|
||||
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->driver[0])
|
||||
return NULL;
|
||||
return talloc_asprintf(NULL, "%s:%f:%f:%d", mixer->driver, mixer->vol_l,
|
||||
mixer->vol_r, mixer->muted_by_us);
|
||||
}
|
||||
|
||||
static void probe_softvol(struct mixer *mixer)
|
||||
{
|
||||
bool ao_perapp = ao_control(mixer->ao, AOCONTROL_HAS_PER_APP_VOLUME, 0) == 1;
|
||||
bool ao_softvol = ao_control(mixer->ao, AOCONTROL_HAS_SOFT_VOLUME, 0) == 1;
|
||||
assert(!(ao_perapp && ao_softvol));
|
||||
mixer->persistent_volume = !ao_softvol;
|
||||
|
||||
if (mixer->opts->softvol == SOFTVOL_AUTO) {
|
||||
// No system-wide volume => fine with AO volume control.
|
||||
mixer->softvol = !ao_softvol && !ao_perapp;
|
||||
} else {
|
||||
mixer->softvol = mixer->opts->softvol == SOFTVOL_YES;
|
||||
}
|
||||
|
||||
if (mixer->softvol)
|
||||
mixer->persistent_volume = false;
|
||||
|
||||
MP_DBG(mixer, "Will use af_volume: %s\n", mixer->softvol ? "yes" : "no");
|
||||
|
||||
// If we can't use real volume control => force softvol.
|
||||
if (!mixer->softvol) {
|
||||
ao_control_vol_t vol;
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK) {
|
||||
mixer->softvol = true;
|
||||
MP_WARN(mixer, "Hardware volume control unavailable.\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Probe native mute support.
|
||||
mixer->emulate_mute = true;
|
||||
if (!mixer->softvol) {
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_MUTE, &(bool){0}) == CONTROL_OK)
|
||||
mixer->emulate_mute = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void restore_volume(struct mixer *mixer)
|
||||
{
|
||||
struct MPOpts *opts = mixer->opts;
|
||||
struct ao *ao = mixer->ao;
|
||||
|
||||
float force_vol_l = -1, force_vol_r = -1;
|
||||
int force_mute = -1;
|
||||
|
||||
const char *prev_driver = mixer->driver;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao_get_name(ao);
|
||||
if (!prev_driver[0])
|
||||
prev_driver = mixer->driver;
|
||||
|
||||
// Restore old parameters if volume won't survive reinitialization.
|
||||
// But not if volume scale is possibly different.
|
||||
if (!mixer->persistent_volume && strcmp(mixer->driver, prev_driver) == 0) {
|
||||
force_vol_l = mixer->vol_l;
|
||||
force_vol_r = mixer->vol_r;
|
||||
}
|
||||
|
||||
// Set mute if we disabled it on uninit last time.
|
||||
if (mixer->muted_by_us)
|
||||
force_mute = 1;
|
||||
|
||||
// Set parameters from command line.
|
||||
if (opts->mixer_init_volume >= 0)
|
||||
force_vol_l = force_vol_r = opts->mixer_init_volume;
|
||||
if (opts->mixer_init_mute >= 0)
|
||||
force_mute = opts->mixer_init_mute;
|
||||
|
||||
// Set parameters from playback resume.
|
||||
char *data = mixer->opts->mixer_restore_volume_data;
|
||||
if (!mixer->persistent_volume && data && data[0]) {
|
||||
char drv[40];
|
||||
float v_l, v_r;
|
||||
int m;
|
||||
if (sscanf(data, "%39[^:]:%f:%f:%d", drv, &v_l, &v_r, &m) == 4) {
|
||||
if (strcmp(mixer->driver, drv) == 0) {
|
||||
force_vol_l = v_l;
|
||||
force_vol_r = v_r;
|
||||
force_mute = !!m;
|
||||
MP_DBG(mixer, "Restoring volume from resume config.\n");
|
||||
}
|
||||
}
|
||||
talloc_free(mixer->opts->mixer_restore_volume_data);
|
||||
mixer->opts->mixer_restore_volume_data = NULL;
|
||||
}
|
||||
|
||||
// Using --volume should not reset the volume on every file (i.e. reinit),
|
||||
// OTOH mpv --{ --volume 10 f1.mkv --} --{ --volume 20 f2.mkv --} must work.
|
||||
// Resetting the option volumes to "auto" (-1) is easiest. If file local
|
||||
// options (as shown above) are used, the option handler code will reset
|
||||
// them to other values, and force the volume to be reset as well.
|
||||
opts->mixer_init_volume = -1;
|
||||
opts->mixer_init_mute = -1;
|
||||
|
||||
checkvolume(mixer);
|
||||
if (force_vol_l >= 0 && force_vol_r >= 0) {
|
||||
MP_DBG(mixer, "Restoring previous volume.\n");
|
||||
mixer_setvolume(mixer, force_vol_l, force_vol_r);
|
||||
}
|
||||
if (force_mute >= 0) {
|
||||
MP_DBG(mixer, "Restoring previous mute toggle.\n");
|
||||
mixer_setmute(mixer, force_mute);
|
||||
}
|
||||
}
|
||||
|
||||
// Called after the audio filter chain is built or rebuilt.
|
||||
// (Can be called multiple times, even without mixer_uninit() in-between.)
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af)
|
||||
{
|
||||
if (!ao || !af)
|
||||
return;
|
||||
mixer->ao = ao;
|
||||
mixer->af = af;
|
||||
|
||||
MP_DBG(mixer, "Reinit...\n");
|
||||
|
||||
probe_softvol(mixer);
|
||||
restore_volume(mixer);
|
||||
|
||||
if (mixer->balance != 0)
|
||||
mixer_setbalance(mixer, mixer->balance);
|
||||
}
|
||||
|
||||
/* Called before uninitializing the audio filter chain. The main purpose is to
|
||||
* turn off mute, in case it's a global/persistent setting which might
|
||||
* otherwise be left enabled even after this player instance exits.
|
||||
*/
|
||||
void mixer_uninit_audio(struct mixer *mixer)
|
||||
{
|
||||
if (!mixer->ao)
|
||||
return;
|
||||
|
||||
MP_DBG(mixer, "Uninit...\n");
|
||||
|
||||
checkvolume(mixer);
|
||||
if (mixer->muted_by_us && mixer->persistent_volume) {
|
||||
MP_DBG(mixer, "Draining.\n");
|
||||
/* Current audio output API combines playing the remaining buffered
|
||||
* audio and uninitializing the AO into one operation, even though
|
||||
* ideally unmute would happen between those two steps. We can't do
|
||||
* volume changes after uninitialization, but we don't want the
|
||||
* remaining audio to play at full volume either. Thus this
|
||||
* workaround to drop remaining audio first. */
|
||||
ao_reset(mixer->ao);
|
||||
mixer_setmute(mixer, false);
|
||||
/* We remember mute status and re-enable it if we play more audio
|
||||
* in the same process. */
|
||||
mixer->muted_by_us = true;
|
||||
}
|
||||
mixer->ao = NULL;
|
||||
mixer->af = NULL;
|
||||
mixer->softvol = false;
|
||||
}
|
||||
@@ -1,50 +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_MIXER_H
|
||||
#define MPLAYER_MIXER_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
// Values of MPOpts.softvol
|
||||
enum {
|
||||
SOFTVOL_NO = 0,
|
||||
SOFTVOL_YES = 1,
|
||||
SOFTVOL_AUTO = 2,
|
||||
};
|
||||
|
||||
struct mpv_global;
|
||||
struct ao;
|
||||
struct af_stream;
|
||||
struct mixer;
|
||||
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global);
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af);
|
||||
void mixer_uninit_audio(struct mixer *mixer);
|
||||
bool mixer_audio_initialized(struct mixer *mixer);
|
||||
void mixer_getvolume(struct mixer *mixer, float *l, float *r);
|
||||
void mixer_setvolume(struct mixer *mixer, float l, float r);
|
||||
void mixer_addvolume(struct mixer *mixer, float step);
|
||||
void mixer_getbothvolume(struct mixer *mixer, float *b);
|
||||
void mixer_setmute(struct mixer *mixer, bool mute);
|
||||
bool mixer_getmute(struct mixer *mixer);
|
||||
void mixer_getbalance(struct mixer *mixer, float *bal);
|
||||
void mixer_setbalance(struct mixer *mixer, float bal);
|
||||
float mixer_getmaxvolume(struct mixer *mixer);
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer);
|
||||
|
||||
#endif /* MPLAYER_MIXER_H */
|
||||
+33
-8
@@ -158,7 +158,7 @@ error:
|
||||
|
||||
static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx, int flags,
|
||||
int samplerate, int format, struct mp_chmap channels,
|
||||
char *dev, char *name, char **args)
|
||||
{
|
||||
@@ -169,6 +169,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
ao->channels = channels;
|
||||
ao->format = format;
|
||||
ao->encode_lavc_ctx = encode_lavc_ctx;
|
||||
ao->init_flags = flags;
|
||||
if (ao->driver->encode != !!ao->encode_lavc_ctx)
|
||||
goto fail;
|
||||
|
||||
@@ -182,6 +183,8 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
ao->api_priv = talloc_zero_size(ao, ao->api->priv_size);
|
||||
assert(!ao->api->priv_defaults && !ao->api->options);
|
||||
|
||||
ao->stream_silence = flags & AO_INIT_STREAM_SILENCE;
|
||||
|
||||
int r = ao->driver->init(ao);
|
||||
if (r < 0) {
|
||||
// Silly exception for coreaudio spdif redirection
|
||||
@@ -190,7 +193,7 @@ static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
snprintf(redirect, sizeof(redirect), "%s", ao->redirect);
|
||||
snprintf(rdevice, sizeof(rdevice), "%s", ao->device ? ao->device : "");
|
||||
talloc_free(ao);
|
||||
return ao_init(probing, global, input_ctx, encode_lavc_ctx,
|
||||
return ao_init(probing, global, input_ctx, encode_lavc_ctx, flags,
|
||||
samplerate, format, channels, rdevice, redirect, NULL);
|
||||
}
|
||||
goto fail;
|
||||
@@ -240,7 +243,7 @@ static void split_ao_device(void *tmp, char *opt, char **out_ao, char **out_dev)
|
||||
}
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
bool ao_null_fallback,
|
||||
int init_flags,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
@@ -283,7 +286,7 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
}
|
||||
}
|
||||
|
||||
if (ao_null_fallback) {
|
||||
if (init_flags & AO_INIT_NULL_FALLBACK) {
|
||||
MP_TARRAY_APPEND(tmp, ao_list, ao_num,
|
||||
(struct m_obj_settings){.name = "null"});
|
||||
}
|
||||
@@ -297,7 +300,7 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
dev = pref_dev;
|
||||
mp_verbose(log, "Using preferred device '%s'\n", dev);
|
||||
}
|
||||
ao = ao_init(probing, global, input_ctx, encode_lavc_ctx,
|
||||
ao = ao_init(probing, global, input_ctx, encode_lavc_ctx, init_flags,
|
||||
samplerate, format, channels, dev,
|
||||
entry->name, entry->attribs);
|
||||
if (ao)
|
||||
@@ -429,6 +432,29 @@ bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
return r;
|
||||
}
|
||||
|
||||
// safe_multichannel=true behaves like ao_chmap_sel_adjust.
|
||||
// safe_multichannel=false is a helper for callers which do not support safe
|
||||
// handling of arbitrary channel layouts. If the multichannel layouts are not
|
||||
// considered "always safe" (e.g. HDMI), then allow only stereo or mono, if
|
||||
// they are part of the list in *s.
|
||||
bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map, bool safe_multichannel)
|
||||
{
|
||||
if (!safe_multichannel && (ao->init_flags & AO_INIT_SAFE_MULTICHANNEL_ONLY)) {
|
||||
struct mp_chmap res = *map;
|
||||
if (mp_chmap_sel_adjust(s, &res)) {
|
||||
if (!mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_MONO) &&
|
||||
!mp_chmap_equals(&res, &(struct mp_chmap)MP_CHMAP_INIT_STEREO))
|
||||
{
|
||||
MP_WARN(ao, "Disabling multichannel output.\n");
|
||||
*map = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ao_chmap_sel_adjust(ao, s, map);
|
||||
}
|
||||
|
||||
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map, int num)
|
||||
{
|
||||
@@ -490,10 +516,9 @@ struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
|
||||
static void get_devices(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
int num = list->num_devices;
|
||||
if (ao->driver->list_devs)
|
||||
if (ao->driver->list_devs) {
|
||||
ao->driver->list_devs(ao, list);
|
||||
// Add at least a default entry
|
||||
if (list->num_devices == num) {
|
||||
} else {
|
||||
char name[80] = "Default";
|
||||
if (num > 1)
|
||||
mp_snprintf_cat(name, sizeof(name), " (%s)", ao->driver->name);
|
||||
|
||||
+11
-1
@@ -50,6 +50,16 @@ enum {
|
||||
AO_EVENT_HOTPLUG = 2,
|
||||
};
|
||||
|
||||
enum {
|
||||
// Allow falling back to ao_null if nothing else works.
|
||||
AO_INIT_NULL_FALLBACK = 1 << 0,
|
||||
// Only accept multichannel configurations that are guaranteed to work
|
||||
// (i.e. not sending arbitrary layouts over HDMI).
|
||||
AO_INIT_SAFE_MULTICHANNEL_ONLY = 1 << 1,
|
||||
// Stream silence as long as no audio is playing.
|
||||
AO_INIT_STREAM_SILENCE = 1 << 2,
|
||||
};
|
||||
|
||||
typedef struct ao_control_vol {
|
||||
float left;
|
||||
float right;
|
||||
@@ -72,7 +82,7 @@ struct encode_lavc_context;
|
||||
struct mp_audio;
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
bool ao_null_fallback,
|
||||
int init_flags,
|
||||
struct input_ctx *input_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels);
|
||||
|
||||
+133
-26
@@ -53,11 +53,14 @@ struct priv {
|
||||
bool device_lost;
|
||||
snd_pcm_format_t alsa_fmt;
|
||||
bool can_pause;
|
||||
bool paused;
|
||||
snd_pcm_sframes_t prepause_frames;
|
||||
double delay_before_pause;
|
||||
snd_pcm_uframes_t buffersize;
|
||||
snd_pcm_uframes_t outburst;
|
||||
|
||||
snd_output_t *output;
|
||||
|
||||
char *cfg_device;
|
||||
char *cfg_mixer_device;
|
||||
char *cfg_mixer_name;
|
||||
@@ -334,8 +337,10 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
struct mp_chmap_sel chmap_sel = {.tmp = p};
|
||||
|
||||
snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps(p->alsa);
|
||||
if (!maps)
|
||||
if (!maps) {
|
||||
MP_VERBOSE(ao, "snd_pcm_query_chmaps() returned NULL\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; maps[i] != NULL; i++) {
|
||||
char aname[128];
|
||||
@@ -358,7 +363,7 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
|
||||
snd_pcm_free_chmaps(maps);
|
||||
|
||||
return ao_chmap_sel_adjust(ao, &chmap_sel, chmap);
|
||||
return ao_chmap_sel_adjust2(ao, &chmap_sel, chmap, false);
|
||||
}
|
||||
|
||||
// Map back our selected channel layout to an ALSA one. This is done this way so
|
||||
@@ -477,6 +482,22 @@ 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,
|
||||
snd_pcm_hw_params_t *hw_params)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
err = snd_pcm_hw_params_dump(hw_params, p->output);
|
||||
CHECK_ALSA_WARN("Dump hwparams error");
|
||||
|
||||
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);
|
||||
snd_output_flush(p->output);
|
||||
}
|
||||
|
||||
static int map_iec958_srate(int srate)
|
||||
{
|
||||
switch (srate) {
|
||||
@@ -520,7 +541,7 @@ static char *append_params(void *ta_parent, const char *device, const char *p)
|
||||
abort();
|
||||
}
|
||||
|
||||
static int try_open_device(struct ao *ao, const char *device)
|
||||
static int try_open_device(struct ao *ao, const char *device, int mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
@@ -534,7 +555,7 @@ static int try_open_device(struct ao *ao, const char *device)
|
||||
map_iec958_srate(ao->samplerate));
|
||||
const char *ac3_device = append_params(tmp, device, params);
|
||||
MP_VERBOSE(ao, "opening device '%s' => '%s'\n", device, ac3_device);
|
||||
err = snd_pcm_open(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
err = snd_pcm_open(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
if (err < 0) {
|
||||
// Some spdif-capable devices do not accept the AES0 parameter,
|
||||
// and instead require the iec958 pseudo-device (they will play
|
||||
@@ -547,13 +568,13 @@ static int try_open_device(struct ao *ao, const char *device)
|
||||
MP_VERBOSE(ao, "got error %d; opening iec fallback device '%s'\n",
|
||||
err, ac3_device);
|
||||
err = snd_pcm_open
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
}
|
||||
}
|
||||
talloc_free(tmp);
|
||||
} else {
|
||||
MP_VERBOSE(ao, "opening device '%s'\n", device);
|
||||
err = snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, 0);
|
||||
err = snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
}
|
||||
|
||||
return err;
|
||||
@@ -563,6 +584,10 @@ static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (p->output)
|
||||
snd_output_close(p->output);
|
||||
p->output = NULL;
|
||||
|
||||
if (p->alsa) {
|
||||
int err;
|
||||
|
||||
@@ -574,20 +599,35 @@ static void uninit(struct ao *ao)
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
static int init_device(struct ao *ao)
|
||||
#define INIT_DEVICE_ERR_GENERIC -1
|
||||
#define INIT_DEVICE_ERR_HWPARAMS -2
|
||||
static int init_device(struct ao *ao, int mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int ret = INIT_DEVICE_ERR_GENERIC;
|
||||
char *tmp;
|
||||
size_t tmp_s;
|
||||
int err;
|
||||
|
||||
err = snd_output_buffer_open(&p->output);
|
||||
CHECK_ALSA_ERROR("Unable to create output buffer");
|
||||
|
||||
const char *device = "default";
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->cfg_device && p->cfg_device[0])
|
||||
device = p->cfg_device;
|
||||
|
||||
err = try_open_device(ao, device);
|
||||
err = try_open_device(ao, device, mode);
|
||||
CHECK_ALSA_ERROR("Playback open error");
|
||||
|
||||
err = snd_pcm_dump(p->alsa, p->output);
|
||||
CHECK_ALSA_WARN("Dump PCM error");
|
||||
tmp_s = snd_output_buffer_string(p->output, &tmp);
|
||||
if (tmp)
|
||||
MP_DBG(ao, "PCM setup:\n---\n%.*s---\n", (int)tmp_s, tmp);
|
||||
snd_output_flush(p->output);
|
||||
|
||||
err = snd_pcm_nonblock(p->alsa, 0);
|
||||
CHECK_ALSA_WARN("Unable to set blocking mode");
|
||||
|
||||
@@ -597,12 +637,15 @@ static int init_device(struct ao *ao)
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
dump_hw_params(ao, MSGL_DEBUG, "Start HW params:\n", alsa_hwparams);
|
||||
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
if (!p->cfg_resample) {
|
||||
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);
|
||||
|
||||
snd_pcm_access_t access = af_fmt_is_planar(ao->format)
|
||||
? SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
@@ -614,6 +657,7 @@ static int init_device(struct ao *ao)
|
||||
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);
|
||||
|
||||
bool found_format = false;
|
||||
int try_formats[AF_FORMAT_COUNT];
|
||||
@@ -637,6 +681,7 @@ static int init_device(struct ao *ao)
|
||||
|
||||
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);
|
||||
|
||||
struct mp_chmap dev_chmap = ao->channels;
|
||||
if (af_fmt_is_spdif(ao->format) || p->cfg_ignore_chmap) {
|
||||
@@ -656,6 +701,7 @@ static int init_device(struct ao *ao)
|
||||
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);
|
||||
|
||||
if (num_channels > MP_NUM_CHANNELS) {
|
||||
MP_FATAL(ao, "Too many audio channels (%d).\n", num_channels);
|
||||
@@ -665,6 +711,7 @@ static int init_device(struct ao *ao)
|
||||
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);
|
||||
|
||||
snd_pcm_hw_params_t *hwparams_backup;
|
||||
snd_pcm_hw_params_alloca(&hwparams_backup);
|
||||
@@ -682,9 +729,14 @@ static int init_device(struct ao *ao)
|
||||
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);
|
||||
|
||||
/* 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);
|
||||
|
||||
if (set_chmap(ao, &dev_chmap, num_channels) < 0)
|
||||
goto alsa_error;
|
||||
@@ -736,11 +788,19 @@ static int init_device(struct ao *ao)
|
||||
MP_VERBOSE(ao, "buffersize: %d samples\n", (int)p->buffersize);
|
||||
MP_VERBOSE(ao, "period size: %d samples\n", (int)p->outburst);
|
||||
|
||||
ao->device_buffer = p->buffersize;
|
||||
|
||||
// ao_alsa implements this by relying on underrun behavior (no data means
|
||||
// underrun, during which silence is played). Trigger by playing some
|
||||
// initial silence.
|
||||
if (ao->stream_silence)
|
||||
ao_play_silence(ao, p->outburst);
|
||||
|
||||
return 0;
|
||||
|
||||
alsa_error:
|
||||
uninit(ao);
|
||||
return -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
@@ -751,7 +811,18 @@ static int init(struct ao *ao)
|
||||
|
||||
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
|
||||
|
||||
int r = init_device(ao);
|
||||
int mode = 0;
|
||||
int r = init_device(ao, mode);
|
||||
if (r == INIT_DEVICE_ERR_HWPARAMS) {
|
||||
// With some drivers, ALSA appears to be unable to set valid hwparams,
|
||||
// but they work if at least SND_PCM_NO_AUTO_FORMAT is set. Also, it
|
||||
// appears you can set this flag only on opening a device, thus there
|
||||
// is the need to retry opening the device.
|
||||
MP_WARN(ao, "Attempting to work around even more ALSA bugs...\n");
|
||||
mode |= SND_PCM_NO_AUTO_CHANNELS | SND_PCM_NO_AUTO_FORMAT |
|
||||
SND_PCM_NO_AUTO_RESAMPLE;
|
||||
r = init_device(ao, mode);
|
||||
}
|
||||
|
||||
// Sometimes, ALSA will advertise certain chmaps, but it's not possible to
|
||||
// set them. This can happen with dmix: as of alsa 1.0.29, dmix can do
|
||||
@@ -773,7 +844,7 @@ static int init(struct ao *ao)
|
||||
MP_VERBOSE(ao, "Working around braindead dmix multichannel behavior.\n");
|
||||
uninit(ao);
|
||||
ao->channels = without_na;
|
||||
r = init_device(ao);
|
||||
r = init_device(ao, mode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -814,7 +885,7 @@ static double get_delay(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
|
||||
if (p->paused)
|
||||
return p->delay_before_pause;
|
||||
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
@@ -828,27 +899,47 @@ static double get_delay(struct ao *ao)
|
||||
return delay / (double)ao->samplerate;
|
||||
}
|
||||
|
||||
// For stream-silence mode: replace remaining buffer with silence.
|
||||
// Tries to cause an instant buffer underrun.
|
||||
static void soft_reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
snd_pcm_sframes_t frames = snd_pcm_rewindable(p->alsa);
|
||||
if (frames > 0 && snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) {
|
||||
frames = snd_pcm_rewind(p->alsa, frames);
|
||||
if (frames < 0) {
|
||||
int err = frames;
|
||||
CHECK_ALSA_WARN("pcm rewind error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
if (p->can_pause) {
|
||||
if (p->paused)
|
||||
return;
|
||||
|
||||
p->delay_before_pause = get_delay(ao);
|
||||
p->prepause_frames = p->delay_before_pause * ao->samplerate;
|
||||
|
||||
if (ao->stream_silence) {
|
||||
soft_reset(ao);
|
||||
} else if (p->can_pause) {
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_RUNNING) {
|
||||
p->delay_before_pause = get_delay(ao);
|
||||
err = snd_pcm_pause(p->alsa, 1);
|
||||
CHECK_ALSA_ERROR("pcm pause error");
|
||||
p->prepause_frames = 0;
|
||||
}
|
||||
} else {
|
||||
if (snd_pcm_delay(p->alsa, &p->prepause_frames) < 0
|
||||
|| p->prepause_frames < 0)
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = p->prepause_frames / (double)ao->samplerate;
|
||||
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm drop error");
|
||||
}
|
||||
|
||||
p->paused = true;
|
||||
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
@@ -870,9 +961,15 @@ static void audio_resume(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
if (!p->paused)
|
||||
return;
|
||||
|
||||
resume_device(ao);
|
||||
|
||||
if (p->can_pause) {
|
||||
if (ao->stream_silence) {
|
||||
p->paused = false;
|
||||
get_delay(ao); // recovers from underrun (as a side-effect)
|
||||
} else if (p->can_pause) {
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED) {
|
||||
err = snd_pcm_pause(p->alsa, 0);
|
||||
CHECK_ALSA_ERROR("pcm resume error");
|
||||
@@ -881,11 +978,13 @@ static void audio_resume(struct ao *ao)
|
||||
MP_VERBOSE(ao, "resume not supported by hardware\n");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
if (p->prepause_frames)
|
||||
ao_play_silence(ao, p->prepause_frames);
|
||||
}
|
||||
|
||||
if (p->prepause_frames)
|
||||
ao_play_silence(ao, p->prepause_frames);
|
||||
|
||||
alsa_error: ;
|
||||
p->paused = false;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
@@ -893,12 +992,18 @@ static void reset(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
int err;
|
||||
|
||||
p->paused = false;
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = 0;
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
|
||||
if (ao->stream_silence) {
|
||||
soft_reset(ao);
|
||||
} else {
|
||||
err = snd_pcm_drop(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
}
|
||||
|
||||
alsa_error: ;
|
||||
}
|
||||
@@ -937,6 +1042,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
} while (res == 0);
|
||||
|
||||
p->paused = false;
|
||||
|
||||
return res < 0 ? -1 : res;
|
||||
|
||||
alsa_error:
|
||||
|
||||
@@ -133,11 +133,6 @@ static bool reinit_device(struct ao *ao) {
|
||||
OSStatus err = ca_select_device(ao, ao->device, &p->device);
|
||||
CHECK_CA_ERROR("failed to select device");
|
||||
|
||||
char *uid;
|
||||
err = CA_GET_STR(p->device, kAudioDevicePropertyDeviceUID, &uid);
|
||||
CHECK_CA_ERROR("failed to get device UID");
|
||||
ao->detected_device = talloc_steal(ao, uid);
|
||||
|
||||
return true;
|
||||
|
||||
coreaudio_error:
|
||||
@@ -424,7 +419,7 @@ const struct ao_driver audio_out_coreaudio = {
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.control = control,
|
||||
.pause = stop,
|
||||
.reset = stop,
|
||||
.resume = start,
|
||||
.hotplug_init = hotplug_init,
|
||||
.hotplug_uninit = hotplug_uninit,
|
||||
|
||||
@@ -399,7 +399,7 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
|
||||
.name = "coreaudio_exclusive",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.pause = audio_pause,
|
||||
.reset = audio_pause,
|
||||
.resume = audio_resume,
|
||||
.list_devs = ca_get_device_list,
|
||||
.priv_size = sizeof(struct priv),
|
||||
|
||||
@@ -70,7 +70,7 @@ OSStatus ca_get_ary(AudioObjectID id, ca_scope scope, ca_sel selector,
|
||||
|
||||
return err;
|
||||
coreaudio_error_free:
|
||||
free(*data);
|
||||
talloc_free(*data);
|
||||
coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -114,6 +114,13 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
kAudioObjectSystemObject, &p_addr, 0, 0, &size, &v);
|
||||
CFRelease(uid);
|
||||
CHECK_CA_ERROR("unable to query for device UID");
|
||||
|
||||
uint32_t is_alive = 1;
|
||||
err = CA_GET(*device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
||||
CHECK_CA_ERROR("could not check whether device is alive (invalid device?)");
|
||||
|
||||
if (!is_alive)
|
||||
MP_WARN(ao, "device is not alive!\n");
|
||||
} else {
|
||||
// device not set by user, get the default one
|
||||
err = CA_GET(kAudioObjectSystemObject,
|
||||
@@ -140,7 +147,7 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
{
|
||||
if (code == noErr) return true;
|
||||
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, mp_tag_str(code));
|
||||
mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -323,6 +330,9 @@ bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream)
|
||||
|
||||
for (int i = 0; i < n_formats; i++) {
|
||||
AudioStreamBasicDescription asbd = formats[i].mFormat;
|
||||
|
||||
ca_print_asbd(ao, "- ", &asbd);
|
||||
|
||||
if (ca_formatid_is_compressed(asbd.mFormatID)) {
|
||||
talloc_free(formats);
|
||||
return true;
|
||||
@@ -334,30 +344,6 @@ coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device)
|
||||
{
|
||||
AudioStreamID *streams = NULL;
|
||||
size_t n_streams;
|
||||
|
||||
/* Retrieve all the output streams. */
|
||||
OSStatus err =
|
||||
CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams);
|
||||
|
||||
CHECK_CA_ERROR("could not get number of streams.");
|
||||
|
||||
for (int i = 0; i < n_streams; i++) {
|
||||
if (ca_stream_supports_compressed(ao, streams[i])) {
|
||||
talloc_free(streams);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
talloc_free(streams);
|
||||
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid)
|
||||
{
|
||||
*pid = getpid();
|
||||
|
||||
@@ -65,7 +65,6 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
|
||||
bool ca_device_supports_compressed(struct ao *ao, AudioDeviceID device);
|
||||
bool ca_stream_supports_compressed(struct ao *ao, AudioStreamID stream);
|
||||
OSStatus ca_lock_device(AudioDeviceID device, pid_t *pid);
|
||||
OSStatus ca_unlock_device(AudioDeviceID device, pid_t *pid);
|
||||
|
||||
+156
-111
@@ -39,9 +39,8 @@
|
||||
#include "common/encode_lavc.h"
|
||||
|
||||
struct priv {
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size;
|
||||
AVStream *stream;
|
||||
AVCodecContext *codec;
|
||||
int pcmhack;
|
||||
int aframesize;
|
||||
int aframecount;
|
||||
@@ -98,15 +97,14 @@ static int init(struct ao *ao)
|
||||
|
||||
pthread_mutex_lock(&ao->encode_lavc_ctx->lock);
|
||||
|
||||
ac->stream = encode_lavc_alloc_stream(ao->encode_lavc_ctx,
|
||||
AVMEDIA_TYPE_AUDIO);
|
||||
|
||||
if (!ac->stream) {
|
||||
MP_ERR(ao, "could not get a new audio stream\n");
|
||||
goto fail;
|
||||
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 = encode_lavc_get_codec(ao->encode_lavc_ctx, ac->stream);
|
||||
codec = ao->encode_lavc_ctx->ac;
|
||||
|
||||
int samplerate = af_select_best_samplerate(ao->samplerate,
|
||||
codec->supported_samplerates);
|
||||
@@ -118,46 +116,38 @@ static int init(struct ao *ao)
|
||||
// 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->stream->codec->time_base.num = 1;
|
||||
ac->stream->codec->time_base.den = ao->samplerate;
|
||||
ac->codec->time_base.num = 1;
|
||||
ac->codec->time_base.den = ao->samplerate;
|
||||
|
||||
ac->stream->codec->sample_rate = ao->samplerate;
|
||||
ac->codec->sample_rate = ao->samplerate;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
if (!ao_chmap_sel_adjust2(ao, &sel, &ao->channels, false))
|
||||
goto fail;
|
||||
mp_chmap_reorder_to_lavc(&ao->channels);
|
||||
ac->stream->codec->channels = ao->channels.num;
|
||||
ac->stream->codec->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
ac->codec->channels = ao->channels.num;
|
||||
ac->codec->channel_layout = mp_chmap_to_lavc(&ao->channels);
|
||||
|
||||
ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
ac->codec->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
|
||||
select_format(ao, codec);
|
||||
|
||||
ac->sample_size = af_fmt_to_bytes(ao->format);
|
||||
ac->stream->codec->sample_fmt = af_to_avformat(ao->format);
|
||||
ac->stream->codec->bits_per_raw_sample = ac->sample_size * 8;
|
||||
ac->codec->sample_fmt = af_to_avformat(ao->format);
|
||||
ac->codec->bits_per_raw_sample = ac->sample_size * 8;
|
||||
|
||||
if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->stream) < 0)
|
||||
if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->codec) < 0)
|
||||
goto fail;
|
||||
|
||||
ac->pcmhack = 0;
|
||||
if (ac->stream->codec->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(ac->stream->codec->codec_id) / 8;
|
||||
if (ac->codec->frame_size <= 1)
|
||||
ac->pcmhack = av_get_bits_per_sample(ac->codec->codec_id) / 8;
|
||||
|
||||
if (ac->pcmhack) {
|
||||
if (ac->pcmhack)
|
||||
ac->aframesize = 16384; // "enough"
|
||||
ac->buffer_size =
|
||||
ac->aframesize * ac->pcmhack * ao->channels.num * 2 + 200;
|
||||
} else {
|
||||
ac->aframesize = ac->stream->codec->frame_size;
|
||||
ac->buffer_size =
|
||||
ac->aframesize * ac->sample_size * ao->channels.num * 2 + 200;
|
||||
}
|
||||
if (ac->buffer_size < FF_MIN_BUFFER_SIZE)
|
||||
ac->buffer_size = FF_MIN_BUFFER_SIZE;
|
||||
ac->buffer = talloc_size(ac, ac->buffer_size);
|
||||
else
|
||||
ac->aframesize = ac->codec->frame_size;
|
||||
|
||||
// enough frames for at least 0.25 seconds
|
||||
ac->framecount = ceil(ao->samplerate * 0.25 / ac->aframesize);
|
||||
@@ -182,7 +172,7 @@ fail:
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static int encode(struct ao *ao, double apts, void **data);
|
||||
static void encode(struct ao *ao, double apts, void **data);
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
@@ -199,12 +189,12 @@ static void uninit(struct ao *ao)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ac->buffer) {
|
||||
if (ac->stream) {
|
||||
double outpts = ac->expected_next_pts;
|
||||
if (!ectx->options->rawts && ectx->options->copyts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->stream);
|
||||
while (encode(ao, outpts, NULL) > 0) ;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
encode(ao, outpts, NULL);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&ectx->lock);
|
||||
@@ -220,24 +210,130 @@ 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};
|
||||
|
||||
#if HAVE_AVCODEC_NEW_CODEC_API
|
||||
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);
|
||||
}
|
||||
#else
|
||||
av_init_packet(&packet);
|
||||
int got_packet = 0;
|
||||
int status = avcodec_encode_audio2(ac->codec, &packet, frame, &got_packet);
|
||||
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;
|
||||
}
|
||||
if (!got_packet) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
#endif
|
||||
}
|
||||
|
||||
// must get exactly ac->aframesize amount of data
|
||||
static int encode(struct ao *ao, double apts, void **data)
|
||||
static void encode(struct ao *ao, double apts, void **data)
|
||||
{
|
||||
AVPacket packet;
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
double realapts = ac->aframecount * (double) ac->aframesize /
|
||||
ao->samplerate;
|
||||
int status, gotpacket;
|
||||
|
||||
ac->aframecount++;
|
||||
|
||||
if (data)
|
||||
ectx->audio_pts_offset = realapts - apts;
|
||||
|
||||
av_init_packet(&packet);
|
||||
packet.data = ac->buffer;
|
||||
packet.size = ac->buffer_size;
|
||||
if(data) {
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
frame->format = af_to_avformat(ao->format);
|
||||
@@ -252,82 +348,29 @@ static int encode(struct ao *ao, double apts, void **data)
|
||||
|
||||
if (ectx->options->rawts || ectx->options->copyts) {
|
||||
// real audio pts
|
||||
frame->pts = floor(apts * ac->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5);
|
||||
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->stream->codec->time_base.den / ac->stream->codec->time_base.num + 0.5);
|
||||
frame->pts = floor(realapts * ac->codec->time_base.den / ac->codec->time_base.num + 0.5);
|
||||
}
|
||||
|
||||
int64_t frame_pts = av_rescale_q(frame->pts, ac->stream->codec->time_base, ac->worst_time_base);
|
||||
int64_t frame_pts = av_rescale_q(frame->pts, ac->codec->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->stream->codec->time_base);
|
||||
frame->pts = av_rescale_q(frame_pts, ac->worst_time_base, ac->codec->time_base);
|
||||
}
|
||||
ac->lastpts = frame_pts;
|
||||
|
||||
frame->quality = ac->stream->codec->global_quality;
|
||||
status = avcodec_encode_audio2(ac->stream->codec, &packet, frame, &gotpacket);
|
||||
|
||||
if (!status) {
|
||||
if (ac->savepts == AV_NOPTS_VALUE)
|
||||
ac->savepts = frame->pts;
|
||||
}
|
||||
|
||||
frame->quality = ac->codec->global_quality;
|
||||
encode_audio_and_write(ao, frame);
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = avcodec_encode_audio2(ac->stream->codec, &packet, NULL, &gotpacket);
|
||||
}
|
||||
|
||||
if(status) {
|
||||
MP_ERR(ao, "error encoding\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(!gotpacket)
|
||||
return 0;
|
||||
|
||||
MP_DBG(ao, "got pts %f (playback time: %f); out size: %d\n",
|
||||
apts, realapts, packet.size);
|
||||
|
||||
encode_lavc_write_stats(ao->encode_lavc_ctx, ac->stream);
|
||||
|
||||
packet.stream_index = ac->stream->index;
|
||||
|
||||
// Do we need this at all? Better be safe than sorry...
|
||||
if (packet.pts == AV_NOPTS_VALUE) {
|
||||
MP_WARN(ao, "encoder lost pts, why?\n");
|
||||
if (ac->savepts != MP_NOPTS_VALUE)
|
||||
packet.pts = ac->savepts;
|
||||
}
|
||||
|
||||
if (packet.pts != AV_NOPTS_VALUE)
|
||||
packet.pts = av_rescale_q(packet.pts, ac->stream->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
if (packet.dts != AV_NOPTS_VALUE)
|
||||
packet.dts = av_rescale_q(packet.dts, ac->stream->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
if(packet.duration > 0)
|
||||
packet.duration = av_rescale_q(packet.duration, ac->stream->codec->time_base,
|
||||
ac->stream->time_base);
|
||||
|
||||
ac->savepts = AV_NOPTS_VALUE;
|
||||
|
||||
if (encode_lavc_write_frame(ao->encode_lavc_ctx, &packet) < 0) {
|
||||
MP_ERR(ao, "error writing at %f %f/%f\n",
|
||||
realapts, (double) ac->stream->time_base.num,
|
||||
(double) ac->stream->time_base.den);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return packet.size;
|
||||
encode_audio_and_write(ao, NULL);
|
||||
}
|
||||
|
||||
// this should round samples down to frame sizes
|
||||
@@ -377,22 +420,22 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
|
||||
if (ac->worst_time_base.den == 0) {
|
||||
//if (ac->stream->codec->time_base.num / ac->stream->codec->time_base.den >= ac->stream->time_base.num / ac->stream->time_base.den)
|
||||
if (ac->stream->codec->time_base.num * (double) ac->stream->time_base.den >=
|
||||
ac->stream->time_base.num * (double) ac->stream->codec->time_base.den) {
|
||||
//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->stream->codec->time_base.num,
|
||||
(int)ac->stream->codec->time_base.den,
|
||||
(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->codec->time_base;
|
||||
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->stream->codec->time_base.num,
|
||||
(int)ac->stream->codec->time_base.den,
|
||||
(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;
|
||||
@@ -437,7 +480,7 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
}
|
||||
|
||||
// Shift pts by the pts offset first.
|
||||
outpts += encode_lavc_getoffset(ectx, ac->stream);
|
||||
outpts += encode_lavc_getoffset(ectx, ac->codec);
|
||||
|
||||
while (samples - bufpos >= ac->aframesize) {
|
||||
void *start[MP_NUM_CHANNELS] = {0};
|
||||
@@ -492,3 +535,5 @@ const struct ao_driver audio_out_lavc = {
|
||||
.play = play,
|
||||
.drain = drain,
|
||||
};
|
||||
|
||||
// vim: sw=4 ts=4 et tw=80
|
||||
+5
-11
@@ -57,7 +57,7 @@ struct priv {
|
||||
// (This is not needed by the AO API, but many AOs behave this way.)
|
||||
int outburst; // samples
|
||||
|
||||
char **channel_layouts;
|
||||
struct m_channels channel_layouts;
|
||||
};
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
@@ -91,15 +91,9 @@ static int init(struct ao *ao)
|
||||
ao->untimed = priv->untimed;
|
||||
|
||||
struct mp_chmap_sel sel = {.tmp = ao};
|
||||
if (priv->channel_layouts) {
|
||||
for (int n = 0; priv->channel_layouts[n]; n++) {
|
||||
struct mp_chmap map = {0};
|
||||
if (!mp_chmap_from_str(&map, bstr0(priv->channel_layouts[n]))) {
|
||||
MP_FATAL(ao, "Invalid channel map in option.\n");
|
||||
return -1;
|
||||
}
|
||||
mp_chmap_sel_add_map(&sel, &map);
|
||||
}
|
||||
if (priv->channel_layouts.num_chmaps) {
|
||||
for (int n = 0; n < priv->channel_layouts.num_chmaps; n++)
|
||||
mp_chmap_sel_add_map(&sel, &priv->channel_layouts.chmaps[n]);
|
||||
} else {
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
}
|
||||
@@ -244,7 +238,7 @@ const struct ao_driver audio_out_null = {
|
||||
OPT_FLOATRANGE("latency", latency_sec, 0, 0, 100),
|
||||
OPT_FLAG("broken-eof", broken_eof, 0),
|
||||
OPT_FLAG("broken-delay", broken_delay, 0),
|
||||
OPT_STRINGLIST("channel-layouts", channel_layouts, 0),
|
||||
OPT_CHANNELS("channel-layouts", channel_layouts, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -46,7 +46,6 @@ struct priv {
|
||||
static const int fmtmap[][2] = {
|
||||
{ AF_FORMAT_U8, SL_PCMSAMPLEFORMAT_FIXED_8 },
|
||||
{ AF_FORMAT_S16, SL_PCMSAMPLEFORMAT_FIXED_16 },
|
||||
{ AF_FORMAT_S32, SL_PCMSAMPLEFORMAT_FIXED_32 },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
||||
+8
-1
@@ -462,7 +462,7 @@ static int init(struct ao *ao)
|
||||
p->buffersize = 0;
|
||||
memset(data, 0, p->outburst);
|
||||
while (p->buffersize < 0x40000 && device_writable(ao) > 0) {
|
||||
write(p->audio_fd, data, p->outburst);
|
||||
(void)write(p->audio_fd, data, p->outburst);
|
||||
p->buffersize += p->outburst;
|
||||
}
|
||||
free(data);
|
||||
@@ -612,6 +612,12 @@ static int audio_wait(struct ao *ao, pthread_mutex_t *lock)
|
||||
return r;
|
||||
}
|
||||
|
||||
static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
{
|
||||
if (stat(PATH_DEV_DSP, &(struct stat){0}) == 0)
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", "Default"});
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_oss = {
|
||||
@@ -629,6 +635,7 @@ const struct ao_driver audio_out_oss = {
|
||||
.drain = drain,
|
||||
.wait = audio_wait,
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.audio_fd = -1,
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
@@ -34,8 +35,8 @@
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
|
||||
#define VOL_PA2MP(v) ((v) * 100 / PA_VOLUME_NORM)
|
||||
#define VOL_MP2PA(v) ((v) * PA_VOLUME_NORM / 100)
|
||||
#define VOL_PA2MP(v) ((v) * 100.0 / PA_VOLUME_NORM)
|
||||
#define VOL_MP2PA(v) lrint((v) * PA_VOLUME_NORM / 100)
|
||||
|
||||
struct priv {
|
||||
// PulseAudio playback stream object
|
||||
|
||||
@@ -132,7 +132,7 @@ static bool thread_feed(struct ao *ao)
|
||||
mp_time_us() + (int64_t)llrint(delay_us));
|
||||
|
||||
// note, we can't use ao_read_data return value here since we already
|
||||
// commited to frame_count above in the GetBuffer call
|
||||
// committed to frame_count above in the GetBuffer call
|
||||
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
|
||||
frame_count, 0);
|
||||
EXIT_ON_ERROR(hr);
|
||||
@@ -270,7 +270,7 @@ static void uninit(struct ao *ao)
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
MP_DBG(ao, "Init wasapi\n");
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
@@ -469,7 +469,7 @@ static int hotplug_init(struct ao *ao)
|
||||
MP_DBG(ao, "Hotplug init\n");
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||
CoInitializeEx(NULL, COINIT_MULTITHREADED);
|
||||
HRESULT hr = wasapi_change_init(ao, true);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <initguid.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "ao_wasapi.h"
|
||||
@@ -94,7 +95,7 @@ static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceAdded(
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// maybe MPV can go over to the prefered device once it is plugged in?
|
||||
// maybe MPV can go over to the preferred device once it is plugged in?
|
||||
static HRESULT STDMETHODCALLTYPE sIMMNotificationClient_OnDeviceRemoved(
|
||||
IMMNotificationClient *This,
|
||||
LPCWSTR pwstrDeviceId)
|
||||
|
||||
@@ -407,7 +407,7 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
}
|
||||
|
||||
entry = ao->channels;
|
||||
if (ao_chmap_sel_adjust(ao, &chmap_sel, &entry)){
|
||||
if (ao_chmap_sel_adjust2(ao, &chmap_sel, &entry, !state->opt_exclusive)){
|
||||
change_waveformat_channels(wformat, &entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ struct ao {
|
||||
struct encode_lavc_context *encode_lavc_ctx;
|
||||
struct input_ctx *input_ctx;
|
||||
struct mp_log *log; // Using e.g. "[ao/coreaudio]" as prefix
|
||||
int init_flags; // AO_INIT_* flags
|
||||
bool stream_silence; // if audio inactive, just play silence
|
||||
|
||||
// The device as selected by the user, usually using ao_device_desc.name
|
||||
// from an entry from the list returned by driver->list_devices. If the
|
||||
@@ -191,6 +193,8 @@ void ao_wakeup_poll(struct ao *ao);
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map);
|
||||
bool ao_chmap_sel_adjust2(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map, bool safe_multichannel);
|
||||
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map, int num);
|
||||
|
||||
|
||||
+6
-2
@@ -185,7 +185,7 @@ static double get_delay(struct ao *ao)
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct ao_pull_state *p = ao->api_priv;
|
||||
if (ao->driver->reset)
|
||||
if (!ao->stream_silence && ao->driver->reset)
|
||||
ao->driver->reset(ao); // assumes the audio callback thread is stopped
|
||||
set_state(ao, AO_STATE_NONE);
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
@@ -195,7 +195,7 @@ static void reset(struct ao *ao)
|
||||
|
||||
static void pause(struct ao *ao)
|
||||
{
|
||||
if (ao->driver->reset)
|
||||
if (!ao->stream_silence && ao->driver->reset)
|
||||
ao->driver->reset(ao);
|
||||
set_state(ao, AO_STATE_NONE);
|
||||
}
|
||||
@@ -244,6 +244,10 @@ static int init(struct ao *ao)
|
||||
p->buffers[n] = mp_ring_new(ao, ao->buffer * ao->sstride);
|
||||
atomic_store(&p->state, AO_STATE_NONE);
|
||||
assert(ao->driver->resume);
|
||||
|
||||
if (ao->stream_silence)
|
||||
ao->driver->resume(ao);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+20
-11
@@ -142,6 +142,7 @@ static void resume(struct ao *ao)
|
||||
static void drain(struct ao *ao)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
double maxbuffer = ao->buffer / (double)ao->samplerate + 1;
|
||||
|
||||
MP_VERBOSE(ao, "draining...\n");
|
||||
|
||||
@@ -151,14 +152,23 @@ static void drain(struct ao *ao)
|
||||
|
||||
p->final_chunk = true;
|
||||
wakeup_playthread(ao);
|
||||
while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0)
|
||||
pthread_cond_wait(&p->wakeup, &p->lock);
|
||||
|
||||
// Wait until everything is done. Since the audio API (especially ALSA)
|
||||
// can't be trusted to do this right, and we're hard-blocking here, apply
|
||||
// an upper bound timeout.
|
||||
struct timespec until = mp_rel_time_to_timespec(maxbuffer);
|
||||
while (p->still_playing && mp_audio_buffer_samples(p->buffer) > 0) {
|
||||
if (pthread_cond_timedwait(&p->wakeup, &p->lock, &until)) {
|
||||
MP_WARN(ao, "Draining is taking too long, aborting.\n");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
if (ao->driver->drain) {
|
||||
ao->driver->drain(ao);
|
||||
} else {
|
||||
double time = unlocked_get_delay(ao);
|
||||
mp_sleep_us(MPMIN(time, ao->buffer / (double)ao->samplerate + 1) * 1e6);
|
||||
mp_sleep_us(MPMIN(time, maxbuffer) * 1e6);
|
||||
}
|
||||
|
||||
done:
|
||||
@@ -292,11 +302,12 @@ static void ao_play_data(struct ao *ao)
|
||||
// If we just filled the AO completely (r == space), don't refill for a
|
||||
// while. Prevents wakeup feedback with byte-granular AOs.
|
||||
int needed = unlocked_get_space(ao);
|
||||
bool more = needed >= (r == space ? ao->device_buffer / 4 : 1) && !stuck;
|
||||
bool more = needed >= (r == space ? ao->device_buffer / 4 : 1) && !stuck &&
|
||||
!(flags & AOPLAY_FINAL_CHUNK);
|
||||
if (more)
|
||||
mp_input_wakeup(ao->input_ctx); // request more data
|
||||
MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa=%d needed=%d more=%d\n",
|
||||
max, flags, space, r, p->wait_on_ao, needed, more);
|
||||
MP_TRACE(ao, "in=%d flags=%d space=%d r=%d wa/pl=%d/%d needed=%d more=%d\n",
|
||||
max, flags, space, r, p->wait_on_ao, p->still_playing, needed, more);
|
||||
}
|
||||
|
||||
static void *playthread(void *arg)
|
||||
@@ -486,10 +497,8 @@ int ao_wait_poll(struct ao *ao, struct pollfd *fds, int num_fds,
|
||||
bool wakeup = false;
|
||||
if (p_fds[num_fds].revents & POLLIN) {
|
||||
wakeup = true;
|
||||
// flush the wakeup pipe contents - might "drown" some wakeups, but
|
||||
// that's ok for our use-case
|
||||
char buf[100];
|
||||
read(p->wakeup_pipe[0], buf, sizeof(buf));
|
||||
// might "drown" some wakeups, but that's ok for our use-case
|
||||
mp_flush_wakeup_pipe(p->wakeup_pipe[0]);
|
||||
}
|
||||
return (r >= 0 || r == -EINTR) ? wakeup : -1;
|
||||
}
|
||||
@@ -499,7 +508,7 @@ void ao_wakeup_poll(struct ao *ao)
|
||||
assert(ao->api == &ao_api_push);
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
|
||||
write(p->wakeup_pipe[1], &(char){0}, 1);
|
||||
(void)write(p->wakeup_pipe[1], &(char){0}, 1);
|
||||
}
|
||||
|
||||
#endif
|
||||
+29
-2
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/log.h>
|
||||
@@ -84,6 +85,30 @@ void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
#endif
|
||||
}
|
||||
|
||||
// Pick a "good" timebase, which will be used to convert double timestamps
|
||||
// back to fractions for passing them through libavcodec.
|
||||
AVRational mp_get_codec_timebase(struct mp_codec_params *c)
|
||||
{
|
||||
AVRational tb = {c->native_tb_num, c->native_tb_den};
|
||||
if (tb.num < 1 || tb.den < 1) {
|
||||
if (c->reliable_fps)
|
||||
tb = av_inv_q(av_d2q(c->fps, 1000000));
|
||||
if (tb.num < 1 || tb.den < 1)
|
||||
tb = AV_TIME_BASE_Q;
|
||||
}
|
||||
|
||||
// If the timebase is too coarse, raise its precision, or small adjustments
|
||||
// to timestamps done between decoder and demuxer could be lost.
|
||||
if (av_q2d(tb) > 0.001) {
|
||||
AVRational r = av_div_q(tb, (AVRational){1, 1000});
|
||||
tb.den *= (r.num + r.den - 1) / r.den;
|
||||
}
|
||||
|
||||
av_reduce(&tb.num, &tb.den, tb.num, tb.den, 1000000);
|
||||
|
||||
return tb;
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
union pts { int64_t i; double d; };
|
||||
|
||||
@@ -97,8 +122,10 @@ union pts { int64_t i; double d; };
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb)
|
||||
{
|
||||
assert(sizeof(int64_t) >= sizeof(double));
|
||||
if (tb && tb->num > 0 && tb->den > 0)
|
||||
return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : mp_pts / av_q2d(*tb);
|
||||
if (tb && tb->num > 0 && tb->den > 0) {
|
||||
return mp_pts == MP_NOPTS_VALUE ?
|
||||
AV_NOPTS_VALUE : llrint(mp_pts / av_q2d(*tb));
|
||||
}
|
||||
// The + 0.0 is to squash possible negative zero mp_pts, which would
|
||||
// happen to end up as AV_NOPTS_VALUE.
|
||||
return (union pts){.d = mp_pts + 0.0}.i;
|
||||
|
||||
@@ -33,6 +33,7 @@ struct mp_log;
|
||||
int mp_lavc_set_extradata(AVCodecContext *avctx, void *ptr, int size);
|
||||
void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st);
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c);
|
||||
AVRational mp_get_codec_timebase(struct mp_codec_params *c);
|
||||
void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb);
|
||||
int64_t mp_pts_to_av(double mp_pts, AVRational *tb);
|
||||
double mp_pts_from_av(int64_t av_pts, AVRational *tb);
|
||||
|
||||
+7
-2
@@ -189,7 +189,7 @@ struct lib {
|
||||
unsigned runv;
|
||||
};
|
||||
|
||||
void print_libav_versions(struct mp_log *log, int v)
|
||||
bool print_libav_versions(struct mp_log *log, int v)
|
||||
{
|
||||
const struct lib libs[] = {
|
||||
{"libavutil", LIBAVUTIL_VERSION_INT, avutil_version()},
|
||||
@@ -207,17 +207,22 @@ void print_libav_versions(struct mp_log *log, int v)
|
||||
|
||||
mp_msg(log, v, "%s library versions:\n", LIB_PREFIX);
|
||||
|
||||
bool mismatch = false;
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(libs); n++) {
|
||||
const struct lib *l = &libs[n];
|
||||
mp_msg(log, v, " %-15s %d.%d.%d", l->name, V(l->buildv));
|
||||
if (l->buildv != l->runv)
|
||||
if (l->buildv != l->runv) {
|
||||
mp_msg(log, v, " (runtime %d.%d.%d)", V(l->runv));
|
||||
mismatch = true;
|
||||
}
|
||||
mp_msg(log, v, "\n");
|
||||
}
|
||||
|
||||
#if HAVE_AV_VERSION_INFO
|
||||
mp_msg(log, v, "%s version: %s\n", LIB_PREFIX, av_version_info());
|
||||
#endif
|
||||
|
||||
return !mismatch;
|
||||
}
|
||||
|
||||
#undef V
|
||||
+4
-1
@@ -1,8 +1,11 @@
|
||||
#ifndef MP_AV_LOG_H
|
||||
#define MP_AV_LOG_H
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct mpv_global;
|
||||
struct mp_log;
|
||||
void init_libav(struct mpv_global *global);
|
||||
void uninit_libav(struct mpv_global *global);
|
||||
void print_libav_versions(struct mp_log *log, int v);
|
||||
bool print_libav_versions(struct mp_log *log, int v);
|
||||
#endif
|
||||
+176
-148
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <libavutil/avutil.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "encode_lavc.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
@@ -291,32 +292,20 @@ int encode_lavc_start(struct encode_lavc_context *ctx)
|
||||
|
||||
CHECK_FAIL(ctx, 0);
|
||||
|
||||
if (ctx->expect_video) {
|
||||
unsigned i;
|
||||
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
||||
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
|
||||
break;
|
||||
if (i >= ctx->avc->nb_streams) {
|
||||
if (ctx->avc->oformat->video_codec != AV_CODEC_ID_NONE ||
|
||||
ctx->options->vcodec) {
|
||||
encode_lavc_fail(ctx,
|
||||
"no video stream succeeded - invalid codec?\n");
|
||||
return 0;
|
||||
}
|
||||
if (ctx->expect_video && ctx->vcc == NULL) {
|
||||
if (ctx->avc->oformat->video_codec != AV_CODEC_ID_NONE ||
|
||||
ctx->options->vcodec) {
|
||||
encode_lavc_fail(ctx,
|
||||
"no video stream succeeded - invalid codec?\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (ctx->expect_audio) {
|
||||
unsigned i;
|
||||
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
||||
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
break;
|
||||
if (i >= ctx->avc->nb_streams) {
|
||||
if (ctx->avc->oformat->audio_codec != AV_CODEC_ID_NONE ||
|
||||
ctx->options->acodec) {
|
||||
encode_lavc_fail(ctx,
|
||||
"no audio stream succeeded - invalid codec?\n");
|
||||
return 0;
|
||||
}
|
||||
if (ctx->expect_audio && ctx->acc == NULL) {
|
||||
if (ctx->avc->oformat->audio_codec != AV_CODEC_ID_NONE ||
|
||||
ctx->options->acodec) {
|
||||
encode_lavc_fail(ctx,
|
||||
"no audio stream succeeded - invalid codec?\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,33 +376,38 @@ void encode_lavc_finish(struct encode_lavc_context *ctx)
|
||||
if (ctx->header_written > 0)
|
||||
av_write_trailer(ctx->avc); // this is allowed to fail
|
||||
|
||||
for (i = 0; i < ctx->avc->nb_streams; i++) {
|
||||
switch (ctx->avc->streams[i]->codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (ctx->twopass_bytebuffer_v) {
|
||||
char *stats = ctx->avc->streams[i]->codec->stats_out;
|
||||
if (stats)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_v,
|
||||
stats, strlen(stats));
|
||||
}
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (ctx->twopass_bytebuffer_a) {
|
||||
char *stats = ctx->avc->streams[i]->codec->stats_out;
|
||||
if (stats)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_a,
|
||||
stats, strlen(stats));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
if (ctx->vcc) {
|
||||
if (ctx->twopass_bytebuffer_v) {
|
||||
char *stats = ctx->vcc->stats_out;
|
||||
if (stats)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_v,
|
||||
stats, strlen(stats));
|
||||
}
|
||||
avcodec_close(ctx->avc->streams[i]->codec);
|
||||
talloc_free(ctx->avc->streams[i]->codec->stats_in);
|
||||
av_free(ctx->avc->streams[i]->codec);
|
||||
avcodec_close(ctx->vcc);
|
||||
talloc_free(ctx->vcc->stats_in);
|
||||
av_free(ctx->vcc);
|
||||
ctx->vcc = NULL;
|
||||
}
|
||||
|
||||
if (ctx->acc) {
|
||||
if (ctx->twopass_bytebuffer_a) {
|
||||
char *stats = ctx->acc->stats_out;
|
||||
if (stats)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_a,
|
||||
stats, strlen(stats));
|
||||
}
|
||||
avcodec_close(ctx->acc);
|
||||
talloc_free(ctx->acc->stats_in);
|
||||
av_free(ctx->acc);
|
||||
ctx->acc = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ctx->avc->nb_streams; i++) {
|
||||
av_free(ctx->avc->streams[i]->info);
|
||||
av_free(ctx->avc->streams[i]);
|
||||
}
|
||||
ctx->vst = NULL;
|
||||
ctx->ast = NULL;
|
||||
|
||||
if (ctx->twopass_bytebuffer_v) {
|
||||
free_stream(ctx->twopass_bytebuffer_v);
|
||||
@@ -437,6 +431,7 @@ void encode_lavc_finish(struct encode_lavc_context *ctx)
|
||||
}
|
||||
|
||||
av_free(ctx->avc);
|
||||
ctx->avc = NULL;
|
||||
}
|
||||
|
||||
ctx->finished = true;
|
||||
@@ -461,7 +456,9 @@ void encode_lavc_set_audio_pts(struct encode_lavc_context *ctx, double pts)
|
||||
|
||||
static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
AVDictionary **dictp,
|
||||
AVStream *stream, struct stream **bytebuf,
|
||||
AVStream *stream,
|
||||
AVCodecContext *codec,
|
||||
struct stream **bytebuf,
|
||||
const char *prefix)
|
||||
{
|
||||
if (!*bytebuf) {
|
||||
@@ -476,7 +473,7 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
if (!(*bytebuf = stream_open(buf, ctx->global))) {
|
||||
MP_WARN(ctx, "%s: could not open '%s', "
|
||||
"disabling 2-pass encoding at pass 2\n", prefix, buf);
|
||||
stream->codec->flags &= ~CODEC_FLAG_PASS2;
|
||||
codec->flags &= ~CODEC_FLAG_PASS2;
|
||||
set_to_avdictionary(ctx, dictp, "flags", "-pass2");
|
||||
} else {
|
||||
struct bstr content = stream_read_complete(*bytebuf, NULL,
|
||||
@@ -487,7 +484,7 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
prefix, ctx->avc->filename);
|
||||
} else {
|
||||
content.start[content.len] = 0;
|
||||
stream->codec->stats_in = content.start;
|
||||
codec->stats_in = content.start;
|
||||
}
|
||||
free_stream(*bytebuf);
|
||||
*bytebuf = NULL;
|
||||
@@ -506,43 +503,55 @@ static void encode_2pass_prepare(struct encode_lavc_context *ctx,
|
||||
}
|
||||
}
|
||||
|
||||
AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
enum AVMediaType mt)
|
||||
int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
enum AVMediaType mt,
|
||||
AVStream **stream_out,
|
||||
AVCodecContext **codec_out)
|
||||
{
|
||||
AVDictionaryEntry *de;
|
||||
AVStream *stream = NULL;
|
||||
char **p;
|
||||
int i;
|
||||
|
||||
CHECK_FAIL(ctx, NULL);
|
||||
*stream_out = NULL;
|
||||
*codec_out = NULL;
|
||||
|
||||
CHECK_FAIL(ctx, -1);
|
||||
|
||||
if (ctx->header_written)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
||||
if (ctx->avc->streams[i]->codec->codec_type == mt)
|
||||
// already have a stream of that type, this cannot really happen
|
||||
return NULL;
|
||||
return -1;
|
||||
|
||||
if (ctx->avc->nb_streams == 0) {
|
||||
// if this stream isn't stream #0, allocate a dummy stream first for
|
||||
// the next loop to use
|
||||
// the next call to use
|
||||
if (mt == AVMEDIA_TYPE_VIDEO && ctx->audio_first) {
|
||||
MP_INFO(ctx, "vo-lavc: preallocated audio stream for later use\n");
|
||||
avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
||||
ctx->ast = avformat_new_stream(
|
||||
ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
||||
}
|
||||
if (mt == AVMEDIA_TYPE_AUDIO && ctx->video_first) {
|
||||
MP_INFO(ctx, "ao-lavc: preallocated video stream for later use\n");
|
||||
avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
||||
ctx->vst = avformat_new_stream(
|
||||
ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now
|
||||
}
|
||||
} else {
|
||||
// find possibly preallocated stream
|
||||
for (i = 0; i < ctx->avc->nb_streams; ++i)
|
||||
if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_UNKNOWN) // preallocated stream
|
||||
stream = ctx->avc->streams[i];
|
||||
}
|
||||
if (!stream)
|
||||
stream = avformat_new_stream(ctx->avc, NULL);
|
||||
|
||||
// already have a stream of that type (this cannot really happen)?
|
||||
switch (mt) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (ctx->vcc != NULL)
|
||||
return -1;
|
||||
if (ctx->vst == NULL)
|
||||
ctx->vst = avformat_new_stream(ctx->avc, NULL);
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (ctx->acc != NULL)
|
||||
return -1;
|
||||
if (ctx->ast == NULL)
|
||||
ctx->ast = avformat_new_stream(ctx->avc, NULL);
|
||||
break;
|
||||
default:
|
||||
encode_lavc_fail(ctx, "requested invalid stream type\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->timebase.den == 0) {
|
||||
AVRational r;
|
||||
@@ -584,13 +593,18 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
ctx->options->vcodec) {
|
||||
encode_lavc_fail(ctx, "vo-lavc: encoder not found\n");
|
||||
}
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
avcodec_get_context_defaults3(stream->codec, ctx->vc);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
ctx->vcc = avcodec_alloc_context3(ctx->vc);
|
||||
#else
|
||||
avcodec_get_context_defaults3(ctx->vst->codec, ctx->vc);
|
||||
ctx->vcc = ctx->vst->codec;
|
||||
#endif
|
||||
|
||||
// Using codec->time_base is deprecated, but needed for older lavf.
|
||||
stream->time_base = ctx->timebase;
|
||||
stream->codec->time_base = ctx->timebase;
|
||||
ctx->vst->time_base = ctx->timebase;
|
||||
ctx->vcc->time_base = ctx->timebase;
|
||||
|
||||
ctx->voptions = NULL;
|
||||
|
||||
@@ -606,10 +620,12 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
set_to_avdictionary(ctx, &ctx->voptions, "flags", "+global_header");
|
||||
|
||||
encode_2pass_prepare(ctx, &ctx->voptions, stream,
|
||||
encode_2pass_prepare(ctx, &ctx->voptions, ctx->vst, ctx->vcc,
|
||||
&ctx->twopass_bytebuffer_v,
|
||||
"vo-lavc");
|
||||
break;
|
||||
*stream_out = ctx->vst;
|
||||
*codec_out = ctx->vcc;
|
||||
return 0;
|
||||
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (!ctx->ac) {
|
||||
@@ -617,15 +633,20 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
ctx->options->acodec) {
|
||||
encode_lavc_fail(ctx, "ao-lavc: encoder not found\n");
|
||||
}
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
avcodec_get_context_defaults3(stream->codec, ctx->ac);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
ctx->acc = avcodec_alloc_context3(ctx->ac);
|
||||
#else
|
||||
avcodec_get_context_defaults3(ctx->ast->codec, ctx->ac);
|
||||
ctx->acc = ctx->ast->codec;
|
||||
#endif
|
||||
|
||||
// Using codec->time_base is deprecated, but needed for older lavf.
|
||||
stream->time_base = ctx->timebase;
|
||||
stream->codec->time_base = ctx->timebase;
|
||||
ctx->ast->time_base = ctx->timebase;
|
||||
ctx->acc->time_base = ctx->timebase;
|
||||
|
||||
ctx->aoptions = NULL;
|
||||
ctx->aoptions = 0;
|
||||
|
||||
if (ctx->options->aopts)
|
||||
for (p = ctx->options->aopts; *p; ++p)
|
||||
@@ -639,49 +660,34 @@ AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
set_to_avdictionary(ctx, &ctx->aoptions, "flags", "+global_header");
|
||||
|
||||
encode_2pass_prepare(ctx, &ctx->aoptions, stream,
|
||||
encode_2pass_prepare(ctx, &ctx->aoptions, ctx->ast, ctx->acc,
|
||||
&ctx->twopass_bytebuffer_a,
|
||||
"ao-lavc");
|
||||
break;
|
||||
|
||||
default:
|
||||
encode_lavc_fail(ctx, "requested invalid stream type\n");
|
||||
return NULL;
|
||||
*stream_out = ctx->ast;
|
||||
*codec_out = ctx->acc;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return stream;
|
||||
// Unreachable.
|
||||
return -1;
|
||||
}
|
||||
|
||||
AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx,
|
||||
AVStream *stream)
|
||||
{
|
||||
CHECK_FAIL(ctx, NULL);
|
||||
|
||||
switch (stream->codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
return ctx->vc;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
return ctx->ac;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
AVDictionaryEntry *de;
|
||||
int ret;
|
||||
|
||||
CHECK_FAIL(ctx, -1);
|
||||
|
||||
switch (stream->codec->codec_type) {
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
MP_INFO(ctx, "Opening video encoder: %s [%s]\n",
|
||||
ctx->vc->long_name, ctx->vc->name);
|
||||
|
||||
if (ctx->vc->capabilities & CODEC_CAP_EXPERIMENTAL) {
|
||||
stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
MP_WARN(ctx, "\n\n"
|
||||
" ********************************************\n"
|
||||
" **** Experimental VIDEO codec selected! ****\n"
|
||||
@@ -701,7 +707,11 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
ctx->vc->name);
|
||||
}
|
||||
|
||||
ret = avcodec_open2(stream->codec, ctx->vc, &ctx->voptions);
|
||||
ret = avcodec_open2(codec, ctx->vc, &ctx->voptions);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (ret >= 0)
|
||||
ret = avcodec_parameters_from_context(ctx->vst->codecpar, codec);
|
||||
#endif
|
||||
|
||||
// complain about all remaining options, then free the dict
|
||||
for (de = NULL; (de = av_dict_get(ctx->voptions, "", de,
|
||||
@@ -716,7 +726,7 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
ctx->ac->long_name, ctx->ac->name);
|
||||
|
||||
if (ctx->ac->capabilities & CODEC_CAP_EXPERIMENTAL) {
|
||||
stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
|
||||
MP_WARN(ctx, "\n\n"
|
||||
" ********************************************\n"
|
||||
" **** Experimental AUDIO codec selected! ****\n"
|
||||
@@ -735,7 +745,12 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
"If none of this helps you, try another codec in place of %s.\n\n",
|
||||
ctx->ac->name);
|
||||
}
|
||||
ret = avcodec_open2(stream->codec, ctx->ac, &ctx->aoptions);
|
||||
|
||||
ret = avcodec_open2(codec, ctx->ac, &ctx->aoptions);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (ret >= 0)
|
||||
ret = avcodec_parameters_from_context(ctx->ast->codecpar, codec);
|
||||
#endif
|
||||
|
||||
// complain about all remaining options, then free the dict
|
||||
for (de = NULL; (de = av_dict_get(ctx->aoptions, "", de,
|
||||
@@ -757,36 +772,43 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
return ret;
|
||||
}
|
||||
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
CHECK_FAIL(ctx, );
|
||||
|
||||
switch (stream->codec->codec_type) {
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
if (ctx->twopass_bytebuffer_v)
|
||||
if (stream->codec->stats_out)
|
||||
if (codec->stats_out)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_v,
|
||||
stream->codec->stats_out,
|
||||
strlen(stream->codec->stats_out));
|
||||
codec->stats_out,
|
||||
strlen(codec->stats_out));
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
if (ctx->twopass_bytebuffer_a)
|
||||
if (stream->codec->stats_out)
|
||||
if (codec->stats_out)
|
||||
stream_write_buffer(ctx->twopass_bytebuffer_a,
|
||||
stream->codec->stats_out,
|
||||
strlen(stream->codec->stats_out));
|
||||
codec->stats_out,
|
||||
strlen(codec->stats_out));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet)
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
|
||||
AVPacket *packet)
|
||||
{
|
||||
int r;
|
||||
|
||||
CHECK_FAIL(ctx, -1);
|
||||
|
||||
if (stream->index != packet->stream_index) {
|
||||
MP_ERR(ctx, "Called encode_lavc_write_frame on the wrong stream\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ctx->header_written <= 0)
|
||||
return -1;
|
||||
|
||||
@@ -795,27 +817,32 @@ int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet)
|
||||
(int)packet->stream_index,
|
||||
(int)packet->pts,
|
||||
packet->pts
|
||||
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
|
||||
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den,
|
||||
* (double)stream->time_base.num
|
||||
/ (double)stream->time_base.den,
|
||||
(int)packet->dts,
|
||||
packet->dts
|
||||
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
|
||||
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den,
|
||||
* (double)stream->time_base.num
|
||||
/ (double)stream->time_base.den,
|
||||
(int)packet->size);
|
||||
|
||||
switch (ctx->avc->streams[packet->stream_index]->codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
ctx->vbytes += packet->size;
|
||||
++ctx->frames;
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
ctx->abytes += packet->size;
|
||||
ctx->audioseconds += packet->duration
|
||||
* (double)ctx->avc->streams[packet->stream_index]->time_base.num
|
||||
/ (double)ctx->avc->streams[packet->stream_index]->time_base.den;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
switch (stream->codecpar->codec_type) {
|
||||
#else
|
||||
switch (stream->codec->codec_type) {
|
||||
#endif
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
ctx->vbytes += packet->size;
|
||||
++ctx->frames;
|
||||
break;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
ctx->abytes += packet->size;
|
||||
ctx->audioseconds += packet->duration
|
||||
* (double)stream->time_base.num
|
||||
/ (double)stream->time_base.den;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
r = av_interleaved_write_frame(ctx->avc, packet);
|
||||
@@ -1062,11 +1089,12 @@ bool encode_lavc_showhelp(struct mp_log *log, struct encode_opts *opts)
|
||||
return help_output;
|
||||
}
|
||||
|
||||
double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream)
|
||||
double encode_lavc_getoffset(struct encode_lavc_context *ctx,
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
CHECK_FAIL(ctx, 0);
|
||||
|
||||
switch (stream->codec->codec_type) {
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_VIDEO:
|
||||
return ctx->options->voffset;
|
||||
case AVMEDIA_TYPE_AUDIO:
|
||||
@@ -1151,49 +1179,49 @@ void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...)
|
||||
}
|
||||
|
||||
bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
|
||||
AVStream *stream, enum mp_csp csp)
|
||||
AVCodecContext *codec, enum mp_csp csp)
|
||||
{
|
||||
CHECK_FAIL(ctx, NULL);
|
||||
|
||||
if (ctx->header_written) {
|
||||
if (stream->codec->colorspace != mp_csp_to_avcol_spc(csp))
|
||||
if (codec->colorspace != mp_csp_to_avcol_spc(csp))
|
||||
MP_WARN(ctx, "can not change color space during encoding\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
stream->codec->colorspace = mp_csp_to_avcol_spc(csp);
|
||||
codec->colorspace = mp_csp_to_avcol_spc(csp);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVStream *stream, enum mp_csp_levels lev)
|
||||
AVCodecContext *codec, enum mp_csp_levels lev)
|
||||
{
|
||||
CHECK_FAIL(ctx, NULL);
|
||||
|
||||
if (ctx->header_written) {
|
||||
if (stream->codec->color_range != mp_csp_levels_to_avcol_range(lev))
|
||||
if (codec->color_range != mp_csp_levels_to_avcol_range(lev))
|
||||
MP_WARN(ctx, "can not change color space during encoding\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
stream->codec->color_range = mp_csp_levels_to_avcol_range(lev);
|
||||
codec->color_range = mp_csp_levels_to_avcol_range(lev);
|
||||
return true;
|
||||
}
|
||||
|
||||
enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
|
||||
AVStream *stream)
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
CHECK_FAIL(ctx, 0);
|
||||
|
||||
return avcol_spc_to_mp_csp(stream->codec->colorspace);
|
||||
return avcol_spc_to_mp_csp(codec->colorspace);
|
||||
}
|
||||
|
||||
enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVStream *stream)
|
||||
AVCodecContext *codec)
|
||||
{
|
||||
CHECK_FAIL(ctx, 0);
|
||||
|
||||
return avcol_range_to_mp_csp_levels(stream->codec->color_range);
|
||||
return avcol_range_to_mp_csp_levels(codec->color_range);
|
||||
}
|
||||
|
||||
// vim: ts=4 sw=4 et
|
||||
+22
-11
@@ -46,8 +46,14 @@ struct encode_lavc_context {
|
||||
|
||||
float vo_fps;
|
||||
|
||||
// these are processed from the options
|
||||
// FFmpeg contexts.
|
||||
AVFormatContext *avc;
|
||||
AVStream *vst;
|
||||
AVStream *ast;
|
||||
AVCodecContext *vcc;
|
||||
AVCodecContext *acc;
|
||||
|
||||
// these are processed from the options
|
||||
AVRational timebase;
|
||||
AVCodec *vc;
|
||||
AVCodec *ac;
|
||||
@@ -88,26 +94,31 @@ struct encode_lavc_context {
|
||||
};
|
||||
|
||||
// interface for vo/ao drivers
|
||||
AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx, enum AVMediaType mt);
|
||||
void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream);
|
||||
int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet);
|
||||
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);
|
||||
AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx, AVStream *stream);
|
||||
int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream);
|
||||
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, AVStream *stream);
|
||||
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
|
||||
|
||||
bool encode_lavc_set_csp(struct encode_lavc_context *ctx,
|
||||
AVStream *stream, enum mp_csp csp);
|
||||
AVCodecContext *codec, enum mp_csp csp);
|
||||
bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVStream *stream, enum mp_csp_levels lev);
|
||||
AVCodecContext *codec, enum mp_csp_levels lev);
|
||||
enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx,
|
||||
AVStream *stream);
|
||||
AVCodecContext *codec);
|
||||
enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx,
|
||||
AVStream *stream);
|
||||
AVCodecContext *codec);
|
||||
|
||||
#endif
|
||||
+250
-121
@@ -119,16 +119,20 @@ struct demux_internal {
|
||||
int max_packs;
|
||||
int max_bytes;
|
||||
|
||||
// Set if we know that we are at the start of the file. This is used to
|
||||
// avoid a redundant initial seek after enabling streams. We could just
|
||||
// allow it, but to avoid buggy seeking affecting normal playback, we don't.
|
||||
bool initial_state;
|
||||
|
||||
bool tracks_switched; // thread needs to inform demuxer of this
|
||||
|
||||
bool seeking; // there's a seek queued
|
||||
int seek_flags; // flags for next seek (if seeking==true)
|
||||
double seek_pts;
|
||||
|
||||
bool refresh_seeks_enabled;
|
||||
bool start_refresh_seek;
|
||||
double ref_pts; // assumed player position (only for track switches)
|
||||
|
||||
double ts_offset; // timestamp offset to apply everything
|
||||
double ts_offset; // timestamp offset to apply to everything
|
||||
|
||||
void (*run_fn)(void *); // if non-NULL, function queued to be run on
|
||||
void *run_fn_arg; // the thread as run_fn(run_fn_arg)
|
||||
@@ -152,7 +156,10 @@ struct demux_stream {
|
||||
// if false, this stream is disabled, or passively
|
||||
// read (like subtitles)
|
||||
bool eof; // end of demuxed stream? (true if all buffer empty)
|
||||
bool need_refresh; // enabled mid-stream
|
||||
bool refreshing;
|
||||
bool correct_dts; // packet DTS is strictly monotonically increasing
|
||||
bool correct_pos; // packet pos is strictly monotonically increasing
|
||||
size_t packs; // number of packets in buffer
|
||||
size_t bytes; // total bytes of packets in buffer
|
||||
double base_ts; // timestamp of the last packet returned to decoder
|
||||
@@ -161,12 +168,12 @@ struct demux_stream {
|
||||
size_t last_br_bytes; // summed packet sizes since last bitrate calculation
|
||||
double bitrate;
|
||||
int64_t last_pos;
|
||||
double last_dts;
|
||||
struct demux_packet *head;
|
||||
struct demux_packet *tail;
|
||||
|
||||
// for closed captions (demuxer_feed_caption)
|
||||
struct sh_stream *cc;
|
||||
|
||||
};
|
||||
|
||||
// Return "a", or if that is NOPTS, return "def".
|
||||
@@ -199,7 +206,10 @@ static void ds_flush(struct demux_stream *ds)
|
||||
ds->eof = false;
|
||||
ds->active = false;
|
||||
ds->refreshing = false;
|
||||
ds->need_refresh = false;
|
||||
ds->last_pos = -1;
|
||||
ds->last_dts = MP_NOPTS_VALUE;
|
||||
ds->correct_dts = ds->correct_pos = true;
|
||||
}
|
||||
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset)
|
||||
@@ -222,6 +232,7 @@ struct sh_stream *demux_alloc_sh_stream(enum stream_type type)
|
||||
.ff_index = -1, // may be overwritten by demuxer
|
||||
.demuxer_id = -1, // ... same
|
||||
.codec = talloc_zero(sh, struct mp_codec_params),
|
||||
.tags = talloc_zero(sh, struct mp_tags),
|
||||
};
|
||||
sh->codec->type = type;
|
||||
return sh;
|
||||
@@ -266,6 +277,33 @@ void demux_add_sh_stream(struct demuxer *demuxer, struct sh_stream *sh)
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
// Update sh->tags (lazily). This must be called by demuxers which update
|
||||
// stream tags after init. (sh->tags can be accessed by the playback thread,
|
||||
// which means the demuxer thread cannot write or read it directly.)
|
||||
// Before init is finished, sh->tags can still be accessed freely.
|
||||
// Ownership of tags goes to the function.
|
||||
void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
||||
struct mp_tags *tags)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(demuxer == in->d_thread);
|
||||
|
||||
if (sh->ds) {
|
||||
while (demuxer->num_update_stream_tags <= sh->index) {
|
||||
MP_TARRAY_APPEND(demuxer, demuxer->update_stream_tags,
|
||||
demuxer->num_update_stream_tags, NULL);
|
||||
}
|
||||
talloc_free(demuxer->update_stream_tags[sh->index]);
|
||||
demuxer->update_stream_tags[sh->index] = talloc_steal(demuxer, tags);
|
||||
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
} else {
|
||||
// not added yet
|
||||
talloc_free(sh->tags);
|
||||
sh->tags = talloc_steal(sh, tags);
|
||||
}
|
||||
}
|
||||
|
||||
// Return a stream with the given index. Since streams can only be added during
|
||||
// the lifetime of the demuxer, it is guaranteed that an index within the valid
|
||||
// range [0, demux_get_num_stream()) always returns a valid sh_stream pointer,
|
||||
@@ -389,6 +427,59 @@ void demuxer_feed_caption(struct sh_stream *stream, demux_packet_t *dp)
|
||||
demux_add_packet(sh, dp);
|
||||
}
|
||||
|
||||
// An obscure mechanism to get stream switching to be executed faster.
|
||||
// On a switch, it seeks back, and then grabs all packets that were
|
||||
// "missing" from the packet queue of the newly selected stream.
|
||||
// Returns MP_NOPTS_VALUE if no seek should happen.
|
||||
static double get_refresh_seek_pts(struct demux_internal *in)
|
||||
{
|
||||
struct demuxer *demux = in->d_thread;
|
||||
|
||||
double start_ts = in->ref_pts;
|
||||
bool needed = false;
|
||||
bool normal_seek = true;
|
||||
bool refresh_possible = true;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
|
||||
if (!ds->selected)
|
||||
continue;
|
||||
|
||||
if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
|
||||
start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
|
||||
|
||||
needed |= ds->need_refresh;
|
||||
// If there were no other streams selected, we can use a normal seek.
|
||||
normal_seek &= ds->need_refresh;
|
||||
ds->need_refresh = false;
|
||||
|
||||
refresh_possible &= ds->correct_dts || ds->correct_pos;
|
||||
}
|
||||
|
||||
if (!needed || start_ts == MP_NOPTS_VALUE || !demux->desc->seek ||
|
||||
!demux->seekable || demux->partially_seekable)
|
||||
return MP_NOPTS_VALUE;
|
||||
|
||||
if (normal_seek)
|
||||
return start_ts;
|
||||
|
||||
if (!refresh_possible) {
|
||||
MP_VERBOSE(in, "can't issue refresh seek\n");
|
||||
return MP_NOPTS_VALUE;
|
||||
}
|
||||
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
// Streams which didn't have any packets yet will return all packets,
|
||||
// other streams return packets only starting from the last position.
|
||||
if (ds->last_pos != -1 || ds->last_dts != MP_NOPTS_VALUE)
|
||||
ds->refreshing = true;
|
||||
}
|
||||
|
||||
// Seek back to player's current position, with a small offset added.
|
||||
return start_ts - 1.0;
|
||||
}
|
||||
|
||||
void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
{
|
||||
struct demux_stream *ds = stream ? stream->ds : NULL;
|
||||
@@ -399,25 +490,34 @@ void demux_add_packet(struct sh_stream *stream, demux_packet_t *dp)
|
||||
struct demux_internal *in = ds->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
bool drop = false;
|
||||
bool drop = ds->refreshing;
|
||||
if (ds->refreshing) {
|
||||
// Resume reading once the old position was reached (i.e. we start
|
||||
// returning packets where we left off before the refresh).
|
||||
drop = dp->pos <= ds->last_pos;
|
||||
if (dp->pos >= ds->last_pos)
|
||||
ds->refreshing = false;
|
||||
// If it's the same position, drop, but continue normally next time.
|
||||
if (ds->correct_dts) {
|
||||
ds->refreshing = dp->dts < ds->last_dts;
|
||||
} else if (ds->correct_pos) {
|
||||
ds->refreshing = dp->pos < ds->last_pos;
|
||||
} else {
|
||||
ds->refreshing = false; // should not happen
|
||||
}
|
||||
}
|
||||
|
||||
if (!ds->selected || in->seeking || drop) {
|
||||
if (!ds->selected || ds->need_refresh || in->seeking || drop) {
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
talloc_free(dp);
|
||||
return;
|
||||
}
|
||||
|
||||
ds->correct_pos &= dp->pos >= 0 && dp->pos > ds->last_pos;
|
||||
ds->correct_dts &= dp->dts != MP_NOPTS_VALUE && dp->dts > ds->last_dts;
|
||||
ds->last_pos = dp->pos;
|
||||
ds->last_dts = dp->dts;
|
||||
|
||||
dp->stream = stream->index;
|
||||
dp->next = NULL;
|
||||
|
||||
ds->last_pos = dp->pos;
|
||||
ds->packs++;
|
||||
ds->bytes += dp->len;
|
||||
if (ds->tail) {
|
||||
@@ -480,12 +580,12 @@ static bool read_packet(struct demux_internal *in)
|
||||
if (packs >= in->max_packs || bytes >= in->max_bytes) {
|
||||
if (!in->warned_queue_overflow) {
|
||||
in->warned_queue_overflow = true;
|
||||
MP_ERR(in, "Too many packets in the demuxer packet queues:\n");
|
||||
MP_WARN(in, "Too many packets in the demuxer packet queues:\n");
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->selected) {
|
||||
MP_ERR(in, " %s/%d: %zd packets, %zd bytes\n",
|
||||
stream_type_name(ds->type), n, ds->packs, ds->bytes);
|
||||
MP_WARN(in, " %s/%d: %zd packets, %zd bytes\n",
|
||||
stream_type_name(ds->type), n, ds->packs, ds->bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -505,27 +605,40 @@ static bool read_packet(struct demux_internal *in)
|
||||
if (!read_more)
|
||||
return false;
|
||||
|
||||
double seek_pts = get_refresh_seek_pts(in);
|
||||
|
||||
// Actually read a packet. Drop the lock while doing so, because waiting
|
||||
// for disk or network I/O can take time.
|
||||
in->idle = false;
|
||||
in->initial_state = false;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
|
||||
struct demuxer *demux = in->d_thread;
|
||||
|
||||
if (seek_pts != MP_NOPTS_VALUE) {
|
||||
MP_VERBOSE(in, "refresh seek to %f\n", seek_pts);
|
||||
demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
|
||||
}
|
||||
|
||||
bool eof = !demux->desc->fill_buffer || demux->desc->fill_buffer(demux) <= 0;
|
||||
update_cache(in);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
if (eof) {
|
||||
for (int n = 0; n < in->num_streams; n++)
|
||||
in->streams[n]->ds->eof = true;
|
||||
// If we had EOF previously, then don't wakeup (avoids wakeup loop)
|
||||
if (!in->last_eof) {
|
||||
if (in->wakeup_cb)
|
||||
in->wakeup_cb(in->wakeup_cb_ctx);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
MP_VERBOSE(in, "EOF reached.\n");
|
||||
if (!in->seeking) {
|
||||
if (eof) {
|
||||
for (int n = 0; n < in->num_streams; n++)
|
||||
in->streams[n]->ds->eof = true;
|
||||
// If we had EOF previously, then don't wakeup (avoids wakeup loop)
|
||||
if (!in->last_eof) {
|
||||
if (in->wakeup_cb)
|
||||
in->wakeup_cb(in->wakeup_cb_ctx);
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
MP_VERBOSE(in, "EOF reached.\n");
|
||||
}
|
||||
}
|
||||
in->eof = in->last_eof = eof;
|
||||
}
|
||||
in->eof = in->last_eof = eof;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -550,42 +663,6 @@ static void ds_get_packets(struct demux_stream *ds)
|
||||
}
|
||||
}
|
||||
|
||||
// An obscure mechanism to get stream switching to be executed faster.
|
||||
// On a switch, it seeks back, and then grabs all packets that were
|
||||
// "missing" from the packet queue of the newly selected stream.
|
||||
static void start_refreshing(struct demux_internal *in)
|
||||
{
|
||||
struct demuxer *demux = in->d_thread;
|
||||
|
||||
in->start_refresh_seek = false;
|
||||
|
||||
double start_ts = MP_NOPTS_VALUE;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->type == STREAM_VIDEO || ds->type == STREAM_AUDIO)
|
||||
start_ts = MP_PTS_MIN(start_ts, ds->base_ts);
|
||||
}
|
||||
|
||||
if (start_ts == MP_NOPTS_VALUE || !demux->desc->seek || !demux->seekable ||
|
||||
demux->partially_seekable || !demux->allow_refresh_seeks)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
// Streams which didn't read any packets yet can return all packets,
|
||||
// or they'd be stuck forever; affects newly selected streams too.
|
||||
if (ds->last_pos != -1)
|
||||
ds->refreshing = true;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
|
||||
// Seek back to player's current position, with a small offset added.
|
||||
in->d_thread->desc->seek(in->d_thread, start_ts - 1.0, SEEK_BACKWARD | SEEK_HR);
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
}
|
||||
|
||||
static void execute_trackswitch(struct demux_internal *in)
|
||||
{
|
||||
in->tracks_switched = false;
|
||||
@@ -603,9 +680,6 @@ static void execute_trackswitch(struct demux_internal *in)
|
||||
&(int){any_selected});
|
||||
|
||||
pthread_mutex_lock(&in->lock);
|
||||
|
||||
if (in->start_refresh_seek)
|
||||
start_refreshing(in);
|
||||
}
|
||||
|
||||
static void execute_seek(struct demux_internal *in)
|
||||
@@ -613,6 +687,7 @@ static void execute_seek(struct demux_internal *in)
|
||||
int flags = in->seek_flags;
|
||||
double pts = in->seek_pts;
|
||||
in->seeking = false;
|
||||
in->initial_state = false;
|
||||
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
|
||||
@@ -853,17 +928,18 @@ static int decode_float(char *str, float *out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_gain(demuxer_t *demuxer, const char *tag, float *out)
|
||||
static int decode_gain(struct mp_log *log, struct mp_tags *tags,
|
||||
const char *tag, float *out)
|
||||
{
|
||||
char *tag_val = NULL;
|
||||
float dec_val;
|
||||
|
||||
tag_val = mp_tags_get_str(demuxer->metadata, tag);
|
||||
tag_val = mp_tags_get_str(tags, tag);
|
||||
if (!tag_val)
|
||||
return -1;
|
||||
|
||||
if (decode_float(tag_val, &dec_val)) {
|
||||
mp_msg(demuxer->log, MSGL_ERR, "Invalid replaygain value\n");
|
||||
if (decode_float(tag_val, &dec_val) < 0) {
|
||||
mp_msg(log, MSGL_ERR, "Invalid replaygain value\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -871,62 +947,68 @@ static int decode_gain(demuxer_t *demuxer, const char *tag, float *out)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_peak(demuxer_t *demuxer, const char *tag, float *out)
|
||||
static int decode_peak(struct mp_log *log, struct mp_tags *tags,
|
||||
const char *tag, float *out)
|
||||
{
|
||||
char *tag_val = NULL;
|
||||
float dec_val;
|
||||
|
||||
*out = 1.0;
|
||||
|
||||
tag_val = mp_tags_get_str(demuxer->metadata, tag);
|
||||
tag_val = mp_tags_get_str(tags, tag);
|
||||
if (!tag_val)
|
||||
return 0;
|
||||
|
||||
if (decode_float(tag_val, &dec_val))
|
||||
return 0;
|
||||
|
||||
if (dec_val == 0.0)
|
||||
return 0;
|
||||
if (decode_float(tag_val, &dec_val) < 0 || dec_val <= 0.0)
|
||||
return -1;
|
||||
|
||||
*out = dec_val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apply_replaygain(demuxer_t *demuxer, struct replaygain_data *rg)
|
||||
static struct replaygain_data *decode_rgain(struct mp_log *log,
|
||||
struct mp_tags *tags)
|
||||
{
|
||||
struct replaygain_data rg = {0};
|
||||
|
||||
if (decode_gain(log, tags, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) >= 0 &&
|
||||
decode_peak(log, tags, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) >= 0)
|
||||
{
|
||||
if (decode_gain(log, tags, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) < 0 ||
|
||||
decode_peak(log, tags, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak) < 0)
|
||||
{
|
||||
rg.album_gain = rg.track_gain;
|
||||
rg.album_peak = rg.track_peak;
|
||||
}
|
||||
return talloc_memdup(NULL, &rg, sizeof(rg));
|
||||
}
|
||||
|
||||
if (decode_gain(log, tags, "REPLAYGAIN_GAIN", &rg.track_gain) >= 0 &&
|
||||
decode_peak(log, tags, "REPLAYGAIN_PEAK", &rg.track_peak) >= 0)
|
||||
{
|
||||
rg.album_gain = rg.track_gain;
|
||||
rg.album_peak = rg.track_peak;
|
||||
return talloc_memdup(NULL, &rg, sizeof(rg));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void demux_update_replaygain(demuxer_t *demuxer)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct sh_stream *sh = in->streams[n];
|
||||
if (sh->type == STREAM_AUDIO && !sh->codec->replaygain_data) {
|
||||
MP_VERBOSE(demuxer, "Replaygain: Track=%f/%f Album=%f/%f\n",
|
||||
rg->track_gain, rg->track_peak,
|
||||
rg->album_gain, rg->album_peak);
|
||||
sh->codec->replaygain_data = talloc_memdup(in, rg, sizeof(*rg));
|
||||
struct replaygain_data *rg = decode_rgain(demuxer->log, sh->tags);
|
||||
if (!rg)
|
||||
rg = decode_rgain(demuxer->log, demuxer->metadata);
|
||||
if (rg)
|
||||
sh->codec->replaygain_data = talloc_steal(in, rg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void demux_export_replaygain(demuxer_t *demuxer)
|
||||
{
|
||||
struct replaygain_data rg = {0};
|
||||
|
||||
if (!decode_gain(demuxer, "REPLAYGAIN_TRACK_GAIN", &rg.track_gain) &&
|
||||
!decode_peak(demuxer, "REPLAYGAIN_TRACK_PEAK", &rg.track_peak) &&
|
||||
!decode_gain(demuxer, "REPLAYGAIN_ALBUM_GAIN", &rg.album_gain) &&
|
||||
!decode_peak(demuxer, "REPLAYGAIN_ALBUM_PEAK", &rg.album_peak))
|
||||
{
|
||||
apply_replaygain(demuxer, &rg);
|
||||
}
|
||||
|
||||
if (!decode_gain(demuxer, "REPLAYGAIN_GAIN", &rg.track_gain) &&
|
||||
!decode_peak(demuxer, "REPLAYGAIN_PEAK", &rg.track_peak))
|
||||
{
|
||||
rg.album_gain = rg.track_gain;
|
||||
rg.album_peak = rg.track_peak;
|
||||
apply_replaygain(demuxer, &rg);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy all fields from src to dst, depending on event flags.
|
||||
static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
{
|
||||
@@ -947,15 +1029,30 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->partially_seekable = src->partially_seekable;
|
||||
dst->filetype = src->filetype;
|
||||
dst->ts_resets_possible = src->ts_resets_possible;
|
||||
dst->allow_refresh_seeks = src->allow_refresh_seeks;
|
||||
dst->fully_read = src->fully_read;
|
||||
dst->start_time = src->start_time;
|
||||
dst->is_network = src->is_network;
|
||||
dst->priv = src->priv;
|
||||
}
|
||||
|
||||
if (src->events & DEMUX_EVENT_METADATA) {
|
||||
talloc_free(dst->metadata);
|
||||
dst->metadata = mp_tags_dup(dst, src->metadata);
|
||||
|
||||
if (dst->num_update_stream_tags != src->num_update_stream_tags) {
|
||||
dst->num_update_stream_tags = src->num_update_stream_tags;
|
||||
talloc_free(dst->update_stream_tags);
|
||||
dst->update_stream_tags =
|
||||
talloc_zero_array(dst, struct mp_tags *, dst->num_update_stream_tags);
|
||||
}
|
||||
for (int n = 0; n < dst->num_update_stream_tags; n++) {
|
||||
talloc_free(dst->update_stream_tags[n]);
|
||||
dst->update_stream_tags[n] =
|
||||
talloc_steal(dst->update_stream_tags, src->update_stream_tags[n]);
|
||||
src->update_stream_tags[n] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
dst->events |= src->events;
|
||||
src->events = 0;
|
||||
}
|
||||
@@ -977,8 +1074,6 @@ void demux_changed(demuxer_t *demuxer, int events)
|
||||
|
||||
if (demuxer->events & DEMUX_EVENT_INIT)
|
||||
demuxer_sort_chapters(demuxer);
|
||||
if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
|
||||
demux_export_replaygain(demuxer);
|
||||
|
||||
demux_copy(in->d_buffer, demuxer);
|
||||
|
||||
@@ -1001,8 +1096,28 @@ void demux_update(demuxer_t *demuxer)
|
||||
demux_copy(demuxer, in->d_buffer);
|
||||
demuxer->events |= in->events;
|
||||
in->events = 0;
|
||||
if (in->stream_metadata && (demuxer->events & DEMUX_EVENT_METADATA))
|
||||
mp_tags_merge(demuxer->metadata, in->stream_metadata);
|
||||
if (demuxer->events & DEMUX_EVENT_METADATA) {
|
||||
int num_streams = MPMIN(in->num_streams, demuxer->num_update_stream_tags);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
struct mp_tags *tags = demuxer->update_stream_tags[n];
|
||||
demuxer->update_stream_tags[n] = NULL;
|
||||
if (tags) {
|
||||
struct sh_stream *sh = in->streams[n];
|
||||
talloc_free(sh->tags);
|
||||
sh->tags = talloc_steal(sh, tags);
|
||||
}
|
||||
}
|
||||
|
||||
// Often useful audio-only files, which have metadata in the audio track
|
||||
// metadata instead of the main metadata (especially OGG).
|
||||
if (in->num_streams == 1)
|
||||
mp_tags_merge(demuxer->metadata, in->streams[0]->tags);
|
||||
|
||||
if (in->stream_metadata)
|
||||
mp_tags_merge(demuxer->metadata, in->stream_metadata);
|
||||
}
|
||||
if (demuxer->events & (DEMUX_EVENT_METADATA | DEMUX_EVENT_STREAMS))
|
||||
demux_update_replaygain(demuxer);
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
@@ -1037,6 +1152,23 @@ static void demux_init_cuesheet(struct demuxer *demuxer)
|
||||
}
|
||||
}
|
||||
|
||||
static void demux_maybe_replace_stream(struct demuxer *demuxer)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
assert(!in->threading && demuxer == in->d_user);
|
||||
|
||||
if (demuxer->fully_read) {
|
||||
MP_VERBOSE(demuxer, "assuming demuxer read all data; closing stream\n");
|
||||
free_stream(demuxer->stream);
|
||||
demuxer->stream = open_memory_stream(NULL, 0); // dummy
|
||||
in->d_thread->stream = demuxer->stream;
|
||||
in->d_buffer->stream = demuxer->stream;
|
||||
|
||||
if (demuxer->desc->control)
|
||||
demuxer->desc->control(in->d_thread, DEMUXER_CTRL_REPLACE_STREAM, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
struct mp_log *log,
|
||||
const struct demuxer_desc *desc,
|
||||
@@ -1058,6 +1190,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
.log = mp_log_new(demuxer, log, desc->name),
|
||||
.glog = log,
|
||||
.filename = talloc_strdup(demuxer, stream->url),
|
||||
.is_network = stream->is_network,
|
||||
.events = DEMUX_EVENT_ALL,
|
||||
};
|
||||
demuxer->seekable = stream->seekable;
|
||||
@@ -1074,6 +1207,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
.min_secs = demuxer->opts->demuxer_min_secs,
|
||||
.max_packs = demuxer->opts->demuxer_max_packs,
|
||||
.max_bytes = demuxer->opts->demuxer_max_bytes,
|
||||
.initial_state = true,
|
||||
};
|
||||
pthread_mutex_init(&in->lock, NULL);
|
||||
pthread_cond_init(&in->wakeup, NULL);
|
||||
@@ -1129,6 +1263,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
¶ms2, DEMUX_CHECK_FORCE);
|
||||
if (sub)
|
||||
return sub;
|
||||
timeline_destroy(tl);
|
||||
}
|
||||
return demuxer;
|
||||
}
|
||||
@@ -1195,6 +1330,7 @@ done:
|
||||
// Convenience function: open the stream, enable the cache (according to params
|
||||
// and global opts.), open the demuxer.
|
||||
// (use free_demuxer_and_stream() to free the underlying stream too)
|
||||
// Also for some reason may close the opened stream if it's not needed.
|
||||
struct demuxer *demux_open_url(const char *url,
|
||||
struct demuxer_params *params,
|
||||
struct mp_cancel *cancel,
|
||||
@@ -1213,7 +1349,9 @@ struct demuxer *demux_open_url(const char *url,
|
||||
if (!params->disable_cache)
|
||||
stream_enable_cache(&s, &opts->stream_cache);
|
||||
struct demuxer *d = demux_open(s, params, global);
|
||||
if (!d) {
|
||||
if (d) {
|
||||
demux_maybe_replace_stream(d);
|
||||
} else {
|
||||
params->demuxer_failed = true;
|
||||
free_stream(s);
|
||||
}
|
||||
@@ -1276,18 +1414,6 @@ int demux_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Enable doing a "refresh seek" on the next stream switch.
|
||||
// Note that this by design does not disable ongoing refresh seeks, and
|
||||
// does not affect previous stream switch commands (even if they were
|
||||
// asynchronous).
|
||||
void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
in->refresh_seeks_enabled = enabled;
|
||||
pthread_mutex_unlock(&in->lock);
|
||||
}
|
||||
|
||||
struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
||||
enum stream_type t, int id)
|
||||
{
|
||||
@@ -1300,19 +1426,22 @@ struct sh_stream *demuxer_stream_by_demuxer_id(struct demuxer *d,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Set whether the given stream should return packets.
|
||||
// ref_pts is used only if the stream is enabled. Then it serves as approximate
|
||||
// start pts for this stream (in the worst case it is ignored).
|
||||
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
|
||||
bool selected)
|
||||
double ref_pts, bool selected)
|
||||
{
|
||||
struct demux_internal *in = demuxer->in;
|
||||
pthread_mutex_lock(&in->lock);
|
||||
// don't flush buffers if stream is already selected / unselected
|
||||
if (stream->ds->selected != selected) {
|
||||
stream->ds->selected = selected;
|
||||
stream->ds->active = false;
|
||||
ds_flush(stream->ds);
|
||||
in->tracks_switched = true;
|
||||
if (selected && in->refresh_seeks_enabled)
|
||||
in->start_refresh_seek = true;
|
||||
stream->ds->need_refresh = selected && !in->initial_state;
|
||||
if (stream->ds->need_refresh)
|
||||
in->ref_pts = MP_ADD_PTS(ref_pts, -in->ts_offset);
|
||||
if (in->threading) {
|
||||
pthread_cond_signal(&in->wakeup);
|
||||
} else {
|
||||
|
||||
+10
-9
@@ -43,6 +43,7 @@ enum demux_ctrl {
|
||||
DEMUXER_CTRL_STREAM_CTRL,
|
||||
DEMUXER_CTRL_GET_READER_STATE,
|
||||
DEMUXER_CTRL_GET_BITRATE_STATS, // double[STREAM_TYPE_COUNT]
|
||||
DEMUXER_CTRL_REPLACE_STREAM,
|
||||
};
|
||||
|
||||
struct demux_ctrl_reader_state {
|
||||
@@ -178,17 +179,12 @@ typedef struct demuxer {
|
||||
double start_time;
|
||||
// File format allows PTS resets (even if the current file is without)
|
||||
bool ts_resets_possible;
|
||||
// Enable fast track switching hacks. This requires from the demuxer:
|
||||
// - seeking is somewhat reliable; packet contents must not change
|
||||
// - packet position (demux_packet.pos) is set, not negative, unique, and
|
||||
// monotonically increasing
|
||||
// - seeking leaves packet positions invariant
|
||||
bool allow_refresh_seeks;
|
||||
// The file data was fully read, and there is no need to keep the stream
|
||||
// open, keep the cache active, or to run the demuxer thread. Generating
|
||||
// packets is not slow either (unlike e.g. libavdevice pseudo-demuxers).
|
||||
// Typical examples: text subtitles, playlists
|
||||
bool fully_read;
|
||||
bool is_network; // opened directly from a network stream
|
||||
|
||||
// Bitmask of DEMUX_EVENT_*
|
||||
int events;
|
||||
@@ -216,12 +212,16 @@ typedef struct demuxer {
|
||||
struct mp_log *log, *glog;
|
||||
struct demuxer_params *params;
|
||||
|
||||
struct demux_internal *in; // internal to demux.c
|
||||
// internal to demux.c
|
||||
struct demux_internal *in;
|
||||
struct mp_tags **update_stream_tags;
|
||||
int num_update_stream_tags;
|
||||
|
||||
// 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.
|
||||
// Also note that the stream can get replaced if fully_read is set.
|
||||
struct stream *stream;
|
||||
} demuxer_t;
|
||||
|
||||
@@ -265,13 +265,12 @@ bool demux_cancel_test(struct demuxer *demuxer);
|
||||
|
||||
void demux_flush(struct demuxer *demuxer);
|
||||
int demux_seek(struct demuxer *demuxer, double rel_seek_secs, int flags);
|
||||
void demux_set_enable_refresh_seeks(struct demuxer *demuxer, bool enabled);
|
||||
void demux_set_ts_offset(struct demuxer *demuxer, double offset);
|
||||
|
||||
int demux_control(struct demuxer *demuxer, int cmd, void *arg);
|
||||
|
||||
void demuxer_select_track(struct demuxer *demuxer, struct sh_stream *stream,
|
||||
bool selected);
|
||||
double ref_pts, bool selected);
|
||||
void demux_set_stream_autoselect(struct demuxer *demuxer, bool autoselect);
|
||||
|
||||
void demuxer_help(struct mp_log *log);
|
||||
@@ -280,6 +279,8 @@ int demuxer_add_attachment(struct demuxer *demuxer, char *name,
|
||||
char *type, void *data, size_t data_size);
|
||||
int demuxer_add_chapter(demuxer_t *demuxer, char *name,
|
||||
double pts, uint64_t demuxer_id);
|
||||
void demux_set_stream_tags(struct demuxer *demuxer, struct sh_stream *sh,
|
||||
struct mp_tags *tags);
|
||||
|
||||
double demuxer_get_time_length(struct demuxer *demuxer);
|
||||
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ static void reselect_streams(demuxer_t *demuxer)
|
||||
for (int n = 0; n < MPMIN(num_slave, p->num_streams); n++) {
|
||||
if (p->streams[n]) {
|
||||
demuxer_select_track(p->slave, demux_get_stream(p->slave, n),
|
||||
demux_stream_is_selected(p->streams[n]));
|
||||
MP_NOPTS_VALUE, demux_stream_is_selected(p->streams[n]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -213,25 +213,25 @@ static void build_timeline(struct timeline *tl, struct tl_parts *parts)
|
||||
|
||||
resolve_timestamps(part, source);
|
||||
|
||||
double len = source_get_length(source);
|
||||
if (len > 0) {
|
||||
len += source->start_time;
|
||||
} else {
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
}
|
||||
double end_time = source_get_length(source);
|
||||
if (end_time >= 0)
|
||||
end_time += source->start_time;
|
||||
|
||||
// Unknown length => use rest of the file. If duration is unknown, make
|
||||
// something up.
|
||||
if (part->length < 0)
|
||||
part->length = (len < 0 ? 1 : len) - part->offset;
|
||||
|
||||
if (len > 0) {
|
||||
double partlen = part->offset + part->length;
|
||||
if (partlen > len) {
|
||||
if (part->length < 0) {
|
||||
if (end_time < 0) {
|
||||
MP_WARN(tl, "EDL: source file '%s' has unknown duration.\n",
|
||||
part->filename);
|
||||
end_time = 1;
|
||||
}
|
||||
part->length = end_time - part->offset;
|
||||
} else if (end_time >= 0) {
|
||||
double end_part = part->offset + part->length;
|
||||
if (end_part > end_time) {
|
||||
MP_WARN(tl, "EDL: entry %d uses %f "
|
||||
"seconds, but file has only %f seconds.\n",
|
||||
n, partlen, len);
|
||||
n, end_part, end_time);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
-50
@@ -114,6 +114,7 @@ struct format_hack {
|
||||
// Do not confuse player's position estimation (position is into external
|
||||
// segment, with e.g. HLS, player knows about the playlist main file only).
|
||||
bool clear_filepos : 1;
|
||||
bool ignore_start : 1;
|
||||
};
|
||||
|
||||
#define BLACKLIST(fmt) {fmt, .ignore = true}
|
||||
@@ -137,6 +138,9 @@ static const struct format_hack format_hacks[] = {
|
||||
{"h264", .if_flags = AVFMT_NOTIMESTAMPS },
|
||||
{"hevc", .if_flags = AVFMT_NOTIMESTAMPS },
|
||||
|
||||
// Rebasing start time to 0 is very weird with ogg shoutcast streams.
|
||||
{"ogg", .ignore_start = true},
|
||||
|
||||
TEXTSUB("aqtitle"), TEXTSUB("jacosub"), TEXTSUB("microdvd"),
|
||||
TEXTSUB("mpl2"), TEXTSUB("mpsub"), TEXTSUB("pjs"), TEXTSUB("realtext"),
|
||||
TEXTSUB("sami"), TEXTSUB("srt"), TEXTSUB("stl"), TEXTSUB("subviewer"),
|
||||
@@ -158,6 +162,7 @@ static const struct format_hack format_hacks[] = {
|
||||
|
||||
typedef struct lavf_priv {
|
||||
struct stream *stream;
|
||||
bool own_stream;
|
||||
char *filename;
|
||||
struct format_hack format_hack;
|
||||
AVInputFormat *avif;
|
||||
@@ -168,7 +173,6 @@ typedef struct lavf_priv {
|
||||
int num_streams;
|
||||
int cur_program;
|
||||
char *mime_type;
|
||||
bool merge_track_metadata;
|
||||
double seek_delay;
|
||||
} lavf_priv_t;
|
||||
|
||||
@@ -287,8 +291,10 @@ static void convert_charset(struct demuxer *demuxer)
|
||||
if (conv.start)
|
||||
data = conv;
|
||||
}
|
||||
if (data.start)
|
||||
if (data.start) {
|
||||
priv->stream = open_memory_stream(data.start, data.len);
|
||||
priv->own_stream = true;
|
||||
}
|
||||
talloc_free(alloc);
|
||||
}
|
||||
|
||||
@@ -502,7 +508,7 @@ static void select_tracks(struct demuxer *demuxer, int start)
|
||||
}
|
||||
}
|
||||
|
||||
static void export_replaygain(demuxer_t *demuxer, struct mp_codec_params *c,
|
||||
static void export_replaygain(demuxer_t *demuxer, struct sh_stream *sh,
|
||||
AVStream *st)
|
||||
{
|
||||
for (int i = 0; i < st->nb_side_data; i++) {
|
||||
@@ -528,7 +534,10 @@ static void export_replaygain(demuxer_t *demuxer, struct mp_codec_params *c,
|
||||
rgain->album_peak = (av_rgain->album_peak != 0.0) ?
|
||||
av_rgain->album_peak / 100000.0f : 1.0;
|
||||
|
||||
c->replaygain_data = rgain;
|
||||
// This must be run only before the stream was added, otherwise there
|
||||
// will be race conditions with accesses from the user thread.
|
||||
assert(!sh->ds);
|
||||
sh->codec->replaygain_data = rgain;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -575,7 +584,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
delay = lavc_delay / (double)codec->sample_rate;
|
||||
priv->seek_delay = MPMAX(priv->seek_delay, delay);
|
||||
|
||||
export_replaygain(demuxer, sh->codec, st);
|
||||
export_replaygain(demuxer, sh, st);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -664,6 +673,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
if (sh->codec->lav_headers)
|
||||
mp_copy_lav_codec_headers(sh->codec->lav_headers, codec);
|
||||
#endif
|
||||
sh->codec->native_tb_num = st->time_base.num;
|
||||
sh->codec->native_tb_den = st->time_base.den;
|
||||
|
||||
if (st->disposition & AV_DISPOSITION_DEFAULT)
|
||||
sh->default_track = true;
|
||||
@@ -681,6 +692,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
if (!sh->title && sh->hls_bitrate > 0)
|
||||
sh->title = talloc_asprintf(sh, "bitrate %d", sh->hls_bitrate);
|
||||
sh->missing_timestamps = !!(priv->avif_flags & AVFMT_NOTIMESTAMPS);
|
||||
mp_tags_copy_from_av_dictionary(sh->tags, st->metadata);
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
}
|
||||
|
||||
@@ -703,14 +715,14 @@ static void update_metadata(demuxer_t *demuxer, AVPacket *pkt)
|
||||
priv->avfc->event_flags = 0;
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
}
|
||||
if (priv->merge_track_metadata) {
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
AVStream *st = priv->streams[n] ? priv->avfc->streams[n] : NULL;
|
||||
if (st && st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, st->metadata);
|
||||
st->event_flags = 0;
|
||||
demux_changed(demuxer, DEMUX_EVENT_METADATA);
|
||||
}
|
||||
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
AVStream *st = priv->streams[n] ? priv->avfc->streams[n] : NULL;
|
||||
if (st && st->event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) {
|
||||
st->event_flags = 0;
|
||||
struct mp_tags *tags = talloc_zero(NULL, struct mp_tags);
|
||||
mp_tags_copy_from_av_dictionary(tags, st->metadata);
|
||||
demux_set_stream_tags(demuxer, priv->streams[n], tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -795,6 +807,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
avfc->pb = priv->pb;
|
||||
if (stream_control(priv->stream, STREAM_CTRL_HAS_AVSEEK, NULL) > 0)
|
||||
demuxer->seekable = true;
|
||||
demuxer->seekable |= priv->format_hack.fully_read;
|
||||
}
|
||||
|
||||
if (matches_avinputformat_name(priv, "rtsp")) {
|
||||
@@ -845,26 +858,15 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
add_new_streams(demuxer);
|
||||
|
||||
// Often useful with OGG audio-only files, which have metadata in the audio
|
||||
// track metadata instead of the main metadata.
|
||||
if (demux_get_num_stream(demuxer) == 1) {
|
||||
priv->merge_track_metadata = true;
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
if (priv->streams[n])
|
||||
mp_tags_copy_from_av_dictionary(demuxer->metadata, avfc->streams[n]->metadata);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
demuxer->start_time = priv->avfc->start_time == AV_NOPTS_VALUE ?
|
||||
0 : (double)priv->avfc->start_time / AV_TIME_BASE;
|
||||
if (avfc->start_time != AV_NOPTS_VALUE && !priv->format_hack.ignore_start)
|
||||
demuxer->start_time = avfc->start_time / (double)AV_TIME_BASE;
|
||||
|
||||
demuxer->allow_refresh_seeks = matches_avinputformat_name(priv, "mp4");
|
||||
demuxer->fully_read = priv->format_hack.fully_read;
|
||||
|
||||
return 0;
|
||||
@@ -952,30 +954,15 @@ static void demux_seek_lavf(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
seek_pts_av = seek_pts * AV_TIME_BASE;
|
||||
}
|
||||
|
||||
int r;
|
||||
if (!priv->avfc->iformat->read_seek2) {
|
||||
// Normal seeking.
|
||||
int r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
|
||||
if (r < 0 && (avsflags & AVSEEK_FLAG_BACKWARD)) {
|
||||
// When seeking before the beginning of the file, and seeking fails,
|
||||
// try again without the backwards flag to make it seek to the
|
||||
// beginning.
|
||||
avsflags &= ~AVSEEK_FLAG_BACKWARD;
|
||||
r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
|
||||
if (r < 0 && (avsflags & AVSEEK_FLAG_BACKWARD)) {
|
||||
// When seeking before the beginning of the file, and seeking fails,
|
||||
// try again without the backwards flag to make it seek to the
|
||||
// beginning.
|
||||
avsflags &= ~AVSEEK_FLAG_BACKWARD;
|
||||
r = av_seek_frame(priv->avfc, -1, seek_pts_av, avsflags);
|
||||
}
|
||||
} else {
|
||||
// av_seek_frame() won't work. Use "new" seeking API. We don't use this
|
||||
// API by default, because there are some major issues.
|
||||
// Set max_ts==ts, so that demuxing starts from an earlier position in
|
||||
// the worst case.
|
||||
r = avformat_seek_file(priv->avfc, -1, INT64_MIN,
|
||||
seek_pts_av, seek_pts_av, avsflags);
|
||||
// Similar issue as in the normal seeking codepath.
|
||||
if (r < 0) {
|
||||
r = avformat_seek_file(priv->avfc, -1, INT64_MIN,
|
||||
seek_pts_av, INT64_MAX, avsflags);
|
||||
}
|
||||
}
|
||||
|
||||
if (r < 0) {
|
||||
char buf[180];
|
||||
av_strerror(r, buf, sizeof(buf));
|
||||
@@ -1084,6 +1071,12 @@ redo:
|
||||
av_seek_frame(priv->avfc, 0, stream_tell(priv->stream),
|
||||
AVSEEK_FLAG_BYTE);
|
||||
return DEMUXER_CTRL_OK;
|
||||
case DEMUXER_CTRL_REPLACE_STREAM:
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
priv->own_stream = false;
|
||||
priv->stream = demuxer->stream;
|
||||
return DEMUXER_CTRL_OK;
|
||||
default:
|
||||
return DEMUXER_CTRL_NOTIMPL;
|
||||
}
|
||||
@@ -1101,10 +1094,14 @@ static void demux_close_lavf(demuxer_t *demuxer)
|
||||
av_freep(&priv->pb->buffer);
|
||||
av_freep(&priv->pb);
|
||||
for (int n = 0; n < priv->num_streams; n++) {
|
||||
if (priv->streams[n])
|
||||
if (priv->streams[n]) {
|
||||
avcodec_free_context(&priv->streams[n]->codec->lav_headers);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
avcodec_parameters_free(&priv->streams[n]->codec->lav_codecpar);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (priv->stream != demuxer->stream)
|
||||
if (priv->own_stream)
|
||||
free_stream(priv->stream);
|
||||
talloc_free(priv);
|
||||
demuxer->priv = NULL;
|
||||
|
||||
@@ -65,23 +65,10 @@ static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
char **files = NULL;
|
||||
int num_files = 0;
|
||||
|
||||
for (;;) {
|
||||
struct archive_entry *entry;
|
||||
int r = archive_read_next_header(mpa->arch, &entry);
|
||||
if (r == ARCHIVE_EOF)
|
||||
break;
|
||||
if (r < ARCHIVE_OK)
|
||||
MP_ERR(demuxer, "%s\n", archive_error_string(mpa->arch));
|
||||
if (r < ARCHIVE_WARN)
|
||||
break;
|
||||
if (archive_entry_filetype(entry) != AE_IFREG)
|
||||
continue;
|
||||
const char *fn = archive_entry_pathname(entry);
|
||||
// Some archives may have no filenames.
|
||||
if (!fn)
|
||||
fn = talloc_asprintf(mpa, "mpv_unknown#%d\n", num_files);
|
||||
while (mp_archive_next_entry(mpa)) {
|
||||
// stream_libarchive.c does the real work
|
||||
char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix, fn);
|
||||
char *f = talloc_asprintf(mpa, "archive://%s|%s", prefix,
|
||||
mpa->entry_filename);
|
||||
MP_TARRAY_APPEND(mpa, files, num_files, f);
|
||||
}
|
||||
|
||||
|
||||
@@ -321,6 +321,7 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
c->disp_w = 0;
|
||||
c->disp_h = 0;
|
||||
c->fps = demuxer->opts->mf_fps;
|
||||
c->reliable_fps = true;
|
||||
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
|
||||
+25
-8
@@ -93,6 +93,7 @@ typedef struct mkv_content_encoding {
|
||||
|
||||
typedef struct mkv_track {
|
||||
int tnum;
|
||||
uint64_t uid;
|
||||
char *name;
|
||||
struct sh_stream *stream;
|
||||
|
||||
@@ -595,6 +596,7 @@ static void parse_trackentry(struct demuxer *demuxer,
|
||||
} else {
|
||||
MP_ERR(demuxer, "Missing track number!\n");
|
||||
}
|
||||
track->uid = entry->track_uid;
|
||||
|
||||
if (entry->name) {
|
||||
track->name = talloc_strdup(track, entry->name);
|
||||
@@ -773,8 +775,9 @@ static int demux_mkv_read_cues(demuxer_t *demuxer)
|
||||
if (cues.n_cue_point <= 3) // probably too sparse and will just break seeking
|
||||
goto done;
|
||||
|
||||
// Discard incremental index.
|
||||
mkv_d->num_indexes = 0;
|
||||
// Discard incremental index. (Keep the first entry, which must be the
|
||||
// start of the file - helps with files that miss the first index entry.)
|
||||
mkv_d->num_indexes = MPMIN(1, mkv_d->num_indexes);
|
||||
mkv_d->index_has_durations = false;
|
||||
|
||||
for (int i = 0; i < cues.n_cue_point; i++) {
|
||||
@@ -985,9 +988,6 @@ static void process_tags(demuxer_t *demuxer)
|
||||
|
||||
for (int i = 0; i < tags->n_tag; i++) {
|
||||
struct ebml_tag tag = tags->tag[i];
|
||||
if (tag.targets.target_track_uid || tag.targets.target_attachment_uid)
|
||||
continue;
|
||||
|
||||
struct mp_tags *dst = NULL;
|
||||
|
||||
if (tag.targets.target_chapter_uid) {
|
||||
@@ -1008,6 +1008,19 @@ static void process_tags(demuxer_t *demuxer)
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (tag.targets.target_track_uid) {
|
||||
for (int n = 0; n < mkv_d->num_tracks; n++) {
|
||||
if (mkv_d->tracks[n]->uid ==
|
||||
tag.targets.target_track_uid)
|
||||
{
|
||||
struct sh_stream *sh = mkv_d->tracks[n]->stream;
|
||||
if (sh)
|
||||
dst = sh->tags;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (tag.targets.target_attachment_uid) {
|
||||
/* ignore */
|
||||
} else {
|
||||
dst = demuxer->metadata;
|
||||
}
|
||||
@@ -1719,6 +1732,7 @@ static const char *const mkv_sub_tag[][2] = {
|
||||
{ "S_HDMV/PGS", "hdmv_pgs_subtitle"},
|
||||
{ "D_WEBVTT/SUBTITLES", "webvtt-webm"},
|
||||
{ "D_WEBVTT/CAPTIONS", "webvtt-webm"},
|
||||
{ "S_TEXT/WEBVTT", "webvtt"},
|
||||
{ "S_DVBSUB", "dvb_subtitle"},
|
||||
{0}
|
||||
};
|
||||
@@ -1922,10 +1936,9 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
MP_VERBOSE(demuxer, "All headers are parsed!\n");
|
||||
|
||||
process_tags(demuxer);
|
||||
display_create_tracks(demuxer);
|
||||
add_coverart(demuxer);
|
||||
demuxer->allow_refresh_seeks = true;
|
||||
process_tags(demuxer);
|
||||
|
||||
probe_first_timestamp(demuxer);
|
||||
if (opts->demux_mkv->probe_duration)
|
||||
@@ -2609,6 +2622,7 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
find_next_cluster:
|
||||
mkv_d->cluster_end = 0;
|
||||
for (;;) {
|
||||
stream_peek(s, 4); // guarantee we can undo ebml_read_id() below
|
||||
mkv_d->cluster_start = stream_tell(s);
|
||||
uint32_t id = ebml_read_id(s);
|
||||
if (id == MATROSKA_ID_CLUSTER)
|
||||
@@ -2627,6 +2641,7 @@ static int read_next_block(demuxer_t *demuxer, struct block_info *block)
|
||||
if ((!ebml_is_mkv_level1_id(id) && id != EBML_ID_VOID) ||
|
||||
ebml_read_skip(demuxer->log, -1, s) != 0)
|
||||
{
|
||||
stream_seek(s, mkv_d->cluster_start);
|
||||
ebml_resync_cluster(demuxer->log, s);
|
||||
}
|
||||
}
|
||||
@@ -2963,8 +2978,10 @@ static void probe_first_timestamp(struct demuxer *demuxer)
|
||||
return;
|
||||
|
||||
struct block_info block;
|
||||
if (read_next_block(demuxer, &block) > 0)
|
||||
if (read_next_block(demuxer, &block) > 0) {
|
||||
index_block(demuxer, &block);
|
||||
mkv_d->tmp_block = block;
|
||||
}
|
||||
|
||||
demuxer->start_time = mkv_d->cluster_tc / 1e9;
|
||||
|
||||
|
||||
+66
-19
@@ -20,6 +20,7 @@
|
||||
#include <strings.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/common.h"
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
@@ -105,14 +106,21 @@ static int parse_m3u(struct pl_parser *p)
|
||||
if (p->check_level == DEMUX_CHECK_UNSAFE) {
|
||||
char *ext = mp_splitext(p->real_stream->url, NULL);
|
||||
bstr data = stream_peek(p->real_stream, PROBE_SIZE);
|
||||
if (ext && !strcmp(ext, "m3u") && data.len > 10 && maybe_text(data))
|
||||
goto ok;
|
||||
if (ext && data.len > 10 && maybe_text(data)) {
|
||||
const char *exts[] = {"m3u", "m3u8", NULL};
|
||||
for (int n = 0; exts[n]; n++) {
|
||||
if (strcasecmp(ext, exts[n]) == 0)
|
||||
goto ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
ok:
|
||||
if (p->probing)
|
||||
return 0;
|
||||
|
||||
char *title = NULL;
|
||||
while (line.len || !pl_eof(p)) {
|
||||
if (bstr_eatstart0(&line, "#EXTINF:")) {
|
||||
@@ -209,6 +217,59 @@ static int parse_txt(struct pl_parser *p)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_DIR_STACK 20
|
||||
|
||||
static bool same_st(struct stat *st1, struct stat *st2)
|
||||
{
|
||||
return HAVE_POSIX && st1->st_dev == st2->st_dev && st1->st_ino == st2->st_ino;
|
||||
}
|
||||
|
||||
// Return true if this was a readable directory.
|
||||
static bool scan_dir(struct pl_parser *p, char *path,
|
||||
struct stat *dir_stack, int num_dir_stack,
|
||||
char ***files, int *num_files)
|
||||
{
|
||||
if (strlen(path) >= 8192 || num_dir_stack == MAX_DIR_STACK)
|
||||
return false; // things like mount bind loops
|
||||
|
||||
DIR *dp = opendir(path);
|
||||
if (!dp) {
|
||||
MP_ERR(p, "Could not read directory.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
struct dirent *ep;
|
||||
while ((ep = readdir(dp))) {
|
||||
if (ep->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
if (mp_cancel_test(p->s->cancel))
|
||||
break;
|
||||
|
||||
char *file = mp_path_join(p, path, ep->d_name);
|
||||
|
||||
struct stat st;
|
||||
if (stat(file, &st) == 0 && S_ISDIR(st.st_mode)) {
|
||||
for (int n = 0; n < num_dir_stack; n++) {
|
||||
if (same_st(&dir_stack[n], &st)) {
|
||||
MP_VERBOSE(p, "Skip recursive entry: %s\n", file);
|
||||
goto skip;
|
||||
}
|
||||
}
|
||||
|
||||
dir_stack[num_dir_stack] = st;
|
||||
scan_dir(p, file, dir_stack, num_dir_stack + 1, files, num_files);
|
||||
} else {
|
||||
MP_TARRAY_APPEND(p, *files, *num_files, file);
|
||||
}
|
||||
|
||||
skip: ;
|
||||
}
|
||||
|
||||
closedir(dp);
|
||||
return true;
|
||||
}
|
||||
|
||||
static int cmp_filename(const void *a, const void *b)
|
||||
{
|
||||
return strcmp(*(char **)a, *(char **)b);
|
||||
@@ -222,32 +283,18 @@ static int parse_dir(struct pl_parser *p)
|
||||
return 0;
|
||||
|
||||
char *path = mp_file_get_path(p, bstr0(p->real_stream->url));
|
||||
if (strlen(path) >= 8192)
|
||||
return -1; // things like mount bind loops
|
||||
|
||||
DIR *dp = opendir(path);
|
||||
if (!dp) {
|
||||
MP_ERR(p, "Could not read directory.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
char **files = NULL;
|
||||
int num_files = 0;
|
||||
struct stat dir_stack[MAX_DIR_STACK];
|
||||
|
||||
struct dirent *ep;
|
||||
while ((ep = readdir(dp))) {
|
||||
if (ep->d_name[0] == '.')
|
||||
continue;
|
||||
MP_TARRAY_APPEND(p, files, num_files, talloc_strdup(p, ep->d_name));
|
||||
}
|
||||
scan_dir(p, path, dir_stack, 0, &files, &num_files);
|
||||
|
||||
if (files)
|
||||
qsort(files, num_files, sizeof(files[0]), cmp_filename);
|
||||
|
||||
for (int n = 0; n < num_files; n++)
|
||||
playlist_add_file(p->pl, mp_path_join(p, path, files[n]));
|
||||
|
||||
closedir(dp);
|
||||
playlist_add_file(p->pl, files[n]);
|
||||
|
||||
p->add_base = false;
|
||||
|
||||
|
||||
+18
-5
@@ -36,7 +36,7 @@
|
||||
#include "osdep/endian.h"
|
||||
|
||||
struct demux_rawaudio_opts {
|
||||
struct mp_chmap channels;
|
||||
struct m_channels channels;
|
||||
int samplerate;
|
||||
int aformat;
|
||||
};
|
||||
@@ -49,13 +49,13 @@ struct demux_rawaudio_opts {
|
||||
#define OPT_BASE_STRUCT struct demux_rawaudio_opts
|
||||
const struct m_sub_options demux_rawaudio_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_CHMAP("channels", channels, CONF_MIN, .min = 1),
|
||||
OPT_CHANNELS("channels", channels, 0, .min = 1),
|
||||
OPT_INTRANGE("rate", samplerate, 0, 1000, 8 * 48000),
|
||||
OPT_CHOICE("format", aformat, 0,
|
||||
({"u8", PCM(0, 0, 8, 0)},
|
||||
{"s8", PCM(1, 0, 8, 0)},
|
||||
{"u16le", PCM(0, 0, 16, 0)}, {"u16be", PCM(0, 0, 16, 1)},
|
||||
{"s16le", PCM(1, 0, 16, 0)}, {"u16be", PCM(1, 0, 16, 1)},
|
||||
{"s16le", PCM(1, 0, 16, 0)}, {"s16be", PCM(1, 0, 16, 1)},
|
||||
{"u24le", PCM(0, 0, 24, 0)}, {"u24be", PCM(0, 0, 24, 1)},
|
||||
{"s24le", PCM(1, 0, 24, 0)}, {"s24be", PCM(1, 0, 24, 1)},
|
||||
{"u32le", PCM(0, 0, 32, 0)}, {"u32be", PCM(0, 0, 32, 1)},
|
||||
@@ -75,7 +75,11 @@ const struct m_sub_options demux_rawaudio_conf = {
|
||||
.size = sizeof(struct demux_rawaudio_opts),
|
||||
.defaults = &(const struct demux_rawaudio_opts){
|
||||
// Note that currently, stream_cdda expects exactly these parameters!
|
||||
.channels = MP_CHMAP_INIT_STEREO,
|
||||
.channels = {
|
||||
.set = 1,
|
||||
.chmaps = (struct mp_chmap[]){ MP_CHMAP_INIT_STEREO, },
|
||||
.num_chmaps = 1,
|
||||
},
|
||||
.samplerate = 44100,
|
||||
.aformat = PCM(1, 0, 16, 0), // s16le
|
||||
},
|
||||
@@ -130,12 +134,20 @@ static int demux_rawaudio_open(demuxer_t *demuxer, enum demux_check check)
|
||||
if (check != DEMUX_CHECK_REQUEST && check != DEMUX_CHECK_FORCE)
|
||||
return -1;
|
||||
|
||||
if (opts->channels.num_chmaps != 1) {
|
||||
MP_ERR(demuxer, "Invalid channels option given.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sh_stream *sh = demux_alloc_sh_stream(STREAM_AUDIO);
|
||||
struct mp_codec_params *c = sh->codec;
|
||||
c->channels = opts->channels;
|
||||
c->channels = opts->channels.chmaps[0];
|
||||
c->force_channels = true;
|
||||
c->samplerate = opts->samplerate;
|
||||
|
||||
c->native_tb_num = 1;
|
||||
c->native_tb_den = c->samplerate;
|
||||
|
||||
int f = opts->aformat;
|
||||
// See PCM(): sign float bits endian
|
||||
mp_set_pcm_codec(sh->codec, f & 1, f & 2, f >> 3, f & 4);
|
||||
@@ -224,6 +236,7 @@ static int demux_rawvideo_open(demuxer_t *demuxer, enum demux_check check)
|
||||
c->codec = decoder;
|
||||
c->codec_tag = imgfmt;
|
||||
c->fps = opts->fps;
|
||||
c->reliable_fps = true;
|
||||
c->disp_w = width;
|
||||
c->disp_h = height;
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
@@ -84,7 +84,7 @@ static void reselect_streams(struct demuxer *demuxer)
|
||||
// This stops demuxer readahead for inactive segments.
|
||||
if (!p->current || seg->d != p->current->d)
|
||||
selected = false;
|
||||
demuxer_select_track(seg->d, sh, selected);
|
||||
demuxer_select_track(seg->d, sh, MP_NOPTS_VALUE, selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,6 +197,11 @@ static int d_fill_buffer(struct demuxer *demuxer)
|
||||
if (pkt->stream < 0)
|
||||
goto drop;
|
||||
|
||||
// for refresh seeks, demux.c prefers monotonically increasing packet pos
|
||||
// since the packet pos is meaningless anyway for timeline, use it
|
||||
if (pkt->pos >= 0)
|
||||
pkt->pos |= (seg->index & 0x7FFFULL) << 48;
|
||||
|
||||
struct virtual_stream *vs = &p->streams[pkt->stream];
|
||||
|
||||
if (pkt->pts != MP_NOPTS_VALUE && pkt->pts >= seg->end) {
|
||||
@@ -301,6 +306,9 @@ static int d_open(struct demuxer *demuxer, enum demux_check check)
|
||||
demuxer->metadata = meta->metadata;
|
||||
demuxer->attachments = meta->attachments;
|
||||
demuxer->num_attachments = meta->num_attachments;
|
||||
demuxer->editions = meta->editions;
|
||||
demuxer->num_editions = meta->num_editions;
|
||||
demuxer->edition = meta->edition;
|
||||
|
||||
int num_streams = demux_get_num_stream(meta);
|
||||
for (int n = 0; n < num_streams; n++) {
|
||||
|
||||
+7
-1
@@ -46,6 +46,8 @@ struct sh_stream {
|
||||
bool forced_track; // container forced track flag
|
||||
int hls_bitrate;
|
||||
|
||||
struct mp_tags *tags;
|
||||
|
||||
bool missing_timestamps;
|
||||
|
||||
// stream is a picture (such as album art)
|
||||
@@ -72,6 +74,9 @@ struct mp_codec_params {
|
||||
struct AVCodecContext *lav_headers;
|
||||
struct AVCodecParameters *lav_codecpar;
|
||||
|
||||
// Timestamp granularity for converting double<->rational timestamps.
|
||||
int native_tb_num, native_tb_den;
|
||||
|
||||
// STREAM_AUDIO
|
||||
int samplerate;
|
||||
struct mp_chmap channels;
|
||||
@@ -82,7 +87,8 @@ struct mp_codec_params {
|
||||
|
||||
// STREAM_VIDEO
|
||||
bool avi_dts; // use DTS timing; first frame and DTS is 0
|
||||
float fps; // frames per second (set only if constant fps)
|
||||
double fps; // frames per second (set only if constant fps)
|
||||
bool reliable_fps; // the fps field is definitely not broken
|
||||
int par_w, par_h; // pixel aspect ratio (0 if unknown/square)
|
||||
int disp_w, disp_h; // display size
|
||||
int rotate; // intended display rotation, in degrees, [0, 359]
|
||||
|
||||
+2
-2
@@ -79,7 +79,7 @@
|
||||
#> playlist-next # skip to next file
|
||||
#ENTER playlist-next # skip to next file
|
||||
#< playlist-prev # skip to previous file
|
||||
#O no-osd cycle_values osd-level 3 1 # cycle through OSD mode
|
||||
#O no-osd cycle-values osd-level 3 1 # cycle through OSD mode
|
||||
#o show-progress
|
||||
#P show-progress
|
||||
#I show-text "${filename}" # display filename in osd
|
||||
@@ -142,7 +142,7 @@
|
||||
#CLOSE_WIN quit
|
||||
#CLOSE_WIN {encode} quit 4
|
||||
#E cycle edition # next edition
|
||||
#l ab_loop # Set/clear A-B loop points
|
||||
#l ab-loop # Set/clear A-B loop points
|
||||
#L cycle-values loop "inf" "no" # toggle infinite looping
|
||||
#ctrl+c quit 4
|
||||
|
||||
|
||||
@@ -24,19 +24,19 @@ SHARP cycle audio # switch audio streams
|
||||
BS set speed 1.0 # reset speed to normal
|
||||
q quit
|
||||
ESC quit
|
||||
ENTER playlist_next force # skip to next file
|
||||
ENTER playlist-next force # skip to next file
|
||||
p cycle pause
|
||||
. frame_step # advance one frame and pause
|
||||
. frame-step # advance one frame and pause
|
||||
SPACE cycle pause
|
||||
HOME set playlist-pos 0 # not the same as MPlayer
|
||||
#END pt_up_step -1
|
||||
> playlist_next # skip to next file
|
||||
< playlist_prev # previous
|
||||
> playlist-next # skip to next file
|
||||
< playlist-prev # previous
|
||||
#INS alt_src_step 1
|
||||
#DEL alt_src_step -1
|
||||
o osd
|
||||
I show_text "${filename}" # display filename in osd
|
||||
P show_progress
|
||||
I show-text "${filename}" # display filename in osd
|
||||
P show-progress
|
||||
z add sub-delay -0.1 # subtract 100 ms delay from subs
|
||||
x add sub-delay +0.1 # add
|
||||
9 add volume -1
|
||||
@@ -57,8 +57,8 @@ d cycle framedrop
|
||||
D cycle deinterlace # toggle deinterlacer (auto-inserted filter)
|
||||
r add sub-pos -1 # move subtitles up
|
||||
t add sub-pos +1 # down
|
||||
#? sub_step +1 # immediately display next subtitle
|
||||
#? sub_step -1 # previous
|
||||
#? sub-step +1 # immediately display next subtitle
|
||||
#? sub-step -1 # previous
|
||||
#? add sub-scale +0.1 # increase subtitle font size
|
||||
#? add sub-scale -0.1 # decrease subtitle font size
|
||||
f cycle fullscreen
|
||||
|
||||
+1
-1
@@ -79,7 +79,7 @@
|
||||
#softvol=no
|
||||
|
||||
# Do not filter audio to keep pitch when changing playback speed.
|
||||
audio-pitch-correction=no
|
||||
#audio-pitch-correction=no
|
||||
|
||||
# Output 5.1 audio natively, and upmix/downmix audio with a different format.
|
||||
#audio-channels=5.1
|
||||
|
||||
@@ -17,7 +17,7 @@ d cycle framedrop
|
||||
|
||||
# changed in mpv 0.7.0
|
||||
|
||||
ENTER playlist_next force
|
||||
ENTER playlist-next force
|
||||
|
||||
# changed in mpv 0.6.0
|
||||
|
||||
@@ -38,4 +38,4 @@ TAB cycle program
|
||||
A cycle angle
|
||||
U stop
|
||||
o osd
|
||||
I show_text "${filename}"
|
||||
I show-text "${filename}"
|
||||
+1
-1
@@ -257,7 +257,7 @@ static const struct legacy_cmd legacy_cmds[] = {
|
||||
{"saturation", "add saturation"},
|
||||
{"hue", "add hue"},
|
||||
{"switch_vsync", "cycle vsync"},
|
||||
{"sub_load", "sub_add"},
|
||||
{"sub_load", "sub-add"},
|
||||
{"sub_select", "cycle sub"},
|
||||
{"sub_pos", "add sub-pos"},
|
||||
{"sub_delay", "add sub-delay"},
|
||||
|
||||
@@ -273,7 +273,6 @@ static struct mp_cmd *parse_cmd_str(struct mp_log *log, void *tmp,
|
||||
break;
|
||||
if (pctx_read_token(ctx, &cur_token) < 0)
|
||||
goto error;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!find_cmd(ctx->log, cmd, cur_token))
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ void mp_event_drop_files(struct input_ctx *ictx, int num_files, char **files,
|
||||
for (int i = 0; i < num_files; i++) {
|
||||
const char *cmd[] = {
|
||||
"osd-auto",
|
||||
"sub_add",
|
||||
"sub-add",
|
||||
files[i],
|
||||
NULL
|
||||
};
|
||||
|
||||
+1
-4
@@ -1247,12 +1247,9 @@ void mp_input_load(struct input_ctx *ictx)
|
||||
void *tmp = talloc_new(NULL);
|
||||
char **files = mp_find_all_config_files(tmp, ictx->global, "input.conf");
|
||||
for (int n = 0; files && files[n]; n++)
|
||||
config_ok = config_ok | parse_config_file(ictx, files[n], false);
|
||||
parse_config_file(ictx, files[n], false);
|
||||
talloc_free(tmp);
|
||||
}
|
||||
if (!config_ok) {
|
||||
MP_VERBOSE(ictx, "Falling back on default (hardcoded) input config\n");
|
||||
}
|
||||
|
||||
if (input_conf->use_alt_gr) {
|
||||
ictx->using_alt_gr = true;
|
||||
|
||||
+2
-3
@@ -133,8 +133,7 @@ static void *client_thread(void *p)
|
||||
}
|
||||
|
||||
if (fds[0].revents & POLLIN) {
|
||||
char discard[100];
|
||||
read(pipe_fd, discard, sizeof(discard));
|
||||
mp_flush_wakeup_pipe(pipe_fd);
|
||||
|
||||
while (1) {
|
||||
mpv_event *event = mpv_wait_event(arg->client, 0);
|
||||
@@ -413,7 +412,7 @@ void mp_uninit_ipc(struct mp_ipc_ctx *arg)
|
||||
if (!arg)
|
||||
return;
|
||||
|
||||
write(arg->death_pipe[1], &(char){0}, 1);
|
||||
(void)write(arg->death_pipe[1], &(char){0}, 1);
|
||||
pthread_join(arg->thread, NULL);
|
||||
|
||||
close(arg->death_pipe[0]);
|
||||
|
||||
+23
-9
@@ -215,7 +215,7 @@ extern "C" {
|
||||
* relational operators (<, >, <=, >=).
|
||||
*/
|
||||
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 20)
|
||||
#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 22)
|
||||
|
||||
/**
|
||||
* Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with.
|
||||
@@ -248,7 +248,7 @@ typedef enum mpv_error {
|
||||
* making asynchronous requests. (Bugs in the client API implementation
|
||||
* could also trigger this, e.g. if events become "lost".)
|
||||
*/
|
||||
MPV_ERROR_EVENT_QUEUE_FULL = -1,
|
||||
MPV_ERROR_EVENT_QUEUE_FULL = -1,
|
||||
/**
|
||||
* Memory allocation failed.
|
||||
*/
|
||||
@@ -299,7 +299,7 @@ typedef enum mpv_error {
|
||||
*/
|
||||
MPV_ERROR_COMMAND = -12,
|
||||
/**
|
||||
* Generic error on loading (used with mpv_event_end_file.error).
|
||||
* Generic error on loading (usually used with mpv_event_end_file.error).
|
||||
*/
|
||||
MPV_ERROR_LOADING_FAILED = -13,
|
||||
/**
|
||||
@@ -329,7 +329,11 @@ typedef enum mpv_error {
|
||||
/**
|
||||
* The API function which was called is a stub only.
|
||||
*/
|
||||
MPV_ERROR_NOT_IMPLEMENTED = -19
|
||||
MPV_ERROR_NOT_IMPLEMENTED = -19,
|
||||
/**
|
||||
* Unspecified error.
|
||||
*/
|
||||
MPV_ERROR_GENERIC = -20
|
||||
} mpv_error;
|
||||
|
||||
/**
|
||||
@@ -660,7 +664,7 @@ typedef enum mpv_format {
|
||||
MPV_FORMAT_NODE_MAP = 8,
|
||||
/**
|
||||
* A raw, untyped byte array. Only used only with mpv_node, and only in
|
||||
* some very special situations. (Currently, only for the screenshot_raw
|
||||
* some very special situations. (Currently, only for the screenshot-raw
|
||||
* command.)
|
||||
*/
|
||||
MPV_FORMAT_BYTE_ARRAY = 9
|
||||
@@ -780,6 +784,10 @@ void mpv_free_node_contents(mpv_node *node);
|
||||
* mpv_set_property() to change settings during playback, because the property
|
||||
* mechanism guarantees that changes take effect immediately.
|
||||
*
|
||||
* Using a format other than MPV_FORMAT_NODE is equivalent to constructing a
|
||||
* mpv_node with the given format and data, and passing the mpv_node to this
|
||||
* function.
|
||||
*
|
||||
* @param name Option name. This is the same as on the mpv command line, but
|
||||
* without the leading "--".
|
||||
* @param format see enum mpv_format.
|
||||
@@ -877,7 +885,13 @@ int mpv_command_node_async(mpv_handle *ctx, uint64_t reply_userdata,
|
||||
* usually will fail with MPV_ERROR_PROPERTY_FORMAT. In some cases, the data
|
||||
* is automatically converted and access succeeds. For example, MPV_FORMAT_INT64
|
||||
* is always converted to MPV_FORMAT_DOUBLE, and access using MPV_FORMAT_STRING
|
||||
* usually invokes a string parser.
|
||||
* usually invokes a string parser. The same happens when calling this function
|
||||
* with MPV_FORMAT_NODE: the underlying format may be converted to another
|
||||
* type if possible.
|
||||
*
|
||||
* Using a format other than MPV_FORMAT_NODE is equivalent to constructing a
|
||||
* mpv_node with the given format and data, and passing the mpv_node to this
|
||||
* function. (Before API version 1.21, this was different.)
|
||||
*
|
||||
* @param name The property name. See input.rst for a list of properties.
|
||||
* @param format see enum mpv_format.
|
||||
@@ -1138,13 +1152,13 @@ typedef enum mpv_event_id {
|
||||
* @deprecated This was used internally with the internal "script_dispatch"
|
||||
* command to dispatch keyboard and mouse input for the OSC.
|
||||
* It was never useful in general and has been completely
|
||||
* replaced with "script_binding".
|
||||
* replaced with "script-binding".
|
||||
* This event never happens anymore, and is included in this
|
||||
* header only for compatibility.
|
||||
*/
|
||||
MPV_EVENT_SCRIPT_INPUT_DISPATCH = 15,
|
||||
/**
|
||||
* Triggered by the script_message input command. The command uses the
|
||||
* Triggered by the script-message input command. The command uses the
|
||||
* first argument of the command as client name (see mpv_client_name()) to
|
||||
* dispatch the message, and passes along all arguments starting from the
|
||||
* second argument as strings.
|
||||
@@ -1436,7 +1450,7 @@ int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable);
|
||||
* required log level for a message to be received with MPV_EVENT_LOG_MESSAGE.
|
||||
*
|
||||
* @param min_level Minimal log level as string. Valid log levels:
|
||||
* no fatal error warn info status v debug trace
|
||||
* no fatal error warn info v debug trace
|
||||
* The value "no" disables all messages. This is the default.
|
||||
* An exception is the value "terminal-default", which uses the
|
||||
* log level as set by the "--msg-level" option. This works
|
||||
|
||||
@@ -37,6 +37,7 @@ mpv_set_property
|
||||
mpv_set_property_async
|
||||
mpv_set_property_string
|
||||
mpv_set_wakeup_callback
|
||||
mpv_stream_cb_add_ro
|
||||
mpv_suspend
|
||||
mpv_terminate_destroy
|
||||
mpv_unobserve_property
|
||||
|
||||
+40
-3
@@ -108,6 +108,28 @@ extern "C" {
|
||||
* context must have been uninitialized. If this doesn't happen, undefined
|
||||
* behavior will result.
|
||||
*
|
||||
* Hardware decoding
|
||||
* -----------------
|
||||
*
|
||||
* Hardware decoding via opengl_cb is fully supported, but requires some
|
||||
* additional setup. (At least if direct hardware decoding modes are wanted,
|
||||
* instead of copying back surface data from GPU to CPU RAM.)
|
||||
*
|
||||
* While "normal" mpv loads the OpenGL hardware decoding interop on demand,
|
||||
* this can't be done with opengl_cb for internal technical reasons. Instead,
|
||||
* make it load the interop at load time by setting the "hwdec-preload"="auto"
|
||||
* option before calling mpv_opengl_cb_init_gl().
|
||||
*
|
||||
* There may be certain requirements on the OpenGL implementation:
|
||||
* - Windows: ANGLE is required (although in theory GL/DX interop could be used)
|
||||
* - Intel/Linux: EGL is required, and also a glMPGetNativeDisplay() callback
|
||||
* must be provided (see sections below)
|
||||
* - nVidia/Linux: GLX is required
|
||||
* - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL)
|
||||
*
|
||||
* Once these things are setup, hardware decoding can be enabled/disabled at
|
||||
* any time by setting the "hwdec" property.
|
||||
*
|
||||
* Special windowing system interop considerations
|
||||
* ------------------------------------------------
|
||||
*
|
||||
@@ -126,19 +148,34 @@ extern "C" {
|
||||
* up until mpv_opengl_cb_uninit_gl() is called. If the name is not anything
|
||||
* you know/expected, return NULL from the function.
|
||||
*
|
||||
* Windowing system interop on Linux
|
||||
* ---------------------------------
|
||||
* Windowing system interop on Intel/Linux with VAAPI
|
||||
* --------------------------------------------------
|
||||
*
|
||||
* The new VAAPI OpenGL interop requires an EGL context. EGL provides no way
|
||||
* to query the X11 Display associated to a specific EGL context, so this API
|
||||
* is used to pass it through.
|
||||
*
|
||||
* glMPGetNativeDisplay("x11") should return a X11 "Display*", which then will
|
||||
* be used to create the hardware decoder state. (On GLX, this is not needed.)
|
||||
* be used to create the hardware decoder state.
|
||||
*
|
||||
* glMPGetNativeDisplay("wl") should return a Wayland "struct wl_display *".
|
||||
*
|
||||
* glMPGetNativeDisplay("drm") should return a DRM FD casted to intptr_t (note
|
||||
* that a 0 FD is not supported - if this can happen in your case, you must
|
||||
* dup2() it to a non-0 FD).
|
||||
*
|
||||
* nVidia/Linux via VDPAU requires GLX, which does not have this problem (the
|
||||
* GLX API can return the current X11 Display).
|
||||
*
|
||||
* Windowing system interop on MS win32
|
||||
* ------------------------------------
|
||||
*
|
||||
* Warning: the following is only required if native OpenGL instead of ANGLE
|
||||
* is used. ANGLE is recommended, because it also allows direct
|
||||
* hardware decoding interop without further setup by the libmpv
|
||||
* API user, while the same with native OpenGL is either very hard
|
||||
* to do (via GL/DX interop with D3D9), or not implemented.
|
||||
*
|
||||
* If OpenGL switches to fullscreen, most players give it access GPU access,
|
||||
* which means DXVA2 hardware decoding in mpv won't work. This can be worked
|
||||
* around by giving mpv access to Direct3D device, which it will then use to
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note: the client API is licensed under ISC (see above) to ease
|
||||
* interoperability with other licenses. But keep in mind that the
|
||||
* mpv core is still mostly GPLv2+. It's up to lawyers to decide
|
||||
* whether applications using this API are affected by the GPL.
|
||||
* One argument against this is that proprietary applications
|
||||
* using mplayer in slave mode is apparently tolerated, and this
|
||||
* API is basically equivalent to slave mode.
|
||||
*/
|
||||
|
||||
#ifndef MPV_CLIENT_API_STREAM_CB_H_
|
||||
#define MPV_CLIENT_API_STREAM_CB_H_
|
||||
|
||||
#include "client.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Warning: this API is not stable yet.
|
||||
*
|
||||
* Overview
|
||||
* --------
|
||||
*
|
||||
* This API can be used to make mpv read from a stream with a custom
|
||||
* implementation. This interface is inspired by funopen on BSD and
|
||||
* fopencookie on linux. The stream is backed by user-defined callbacks
|
||||
* which can implement customized open, read, seek, size and close behaviors.
|
||||
*
|
||||
* Usage
|
||||
* -----
|
||||
*
|
||||
* Register your stream callbacks with the mpv_stream_cb_add_ro() function. You
|
||||
* have to provide a mpv_stream_cb_open_ro_fn callback to it (open_fn argument).
|
||||
*
|
||||
* Once registered, you can `loadfile myprotocol://myfile`. Your open_fn will be
|
||||
* invoked with the URI and you must fill out the provided mpv_stream_cb_info
|
||||
* struct. This includes your stream callbacks (like read_fn), and an opaque
|
||||
* cookie, which will be passed as the first argument to all the remaining
|
||||
* stream callbacks.
|
||||
*
|
||||
* Note that your custom callbacks must not invoke libmpv APIs as that would
|
||||
* cause a deadlock.
|
||||
*
|
||||
* Stream lifetime
|
||||
* ---------------
|
||||
*
|
||||
* A stream remains valid until its close callback has been called. It's up to
|
||||
* libmpv to call the close callback, and the libmpv user cannot close it
|
||||
* directly with the stream_cb API.
|
||||
*
|
||||
* For example, if you consider your custom stream to become suddenly invalid
|
||||
* (maybe because the underlying stream died), libmpv will continue using your
|
||||
* stream. All you can do is returning errors from each callback, until libmpv
|
||||
* gives up and closes it.
|
||||
*
|
||||
* Protocol registration and lifetime
|
||||
* ----------------------------------
|
||||
*
|
||||
* Protocols remain registered until the mpv instance is terminated. This means
|
||||
* in particular that it can outlive the mpv_handle that was used to register
|
||||
* it, but once mpv_terminate_destroy() is called, your registered callbacks
|
||||
* will not be called again.
|
||||
*
|
||||
* Protocol unregistration is finished after the mpv core has been destroyed
|
||||
* (e.g. after mpv_terminate_destroy() has returned).
|
||||
*
|
||||
* If you do not call mpv_terminate_destroy() yourself (e.g. plugin-style code),
|
||||
* you will have to deal with the registration or even streams outliving your
|
||||
* code. Here are some possible ways to do this:
|
||||
* - call mpv_terminate_destroy(), which destroys the core, and will make sure
|
||||
* all streams are closed once this function returns
|
||||
* - you refcount all resources your stream "cookies" reference, so that it
|
||||
* doesn't matter if streams live longer than expected
|
||||
* - create "cancellation" semantics: after your protocol has been unregistered,
|
||||
* notify all your streams that are still opened, and make them drop all
|
||||
* referenced resources - then return errors from the stream callbacks as
|
||||
* long as the stream is still opened
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Read callback used to implement a custom stream. The semantics of the
|
||||
* callback match read(2) in blocking mode. Short reads are allowed (you can
|
||||
* return less bytes than requested, and libmpv will retry reading the rest
|
||||
* with a nother call). If no data can be immediately read, the callback must
|
||||
* block until there is new data. A return of 0 will be interpreted as final
|
||||
* EOF, although libmpv might retry the read, or seek to a different position.
|
||||
*
|
||||
* @param cookie opaque cookie identifying the stream,
|
||||
* returned from mpv_stream_cb_open_fn
|
||||
* @param buf buffer to read data into
|
||||
* @param size of the buffer
|
||||
* @return number of bytes read into the buffer
|
||||
* @return 0 on EOF
|
||||
* @return -1 on error
|
||||
*/
|
||||
typedef int64_t (*mpv_stream_cb_read_fn)(void *cookie, char *buf, uint64_t nbytes);
|
||||
|
||||
/**
|
||||
* Seek callback used to implement a custom stream.
|
||||
*
|
||||
* Note that mpv will issue a seek to position 0 immediately after opening. This
|
||||
* is used to test whether the stream is seekable (since seekability might
|
||||
* depend on the URI contents, not just the protocol). Return
|
||||
* MPV_ERROR_UNSUPPORTED if seeking is not implemented for this stream. This
|
||||
* seek also servies to establish the fact that streams start at position 0.
|
||||
*
|
||||
* This callback can be NULL, in which it behaves as if always returning
|
||||
* MPV_ERROR_UNSUPPORTED.
|
||||
*
|
||||
* @param cookie opaque cookie identifying the stream,
|
||||
* returned from mpv_stream_cb_open_fn
|
||||
* @param offset target absolut stream position
|
||||
* @return the resulting offset of the stream
|
||||
* MPV_ERROR_UNSUPPORTED or MPV_ERROR_GENERIC if the seek failed
|
||||
*/
|
||||
typedef int64_t (*mpv_stream_cb_seek_fn)(void *cookie, int64_t offset);
|
||||
|
||||
/**
|
||||
* Size callback used to implement a custom stream.
|
||||
*
|
||||
* Return MPV_ERROR_UNSUPPORTED if no size is known.
|
||||
*
|
||||
* This callback can be NULL, in which it behaves as if always returning
|
||||
* MPV_ERROR_UNSUPPORTED.
|
||||
*
|
||||
* @param cookie opaque cookie identifying the stream,
|
||||
* returned from mpv_stream_cb_open_fn
|
||||
* @return the total size in bytes of the stream
|
||||
*/
|
||||
typedef int64_t (*mpv_stream_cb_size_fn)(void *cookie);
|
||||
|
||||
/**
|
||||
* Close callback used to implement a custom stream.
|
||||
*
|
||||
* @param cookie opaque cookie identifying the stream,
|
||||
* returned from mpv_stream_cb_open_fn
|
||||
*/
|
||||
typedef void (*mpv_stream_cb_close_fn)(void *cookie);
|
||||
|
||||
/**
|
||||
* See mpv_stream_cb_open_ro_fn callback.
|
||||
*/
|
||||
typedef struct mpv_stream_cb_info {
|
||||
/**
|
||||
* Opaque user-provided value, which will be passed to the other callbacks.
|
||||
* The close callback will be called to release the cookie. It is not
|
||||
* interpreted by mpv. It doesn't even need to be a valid pointer.
|
||||
*
|
||||
* The user sets this in the mpv_stream_cb_open_ro_fn callback.
|
||||
*/
|
||||
void *cookie;
|
||||
|
||||
/**
|
||||
* Callbacks set by the user in the mpv_stream_cb_open_ro_fn callback. Some
|
||||
* of them are optional, and can be left unset.
|
||||
*
|
||||
* The following callbacks are mandatory: read_fn, close_fn
|
||||
*/
|
||||
mpv_stream_cb_read_fn read_fn;
|
||||
mpv_stream_cb_seek_fn seek_fn;
|
||||
mpv_stream_cb_size_fn size_fn;
|
||||
mpv_stream_cb_close_fn close_fn;
|
||||
} mpv_stream_cb_info;
|
||||
|
||||
/**
|
||||
* Open callback used to implement a custom read-only (ro) stream. The user
|
||||
* must set the callback fields in the passed info struct. The cookie field
|
||||
* also can be set to store state associated to the stream instance.
|
||||
*
|
||||
* Note that the info struct is valid only for the duration of this callback.
|
||||
* You can't change the callbacks or the pointer to the cookie at a later point.
|
||||
*
|
||||
* Each stream instance created by the open callback can have different
|
||||
* callbacks.
|
||||
*
|
||||
* The close_fn callback will terminate the stream instance. The pointers to
|
||||
* your callbacks and cookie will be discarded, and the callbacks will not be
|
||||
* called again.
|
||||
*
|
||||
* @param user_data opaque user data provided via mpv_stream_cb_add()
|
||||
* @param uri name of the stream to be opened (with protocol prefix)
|
||||
* @param info fields which the user should fill
|
||||
* @return opaque cookie identifing the newly opened stream
|
||||
* @return 0 on success, MPV_ERROR_LOADING_FAILED if the URI cannot be opened.
|
||||
*/
|
||||
typedef int (*mpv_stream_cb_open_ro_fn)(void *user_data, char *uri,
|
||||
mpv_stream_cb_info *info);
|
||||
|
||||
/**
|
||||
* Add a custom stream protocol. This will register a protocol handler under
|
||||
* the given protocol prefix, and invoke the given callbacks if an URI with the
|
||||
* matching protocol prefix is opened.
|
||||
*
|
||||
* The "ro" is for read-only - only read-only streams can be registered with
|
||||
* this function.
|
||||
*
|
||||
* The callback remains registered until the mpv core is registered.
|
||||
*
|
||||
* If a custom stream with the same name is already registered, then the
|
||||
* MPV_ERROR_INVALID_PARAMETER error is returned.
|
||||
*
|
||||
* @param protocol protocol prefix, for example "foo" for "foo://" URIs
|
||||
* @param user_data opaque pointer passed into the mpv_stream_cb_open_fn
|
||||
* callback.
|
||||
* @return error code
|
||||
*/
|
||||
int mpv_stream_cb_add_ro(mpv_handle *ctx, const char *protocol, void *user_data,
|
||||
mpv_stream_cb_open_ro_fn open_fn);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+10
-2
@@ -215,9 +215,9 @@ struct bstr *bstr_splitlines(void *talloc_ctx, struct bstr str)
|
||||
return r;
|
||||
}
|
||||
|
||||
struct bstr bstr_getline(struct bstr str, struct bstr *rest)
|
||||
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c)
|
||||
{
|
||||
int pos = bstrchr(str, '\n');
|
||||
int pos = bstrchr(str, c);
|
||||
if (pos < 0)
|
||||
pos = str.len;
|
||||
if (rest)
|
||||
@@ -243,6 +243,14 @@ bool bstr_eatstart(struct bstr *s, struct bstr prefix)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bstr_eatend(struct bstr *s, struct bstr prefix)
|
||||
{
|
||||
if (!bstr_endswith(*s, prefix))
|
||||
return false;
|
||||
s->len -= prefix.len;
|
||||
return true;
|
||||
}
|
||||
|
||||
void bstr_lower(struct bstr str)
|
||||
{
|
||||
for (int i = 0; i < str.len; i++)
|
||||
|
||||
+18
-6
@@ -116,10 +116,15 @@ int bstr_validate_utf8(struct bstr s);
|
||||
// talloc, with talloc_ctx as parent.
|
||||
struct bstr bstr_sanitize_utf8_latin1(void *talloc_ctx, struct bstr s);
|
||||
|
||||
// Return the text before the next line break, and return it. Change *rest to
|
||||
// point to the text following this line break. (rest can be NULL.)
|
||||
// Line break characters are not stripped.
|
||||
struct bstr bstr_getline(struct bstr str, struct bstr *rest);
|
||||
// Return the text before the occurrence of a character, and return it. Change
|
||||
// *rest to point to the text following this character. (rest can be NULL.)
|
||||
struct bstr bstr_splitchar(struct bstr str, struct bstr *rest, const char c);
|
||||
|
||||
// Like bstr_splitchar. Trailing newlines are not stripped.
|
||||
static inline struct bstr bstr_getline(struct bstr str, struct bstr *rest)
|
||||
{
|
||||
return bstr_splitchar(str, rest, '\n');
|
||||
}
|
||||
|
||||
// Strip one trailing line break. This is intended for use with bstr_getline,
|
||||
// and will remove the trailing \n or \r\n sequence.
|
||||
@@ -131,8 +136,10 @@ void bstr_xappend_asprintf(void *talloc_ctx, bstr *s, const char *fmt, ...)
|
||||
void bstr_xappend_vasprintf(void *talloc_ctx, bstr *s, const char *fmt, va_list va)
|
||||
PRINTF_ATTRIBUTE(3, 0);
|
||||
|
||||
// If s starts with prefix, return true and return the rest of the string in s.
|
||||
// If s starts/ends with prefix, return true and return the rest of the string
|
||||
// in s.
|
||||
bool bstr_eatstart(struct bstr *s, struct bstr prefix);
|
||||
bool bstr_eatend(struct bstr *s, struct bstr prefix);
|
||||
|
||||
bool bstr_case_startswith(struct bstr s, struct bstr prefix);
|
||||
bool bstr_case_endswith(struct bstr s, struct bstr suffix);
|
||||
@@ -200,11 +207,16 @@ static inline int bstr_find0(struct bstr haystack, const char *needle)
|
||||
return bstr_find(haystack, bstr0(needle));
|
||||
}
|
||||
|
||||
static inline int bstr_eatstart0(struct bstr *s, const char *prefix)
|
||||
static inline bool bstr_eatstart0(struct bstr *s, const char *prefix)
|
||||
{
|
||||
return bstr_eatstart(s, bstr0(prefix));
|
||||
}
|
||||
|
||||
static inline bool bstr_eatend0(struct bstr *s, const char *prefix)
|
||||
{
|
||||
return bstr_eatend(s, bstr0(prefix));
|
||||
}
|
||||
|
||||
// create a pair (not single value!) for "%.*s" printf syntax
|
||||
#define BSTR_P(bstr) (int)((bstr).len), ((bstr).start ? (char*)(bstr).start : "")
|
||||
|
||||
|
||||
Loaded 100 of 290 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user