Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10a005df0c | ||
|
|
b3d670a3e1 | ||
|
|
1c761bd6f5 | ||
|
|
1d5e95783e | ||
|
|
9d21f2503f | ||
|
|
c560f6ff0a | ||
|
|
17d6ba7f77 | ||
|
|
b1c0bbe8b8 | ||
|
|
1ba3525816 | ||
|
|
99a4be4127 | ||
|
|
3b5777d86a | ||
|
|
e57037dc95 | ||
|
|
4bfec4279f | ||
|
|
1ef9876b7d | ||
|
|
a61eb81141 | ||
|
|
ff9f5e06ff | ||
|
|
2b8b17402e | ||
|
|
aab98776f6 | ||
|
|
a8347eb9ba | ||
|
|
870a6a11d9 | ||
|
|
544110bf43 | ||
|
|
a6035439a9 | ||
|
|
3bb06155c9 | ||
|
|
bf5727a60f | ||
|
|
765a7228d1 | ||
|
|
6b6789a47a | ||
|
|
ed7e650d3a | ||
|
|
4399be5b62 | ||
|
|
c21c68edd4 | ||
|
|
2d9b6ff7cd | ||
|
|
0aa9b2a469 | ||
|
|
82855bec50 | ||
|
|
c324bfab59 | ||
|
|
0eb87e1baf | ||
|
|
2aebf3e482 | ||
|
|
736cd7336a | ||
|
|
a8b8295f8d | ||
|
|
51fd8f6fe1 | ||
|
|
3eceac2eab | ||
|
|
a660e15c9b | ||
|
|
2b1d150a53 | ||
|
|
42799005dc | ||
|
|
b60817f9dd | ||
|
|
ceb2e1026d | ||
|
|
83c5f704e7 | ||
|
|
47f974a395 | ||
|
|
f53b2952eb | ||
|
|
5544ffecd9 | ||
|
|
796b48b2a3 | ||
|
|
a7a919f93f | ||
|
|
a89785f297 | ||
|
|
09238a9bb5 | ||
|
|
ec74a79e12 | ||
|
|
20f02229cd | ||
|
|
4958c1a556 | ||
|
|
3203d6003c | ||
|
|
89837523b5 | ||
|
|
1a2319f3e4 | ||
|
|
98a257b3a8 | ||
|
|
c03a67c37c | ||
|
|
48a7c4be3a | ||
|
|
5aab17f833 | ||
|
|
e89382f5f6 | ||
|
|
7eacaf51f8 | ||
|
|
3abb6f1fef | ||
|
|
755e9fad29 | ||
|
|
f696975fe3 | ||
|
|
79d99bd264 | ||
|
|
f9668f5596 | ||
|
|
535e180b9e | ||
|
|
6b3d682f4e | ||
|
|
f5e82d5ed3 | ||
|
|
ebd9ce9fca | ||
|
|
585c5c34f1 | ||
|
|
5087816a74 | ||
|
|
7201fd7d08 | ||
|
|
78f76bdddd | ||
|
|
c731513efa | ||
|
|
07b6969ba4 | ||
|
|
f30c5d09f4 | ||
|
|
745862131f | ||
|
|
f843ae9983 | ||
|
|
de37c5b1cb | ||
|
|
fcba41e2e4 | ||
|
|
b39a7fddb5 | ||
|
|
1d4273cfb5 | ||
|
|
8700700de8 | ||
|
|
eafc273d2c | ||
|
|
bb48f09532 | ||
|
|
5a011c5c06 | ||
|
|
c4d6fcbb02 | ||
|
|
6250007bca | ||
|
|
803e2c5f87 | ||
|
|
40b626fd9b | ||
|
|
a3c27786c6 | ||
|
|
03ea61c895 | ||
|
|
274c6f36f5 | ||
|
|
79efe248a5 | ||
|
|
106f607ef7 | ||
|
|
c1ae1def85 | ||
|
|
a2b93e0c27 | ||
|
|
84513ba58b | ||
|
|
cf85191cb7 | ||
|
|
19c9347e47 | ||
|
|
aceeeaf9bb | ||
|
|
98e7b4e538 | ||
|
|
ca175871cd | ||
|
|
fc1017c335 | ||
|
|
67467103e8 | ||
|
|
a55e8ff31c | ||
|
|
b787a4121a | ||
|
|
adf65634d8 | ||
|
|
5beb230690 | ||
|
|
616ecd9365 | ||
|
|
c676c31815 | ||
|
|
81ceb7b6a5 | ||
|
|
33012b4141 | ||
|
|
1d51dc20ea | ||
|
|
2e113a7391 | ||
|
|
2b71bef2ba | ||
|
|
8f1ec91b45 | ||
|
|
8b7f23129d | ||
|
|
6a06e6002b | ||
|
|
de72cb2c33 | ||
|
|
4914e688cf | ||
|
|
932ecbfe9e | ||
|
|
7c9f32620f | ||
|
|
3f5b41dfa3 | ||
|
|
0dc1fe3269 | ||
|
|
654721c27b | ||
|
|
5838322975 | ||
|
|
8dff51e06c | ||
|
|
17733bd5b8 | ||
|
|
eedda59a69 | ||
|
|
8fad4b8eab | ||
|
|
bc77565838 | ||
|
|
b0ef3dd4fb | ||
|
|
1ff6d8ae2e | ||
|
|
1afc0c8b55 | ||
|
|
a1801da717 | ||
|
|
eac977b12f | ||
|
|
3adc6071b6 | ||
|
|
0c3e4b13ac | ||
|
|
22133f8efc | ||
|
|
4abd23bd95 | ||
|
|
993466fc30 | ||
|
|
ad819b5458 | ||
|
|
894dbcd1b7 | ||
|
|
90b968a67a | ||
|
|
fc4318d23e | ||
|
|
65c06511bf | ||
|
|
ee4bed25a8 | ||
|
|
6953a1ca2f | ||
|
|
c8e3ee494c | ||
|
|
363982c774 | ||
|
|
02d2c2cc97 | ||
|
|
6e143ffec3 | ||
|
|
61ff2408e3 | ||
|
|
d0b997d528 | ||
|
|
18681a7dd2 | ||
|
|
3a78eefc88 | ||
|
|
1bf474e19a | ||
|
|
bf5c4c42fa | ||
|
|
11b8cbcce7 | ||
|
|
082340b467 | ||
|
|
5f544c1081 | ||
|
|
9ea9bdf130 | ||
|
|
202f695398 | ||
|
|
f64de3ea66 | ||
|
|
61a51c57a9 | ||
|
|
b8b3163b85 | ||
|
|
d38baa65ca | ||
|
|
8b00d82b79 | ||
|
|
7cd32ec29e | ||
|
|
183af9d72e | ||
|
|
ea03ae157f | ||
|
|
a6da4faac5 | ||
|
|
773d52162e | ||
|
|
cc9e8e7e3d | ||
|
|
ee2ba599e7 | ||
|
|
26458419a7 | ||
|
|
4bd3e51fbe | ||
|
|
5da510b5fc | ||
|
|
6b18d4dba5 | ||
|
|
dd02369c32 | ||
|
|
7684483052 | ||
|
|
e6b85c9170 | ||
|
|
5d74fa7dc0 | ||
|
|
a2fa0d0b68 | ||
|
|
b86a2316df | ||
|
|
912d668ea8 | ||
|
|
c398ab7520 | ||
|
|
68839e4e77 | ||
|
|
51dbb5607e | ||
|
|
139f6b5de7 | ||
|
|
118d7a310e | ||
|
|
0a81fe1cf9 | ||
|
|
8956229ee4 | ||
|
|
992bdc9411 | ||
|
|
5d3657e092 | ||
|
|
06065dc5ef | ||
|
|
2a99e7ae12 | ||
|
|
b99dc17f8e | ||
|
|
8bb57c0387 | ||
|
|
13259f35c8 | ||
|
|
a049a3240e | ||
|
|
0af8811b15 | ||
|
|
6a8d1cdd49 | ||
|
|
b5357e8ba7 | ||
|
|
65c75112a7 | ||
|
|
e41dbc3019 | ||
|
|
7022a24542 | ||
|
|
403f489f6c | ||
|
|
e9c43258b8 | ||
|
|
c3ae2ff107 | ||
|
|
ea1b183f4c | ||
|
|
e543853a7f | ||
|
|
9340f19ba2 | ||
|
|
53798b6465 | ||
|
|
d7e587acc9 | ||
|
|
6fb12a5fda | ||
|
|
3825dc63b2 | ||
|
|
e6291697e3 | ||
|
|
6789f9b094 | ||
|
|
387f501278 | ||
|
|
39f515cb6a | ||
|
|
13dcf82a96 | ||
|
|
202f14aa29 | ||
|
|
75cab315ea | ||
|
|
ba3f6f3779 | ||
|
|
1648ff8a0f | ||
|
|
abbc8fc84a | ||
|
|
7debdde9b9 | ||
|
|
81d125efd8 | ||
|
|
ff531b71e3 | ||
|
|
3a5cbf3907 | ||
|
|
6f4d918cb7 | ||
|
|
8d29d5b5d7 | ||
|
|
39fc5e1deb | ||
|
|
e3a57272a7 | ||
|
|
486b3ce6f8 | ||
|
|
82231fd74d | ||
|
|
052584c9e2 | ||
|
|
52fea2f909 | ||
|
|
026cccaddf | ||
|
|
ea50f6fdef | ||
|
|
33c24b07e4 | ||
|
|
5f547e57e3 | ||
|
|
51f10c512e | ||
|
|
ab07caf39d | ||
|
|
2aba6972cf | ||
|
|
2f1af04745 | ||
|
|
d3111ee777 | ||
|
|
1d385b0dae | ||
|
|
7e6456f43a | ||
|
|
b6920372c2 | ||
|
|
86ab4b8a9f | ||
|
|
b81ae52f50 | ||
|
|
c245d04c37 | ||
|
|
f549cec0ac | ||
|
|
6487ba4864 | ||
|
|
1557ca6712 | ||
|
|
b344da22ae | ||
|
|
ef2bbd5a7a | ||
|
|
73a5bde518 | ||
|
|
b712095d89 | ||
|
|
3751065f97 | ||
|
|
c239b7de7e | ||
|
|
45c97aea78 | ||
|
|
b6cbf74518 | ||
|
|
0fe3e9ca5e | ||
|
|
9f30cd8292 | ||
|
|
f0ab9f05f1 | ||
|
|
dd339b200b | ||
|
|
e6dedbcc23 | ||
|
|
a314b1013f | ||
|
|
0f110ad0e2 | ||
|
|
41002c46f5 | ||
|
|
29d74ca2f5 | ||
|
|
077dd27df2 | ||
|
|
cad6fb038b | ||
|
|
733218b233 | ||
|
|
88f10ec84f | ||
|
|
2361ec35aa | ||
|
|
68d2903cb1 | ||
|
|
e2e9a7faa8 | ||
|
|
343da8d73d | ||
|
|
441febfcba | ||
|
|
cb46f73cbb | ||
|
|
1500c84226 | ||
|
|
9eef41dec1 | ||
|
|
f0fd663320 | ||
|
|
b9153ee177 | ||
|
|
f4db6b8479 | ||
|
|
5f2822d502 | ||
|
|
9664bc23d5 | ||
|
|
6f6c4a57ec | ||
|
|
caa14e3d45 | ||
|
|
3a43682355 | ||
|
|
075edc6fee | ||
|
|
2ac74977c5 | ||
|
|
01e95468f9 | ||
|
|
efbfc00741 | ||
|
|
6fe83fdc33 | ||
|
|
6ba2bbd090 | ||
|
|
9179e8ed21 | ||
|
|
c296b6204f | ||
|
|
e300bfcf3a | ||
|
|
9df797575f | ||
|
|
78b3852f7e | ||
|
|
7783f0b7d7 | ||
|
|
75d12c174f | ||
|
|
14c232bdbf | ||
|
|
47f3cc7e6b | ||
|
|
b521f15ae9 | ||
|
|
33598182a0 | ||
|
|
f66f0b34c8 | ||
|
|
af6126adbe | ||
|
|
d080851a30 | ||
|
|
41d9f2f058 | ||
|
|
89674854ce | ||
|
|
23639e5b0e | ||
|
|
e13eb3fede | ||
|
|
bf385e1140 | ||
|
|
fe872f5688 | ||
|
|
6b5e5b6843 | ||
|
|
5fca497698 | ||
|
|
995962291a | ||
|
|
b6ba87af4b | ||
|
|
fb67db8b72 | ||
|
|
bf5c97a6c3 | ||
|
|
df85c76b7f | ||
|
|
44a7cb7f0e | ||
|
|
ce65ea3345 | ||
|
|
b62634c051 | ||
|
|
75fe626aa6 | ||
|
|
fe7db61035 | ||
|
|
32f235bcef | ||
|
|
a802afb206 | ||
|
|
297f9f1bec | ||
|
|
f504661852 | ||
|
|
57eca14a45 | ||
|
|
ed8540c38e | ||
|
|
0525f5fa93 | ||
|
|
08432fcbd1 | ||
|
|
2415b69572 | ||
|
|
3ecc6d0a79 | ||
|
|
9e972ed7d2 | ||
|
|
62d4a3891a | ||
|
|
79e20ff485 | ||
|
|
74c342c6d3 | ||
|
|
f8659d0013 | ||
|
|
554c3a1bda | ||
|
|
cb604d5412 | ||
|
|
a3e8ff624c | ||
|
|
2d34171bec | ||
|
|
ffc9aa8104 | ||
|
|
b44d075164 | ||
|
|
b7d5cbd375 | ||
|
|
dc48893630 | ||
|
|
d1d5e9dda4 | ||
|
|
7fa26bfd9c | ||
|
|
4395a4f837 | ||
|
|
56616b0b01 | ||
|
|
17e3e800e1 | ||
|
|
ccf3458f8a | ||
|
|
c47ae06ed8 | ||
|
|
8716c2e88f | ||
|
|
15baf2789c | ||
|
|
03fec24e19 | ||
|
|
b8ade7c99b | ||
|
|
f845f64c2a | ||
|
|
b83bfea05d | ||
|
|
cf5c3fc31a | ||
|
|
8bfcae748c | ||
|
|
9c9cf125ad | ||
|
|
5968a307d5 | ||
|
|
d1f5b926ac | ||
|
|
2b0c620b22 | ||
|
|
98f1b5fb98 | ||
|
|
1b5b23b948 | ||
|
|
f143665864 | ||
|
|
30d147687f | ||
|
|
88a07c5f53 | ||
|
|
e24ba8fa7f | ||
|
|
ffbc85cde9 | ||
|
|
8f1a889f75 | ||
|
|
9b6c93e904 | ||
|
|
c56f5f0681 | ||
|
|
215268ea34 | ||
|
|
d2e8bc4499 | ||
|
|
c48bd0ef18 | ||
|
|
9def3682d1 | ||
|
|
5819a4e301 | ||
|
|
060599ac6a | ||
|
|
0ccceecdc6 | ||
|
|
6dc9280b58 | ||
|
|
3905c46f9a | ||
|
|
5c0c0e469b | ||
|
|
f7471b7ff4 | ||
|
|
6a0df47d94 | ||
|
|
fb8f2a812e | ||
|
|
00f8ee573b | ||
|
|
08cbac311d | ||
|
|
60a0815b4c | ||
|
|
5edd6a8caa | ||
|
|
0e72f64ff4 | ||
|
|
876ec446f3 | ||
|
|
8c39c6903b | ||
|
|
e8cdc22245 | ||
|
|
b261e1e782 | ||
|
|
9a873cc53b | ||
|
|
274e71ee8b | ||
|
|
343f5ca24b | ||
|
|
043512045e | ||
|
|
6ac0ef78c5 | ||
|
|
3f7e43c2e2 | ||
|
|
71681e04ee | ||
|
|
440e0a98ab | ||
|
|
1e684e3cca | ||
|
|
eec7660274 | ||
|
|
defcb9047e | ||
|
|
fc1c004cd8 | ||
|
|
484328fe04 | ||
|
|
ebc04333d2 | ||
|
|
5ca654301b | ||
|
|
7383b45682 | ||
|
|
fdc5388f8c | ||
|
|
76818e3dc7 | ||
|
|
d054a7181f | ||
|
|
04320d26eb | ||
|
|
c157641019 | ||
|
|
5324fb731f | ||
|
|
9213db0d49 | ||
|
|
d66f2a679b | ||
|
|
27e3f04de7 | ||
|
|
ea94b01e6b | ||
|
|
5e30e7a041 | ||
|
|
35e8b6c1e6 | ||
|
|
ca55a32922 | ||
|
|
c8cc1f401b | ||
|
|
7177ef3e1c | ||
|
|
a2fce5ba26 | ||
|
|
c3097422f2 | ||
|
|
8bb9632e27 | ||
|
|
2048ad2b8a | ||
|
|
622f9cf1aa | ||
|
|
4552ee286b | ||
|
|
062423381d | ||
|
|
2372b0a6f6 | ||
|
|
2f1eb49e9f | ||
|
|
355361e1e3 | ||
|
|
5a7b1ff4c0 | ||
|
|
591e21a2eb | ||
|
|
c6b7a4dace | ||
|
|
2a4e7dcaad | ||
|
|
d4d8b3a4fc | ||
|
|
9f0e7bb998 | ||
|
|
e66c098228 | ||
|
|
618d7f59b0 | ||
|
|
723e3ec8d9 | ||
|
|
9ab0f60d44 | ||
|
|
e5cefa346d | ||
|
|
c36aaeeb2a | ||
|
|
c4e01cc00d | ||
|
|
1d9032f011 | ||
|
|
13786dc643 | ||
|
|
69283bc0f8 | ||
|
|
633eb30cbe | ||
|
|
327cb2a06c | ||
|
|
9e6b9d8a98 | ||
|
|
726ef35aa8 | ||
|
|
4ab860cddc | ||
|
|
cc813647d5 | ||
|
|
6cd80e972e | ||
|
|
3878a59e2c | ||
|
|
2619d8eff4 | ||
|
|
d889d1fbaa | ||
|
|
e955ae9000 | ||
|
|
4236321835 | ||
|
|
4f263dce34 | ||
|
|
590caeb2bd | ||
|
|
0c2c059826 | ||
|
|
9bc39b352e | ||
|
|
ece86d1061 | ||
|
|
c92450d7fb | ||
|
|
a85eecfe40 | ||
|
|
eb14b18a33 | ||
|
|
9770ce6c44 | ||
|
|
b92d3b6d44 | ||
|
|
cd7c7d0841 | ||
|
|
b2c84a91b6 | ||
|
|
849480d0c9 | ||
|
|
a07dae57e3 | ||
|
|
72c6bf1345 | ||
|
|
2c917219cf | ||
|
|
423e53ba0b | ||
|
|
f2e25e9e1f | ||
|
|
b2657814c9 | ||
|
|
4fa6bcbb90 | ||
|
|
875aeb0f5c | ||
|
|
c72df80460 | ||
|
|
907bf83297 | ||
|
|
3659f9e416 | ||
|
|
ce05413a87 | ||
|
|
453fea87fa | ||
|
|
1393d79417 | ||
|
|
192a7344d9 | ||
|
|
e4e1dc3c79 | ||
|
|
d32bee5f01 | ||
|
|
17dbb39dec | ||
|
|
6b4f560f3c | ||
|
|
ec3c428e5f | ||
|
|
e22ae2cba0 | ||
|
|
aae47146a7 | ||
|
|
7539928c1c | ||
|
|
c55d859920 | ||
|
|
2057209057 | ||
|
|
b10dcecf7d | ||
|
|
7dde096d8a | ||
|
|
e024906408 | ||
|
|
ce8dae3f0d | ||
|
|
f519887fe7 | ||
|
|
7614f2b3a6 | ||
|
|
4d75514321 | ||
|
|
af1379c43d | ||
|
|
e65a8d7b61 | ||
|
|
5f88e6a0db | ||
|
|
3bb134969e | ||
|
|
6f6d2eb770 | ||
|
|
f3b3702025 | ||
|
|
d223959cda | ||
|
|
ae3bbc4f63 | ||
|
|
cb2cb540f4 | ||
|
|
3e0d4de988 | ||
|
|
251ecf6e4b | ||
|
|
edbb8f6286 | ||
|
|
0110b738d5 | ||
|
|
bda614bfd8 | ||
|
|
f42e4374d5 | ||
|
|
a9a55ea7f2 | ||
|
|
5e56c07417 | ||
|
|
5086b2d456 | ||
|
|
7af6e64db7 | ||
|
|
eed99d3609 | ||
|
|
a05f20ea1e | ||
|
|
ed62f56a40 | ||
|
|
37d6604d70 | ||
|
|
872b7a2654 | ||
|
|
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 |
No files matched your search
@@ -8,12 +8,14 @@ If you're not using git master or the latest release, update.
|
||||
|
||||
### Actual behavior
|
||||
|
||||
### Log files
|
||||
### Log file
|
||||
|
||||
Make a log file made with -v or --log-file=output.txt, paste it to
|
||||
http://sprunge.us or a similar site, and replace this text with a link to it.
|
||||
|
||||
Do not bother reporting a bug if you do not provide the required information.
|
||||
Providing a log file is strongly encouraged. It is very helpful for reproducing
|
||||
bugs or getting important technical context. Without a log it might not be
|
||||
possible to analyze and fix certain bugs.
|
||||
|
||||
### Sample files
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
/demux/ebml_types.h
|
||||
/sub/osd_font.h
|
||||
/player/lua/*.inc
|
||||
/player/builtin_conf.inc
|
||||
/DOCS/man/mpv.1
|
||||
/DOCS/man/mpv.aux
|
||||
/DOCS/man/mpv.log
|
||||
|
||||
@@ -12,6 +12,8 @@ env:
|
||||
global:
|
||||
# Coverity token
|
||||
- secure: "H21mSRlMhk4BKS0xHZvCFGJxteCP0hRVUxTuNfM2Z9HBsyutuLEYMtViLO86VtM+Tqla3xXPzUdS4ozLwI72Ax/5ZUDXACROj73yW6QhFB5D6rLut12+FjqC7M33Qv2hl0xwgNBmR5dsm1ToP37+Wn+ecJQNvN8fkTXF+HVzOEw="
|
||||
# Travis token for mpv.io
|
||||
- secure: "nlTVLJK6kRhtXvhKCoJ3YdFGHuKaq/eHowfPw25hqRWuBOZd+HjHY5KIYjV7SxuKFDpJE4GpNcvA3Q31nsqomxpkLYgrwjg6TSazN7ZP+x85ZgV1QGFebrPfGm2n5UR5CAPAwFoeF3pZheLi4bajVzwq1fWW+x3grS188P9OZso="
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
@@ -42,6 +44,7 @@ script:
|
||||
- ./bootstrap.py
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
after_script: TOOLS/travis-rebuild-website
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
@@ -32,7 +32,28 @@ API changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.19.0
|
||||
--- mpv 0.23.0 ---
|
||||
1.24 - the deprecated mpv_suspend() and mpv_resume() APIs now do nothing.
|
||||
--- mpv 0.22.0 ---
|
||||
1.23 - deprecate setting "no-" options via mpv_set_option*(). For example,
|
||||
instead of "no-video=" you should set "video=no".
|
||||
- do not override the SIGPIPE signal handler anymore. This was done as
|
||||
workaround for the FFmpeg TLS code, which has been fixed long ago.
|
||||
- deprecate mpv_suspend() and mpv_resume(). They will be stubbed out
|
||||
in mpv 0.23.0.
|
||||
- make mpv_set_property() work to some degree before mpv_initialize().
|
||||
It can now be used instead of mpv_set_option().
|
||||
- semi-deprecate mpv_set_option()/mpv_set_option_string(). You should
|
||||
use mpv_set_property() instead. There are some deprecated properties
|
||||
which conflict with some options (see client.h remarks on
|
||||
mpv_set_option()), for which mpv_set_option() might still be required.
|
||||
In future mpv releases, the conflicting deprecated options/properties
|
||||
will be removed, and mpv_set_option() will internally translate API
|
||||
calls to mpv_set_property().
|
||||
- qthelper.hpp: deprecate get_property_variant, set_property_variant,
|
||||
set_option_variant, command_variant, and replace them with
|
||||
get_property, set_property, command.
|
||||
--- 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
|
||||
|
||||
+11
-15
@@ -104,10 +104,10 @@ Installing MSYS2
|
||||
It doesn't matter whether the i686 or the x86_64 version is used. Both can
|
||||
build 32-bit and 64-bit binaries when running on a 64-bit version of Windows.
|
||||
|
||||
2. Start a MinGW-w64 shell (``mingw64_shell.bat``). Note that this is different
|
||||
2. Start a MinGW-w64 shell (``mingw64.exe``). Note that this is different
|
||||
from the MSYS2 shell that is started from the final installation dialog.
|
||||
|
||||
For a 32-bit build, use ``mingw32_shell.bat``.
|
||||
For a 32-bit build, use ``mingw32.exe``.
|
||||
|
||||
Updating MSYS2
|
||||
--------------
|
||||
@@ -118,7 +118,7 @@ separately.
|
||||
```bash
|
||||
# Check for core updates. If instructed, close the shell window and reopen it
|
||||
# before continuing.
|
||||
update-core
|
||||
pacman -Syu
|
||||
|
||||
# Update everything else
|
||||
pacman -Su
|
||||
@@ -166,11 +166,8 @@ Or, compile and install both libmpv and mpv:
|
||||
./waf configure CC=gcc.exe --check-c-compiler=gcc --enable-libmpv-shared --prefix=/mingw64
|
||||
./waf install
|
||||
|
||||
# waf installs libmpv to the wrong directory, so fix it up
|
||||
mv -f /mingw64/bin/pkgconfig/mpv.pc /mingw64/lib/pkgconfig/
|
||||
mv -f /mingw64/bin/libmpv.dll.a /mingw64/lib/
|
||||
sed -i 's_/mingw64/bin_/mingw64/lib_' /mingw64/lib/pkgconfig/mpv.pc
|
||||
rmdir /mingw64/bin/pkgconfig
|
||||
# waf installs libmpv dll to the wrong directory, so fix it up
|
||||
mv -f /mingw64/lib/mpv-1.dll /mingw64/bin/
|
||||
```
|
||||
|
||||
Linking libmpv with MSVC programs
|
||||
@@ -208,11 +205,10 @@ DLLs in that folder. The simplest solution is to add ``C:\msys64\mingw64\bin``
|
||||
to the windows system ``%PATH%``. Beware though that this can cause problems or
|
||||
confusion in Cygwin if that is also installed on the machine.
|
||||
|
||||
Use of the ANGLE OpenGL backend requires a copy of ``d3dcompiler_43.dll`` (yes,
|
||||
exactly 43) in the path or in the same folder as mpv. It must be of the same
|
||||
architecture (x86_64 / i686) as the mpv you compiled. You can find a copy in the
|
||||
official mpv builds:
|
||||
Use of the ANGLE OpenGL backend requires a copy of the D3D compiler DLL that
|
||||
matches the version of the D3D SDK that ANGLE was built with
|
||||
(``d3dcompiler_43.dll`` in case of MinGW-built ANGLE) in the path or in the
|
||||
same folder as mpv. It must be of the same architecture (x86_64 / i686) as the
|
||||
mpv you compiled. You can find copies here:
|
||||
|
||||
https://mpv.srsfckn.biz/mpv-x86_64-20160118.7z
|
||||
|
||||
https://mpv.srsfckn.biz/mpv-i686-20160118.7z
|
||||
https://mpv.srsfckn.biz/d3dcompiler.7z
|
||||
@@ -19,6 +19,125 @@ Interface changes
|
||||
|
||||
::
|
||||
|
||||
--- mpv 0.23.0 ---
|
||||
- remove deprecated vf_vdpaurb (use "--hwdec=vdpau-copy" instead)
|
||||
- the following properties now have new semantics:
|
||||
- "demuxer" (use "current-demuxer")
|
||||
- "fps" (use "container-fps")
|
||||
- "idle" (use "idle-active")
|
||||
- "cache" (use "cache-percent")
|
||||
- "audio-samplerate" (use "audio-params/samplerate")
|
||||
- "audio-channels" (use "audio-params/channel-count")
|
||||
- "audio-format" (use "audio-codec-name")
|
||||
(the properties equivalent to the old semantics are in parentheses)
|
||||
- remove deprecated --vo and --ao sub-options (like --vo=opengl:...), and
|
||||
replace them with global options. A somewhat complete list can be found
|
||||
here: https://github.com/mpv-player/mpv/wiki/Option-replacement-list#mpv-0210
|
||||
- remove --vo-defaults and --ao-defaults as well
|
||||
- remove deprecated global sub-options (like -demuxer-rawaudio format=...),
|
||||
use flat options (like --demuxer-rawaudio-format=...)
|
||||
- the --sub-codepage option changes in incompatible ways:
|
||||
- detector-selection and fallback syntax is deprecated
|
||||
- enca/libguess are removed and deprecated (behaves as if they hadn't
|
||||
been compiled-in)
|
||||
- --sub-codepage=<codepage> does not force the codepage anymore
|
||||
(this requires different and new syntax)
|
||||
- remove --fs-black-out-screens option for macOS
|
||||
- change how spdif codecs are selected. You can't enable spdif passthrough
|
||||
with --ad anymore. This was deprecated; use --audio-spdif instead.
|
||||
- deprecate the "family" selection with --ad/--vd
|
||||
forcing/excluding codecs with "+", "-", "-" is deprecated as well
|
||||
- explicitly mark --ad-spdif-dtshd as deprecated (it was done so a long time
|
||||
ago, but it didn't complain when using the option)
|
||||
--- mpv 0.22.0 ---
|
||||
- the "audio-device-list" property now sets empty device description to the
|
||||
device name as a fallback
|
||||
- add --hidpi-window-scale option for macOS
|
||||
- add audiounit audio output for iOS
|
||||
- make --start-time work with --rebase-start-time=no
|
||||
- add --opengl-early-flush=auto mode
|
||||
- add --hwdec=vdpau-copy, deprecate vf_vdpaurb
|
||||
- add tct video output for true-color and 256-color terminals
|
||||
--- mpv 0.21.0 ---
|
||||
- unlike in older versions, setting options at runtime will now take effect
|
||||
immediately (see for example issue #3281). On the other hand, it will also
|
||||
do runtime verification and reject option changes that do not work
|
||||
(example: setting the "vf" option to a filter during playback, which fails
|
||||
to initialize - the option value will remain at its old value). In general,
|
||||
"set name value" should be mostly equivalent to "set options/name value"
|
||||
in cases where the "name" property is not deprecated and "options/name"
|
||||
exists - deviations from this are either bugs, or documented as caveats
|
||||
in the "Inconsistencies between options and properties" manpage section.
|
||||
- deprecate _all_ --vo and --ao suboptions. Generally, all suboptions are
|
||||
replaced by global options, which do exactly the same. For example,
|
||||
"--vo=opengl:scale=nearest" turns into "--scale=nearest". In some cases,
|
||||
the global option is prefixed, e.g. "--vo=opengl:pbo" turns into
|
||||
"--opengl-pbo".
|
||||
Most of the exact replacements are documented here:
|
||||
https://github.com/mpv-player/mpv/wiki/Option-replacement-list
|
||||
- remove --vo=opengl-hq. Set --profile=opengl-hq instead. Note that this
|
||||
profile does not force the VO. This means if you use the --vo option to
|
||||
set another VO, it won't work. But this also means it can be used with
|
||||
opengl-cb.
|
||||
- remove the --vo=opengl "pre-shaders", "post-shaders" and "scale-shader"
|
||||
sub-options: they were deprecated in favor of "user-shaders"
|
||||
- deprecate --vo-defaults (no replacement)
|
||||
- remove the vo-cmdline command. You can set OpenGL renderer options
|
||||
directly via properties instead.
|
||||
- deprecate the device/sink options on all AOs. Use --audio-device instead.
|
||||
- deprecate "--ao=wasapi:exclusive" and "--ao=coreaudio:exclusive",
|
||||
use --audio-exclusive instead.
|
||||
- subtle changes in how "--no-..." options are treated mean that they are
|
||||
not accessible under "options/..." anymore (instead, these are resolved
|
||||
at parsing time). This does not affect options which start with "--no-",
|
||||
but do not use the mechanism for negation options.
|
||||
(Also see client API change for API version 1.23.)
|
||||
- rename the following properties
|
||||
- "demuxer" -> "current-demuxer"
|
||||
- "fps" -> "container-fps"
|
||||
- "idle" -> "idle-active"
|
||||
- "cache" -> "cache-percent"
|
||||
the old names are deprecated and will change behavior in mpv 0.23.0.
|
||||
- remove deprecated "hwdec-active" and "hwdec-detected" properties
|
||||
- deprecate the ao and vo auto-profiles (they never made any sense)
|
||||
- deprecate "--vo=direct3d_shaders" - use "--vo=direct3d" instead.
|
||||
Change "--vo=direct3d" to always use shaders by default.
|
||||
- deprecate --playlist-pos option, renamed to --playlist-start
|
||||
- deprecate the --chapter option, as it is redundant with --start/--end,
|
||||
and conflicts with the semantics of the "chapter" property
|
||||
- rename --sub-text-* to --sub-* and --ass-* to --sub-ass-* (old options
|
||||
deprecated)
|
||||
- incompatible change to cdda:// protocol options: the part after cdda://
|
||||
now always sets the device, not the span or speed to be played. No
|
||||
separating extra "/" is needed. The hidden --cdda-device options is also
|
||||
deleted (it was redundant with the documented --cdrom-device).
|
||||
- deprecate --vo=rpi. It will be removed in mpv 0.23.0. Its functionality
|
||||
was folded into --vo=opengl, which now uses RPI hardware decoding by
|
||||
treating it as a hardware overlay (without applying GL filtering). Also
|
||||
to be changed in 0.23.0: the --fs flag will be reset to "no" by default
|
||||
(like on the other platforms).
|
||||
- deprecate --mute=auto (informally has been since 0.18.1)
|
||||
- deprecate "resume" and "suspend" IPC commands. They will be completely
|
||||
removed in 0.23.0.
|
||||
- deprecate mp.suspend(), mp.resume(), mp.resume_all() Lua scripting
|
||||
commands, as well as setting mp.use_suspend. They will be completely
|
||||
removed in 0.23.0.
|
||||
- the "seek" command's absolute seek mode will now interpret negative
|
||||
seek times as relative from the end of the file (and clamps seeks that
|
||||
still go before 0)
|
||||
- add almost all options to the property list, meaning you can change
|
||||
options without adding "options/" to the property name (a new section
|
||||
has been added to the manpage describing some conflicting behavior
|
||||
between options and properties)
|
||||
- implement changing sub-speed during playback
|
||||
- make many previously fixed options changeable at runtime (for example
|
||||
--terminal, --osc, --ytdl, can all be enable/disabled after
|
||||
mpv_initialize() - this can be extended to other still fixed options
|
||||
on user requests)
|
||||
--- 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 ---
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
Client API (libmpv) documentation and examples
|
||||
==============================================
|
||||
|
||||
libmpv lets you embed mpv as a library. It is meant to enable development of
|
||||
GUI frontends.
|
||||
|
||||
Since libmpv merely allows access to underlying mechanisms that can control
|
||||
mpv, documentation is spread over a few places:
|
||||
|
||||
- https://github.com/mpv-player/mpv/blob/master/libmpv/client.h
|
||||
- http://mpv.io/manual/master/#options
|
||||
- http://mpv.io/manual/master/#list-of-input-commands
|
||||
- http://mpv.io/manual/master/#properties
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
|
||||
+20
-6
@@ -312,6 +312,13 @@ 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.
|
||||
|
||||
This filter supports the following ``af-command`` commands:
|
||||
|
||||
``set-matrix``
|
||||
Set the ``<matrix>`` argument dynamically. This can be used to change
|
||||
the mixing matrix at runtime, without reinitializing the entire filter
|
||||
chain.
|
||||
|
||||
``drc[=method:target]``
|
||||
Applies dynamic range compression. This maximizes the volume by compressing
|
||||
the audio signal's dynamic range. (Formerly called ``volnorm``.)
|
||||
@@ -394,10 +401,6 @@ Available filters are:
|
||||
``mpv --af=scaletempo=stride=30:overlap=.50:search=10 media.ogg``
|
||||
Would tweak the quality and performance parameters.
|
||||
|
||||
``mpv --af=format=float,scaletempo media.ogg``
|
||||
Would make scaletempo use float code. Maybe faster on some
|
||||
platforms.
|
||||
|
||||
``mpv --af=scaletempo=scale=1.2:speed=pitch audio.ogg``
|
||||
Would play media at 1.2x normal speed, with audio at normal pitch.
|
||||
Changing playback speed would change pitch, leaving audio tempo at
|
||||
@@ -406,9 +409,13 @@ Available filters are:
|
||||
``rubberband``
|
||||
High quality pitch correction with librubberband. This can be used in place
|
||||
of ``scaletempo``, and will be used to adjust audio pitch when playing
|
||||
at speed different from normal.
|
||||
at speed different from normal. It can also be used to adjust audio pitch
|
||||
without changing playback speed.
|
||||
|
||||
This filter has a number of sub-options. You can list them with
|
||||
``<pitch-scale>``
|
||||
Sets the pitch scaling factor. Frequencies are multiplied by this value.
|
||||
|
||||
This filter has a number of additional sub-options. You can list them with
|
||||
``mpv --af=rubberband=help``. This will also show the default values
|
||||
for each option. The options are not documented here, because they are
|
||||
merely passed to librubberband. Look at the librubberband documentation
|
||||
@@ -417,6 +424,13 @@ Available filters are:
|
||||
(The mapping of the mpv rubberband filter sub-option names and values to
|
||||
those of librubberband follows a simple pattern: ``"Option" + Name + Value``.)
|
||||
|
||||
This filter supports the following ``af-command`` commands:
|
||||
|
||||
``set-pitch``
|
||||
Set the ``<pitch-scale>`` argument dynamically. This can be used to
|
||||
change the playback pitch at runtime. Note that speed is controlled
|
||||
using the standard ``speed`` property, not ``af-command``.
|
||||
|
||||
``lavfi=graph``
|
||||
Filter audio using FFmpeg's libavfilter.
|
||||
|
||||
|
||||
+72
-120
@@ -4,17 +4,11 @@ AUDIO OUTPUT DRIVERS
|
||||
Audio output drivers are interfaces to different audio output facilities. The
|
||||
syntax is:
|
||||
|
||||
``--ao=<driver1[:suboption1[=value]:...],driver2,...[,]>``
|
||||
``--ao=<driver1,driver2,...[,]>``
|
||||
Specify a priority list of audio output drivers to be used.
|
||||
|
||||
If the list has a trailing ',', mpv will fall back on drivers not contained
|
||||
in the list. Suboptions are optional and can mostly be omitted.
|
||||
|
||||
You can also set defaults for each driver. The defaults are applied before the
|
||||
normal driver parameters.
|
||||
|
||||
``--ao-defaults=<driver1[:parameter1:parameter2:...],driver2,...>``
|
||||
Set defaults for each driver.
|
||||
in the list.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -23,87 +17,19 @@ normal driver parameters.
|
||||
where PulseAudio is used. On BSD systems, ``--ao=oss`` or ``--ao=sndio``
|
||||
may work (the latter being experimental).
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--ao=alsa,oss,`` Try the ALSA driver, then the OSS driver, then others.
|
||||
- ``--ao=alsa:resample=yes:device=[plughw:0,3]`` Lets ALSA resample and
|
||||
sets the device-name as first card, fourth device.
|
||||
|
||||
Available audio output drivers are:
|
||||
|
||||
``alsa`` (Linux only)
|
||||
ALSA audio output driver
|
||||
|
||||
``device=<device>``
|
||||
Sets the device name. For ac3 output via S/PDIF, use an "iec958" or
|
||||
"spdif" device, unless you really know how to set it correctly.
|
||||
``resample=yes``
|
||||
Enable ALSA resampling plugin. (This is disabled by default, because
|
||||
some drivers report incorrect audio delay in some cases.)
|
||||
``mixer-device=<device>``
|
||||
Set the mixer device used with ``--no-softvol`` (default: ``default``).
|
||||
``mixer-name=<name>``
|
||||
Set the name of the mixer element (default: ``Master``). This is for
|
||||
example ``PCM`` or ``Master``.
|
||||
``mixer-index=<number>``
|
||||
Set the index of the mixer channel (default: 0). Consider the output of
|
||||
"``amixer scontrols``", then the index is the number that follows the
|
||||
name of the element.
|
||||
``non-interleaved``
|
||||
Allow output of non-interleaved formats (if the audio decoder uses
|
||||
this format). Currently disabled by default, because some popular
|
||||
ALSA plugins are utterly broken with non-interleaved formats.
|
||||
``ignore-chmap``
|
||||
Don't read or set the channel map of the ALSA device - only request the
|
||||
required number of channels, and then pass the audio as-is to it. This
|
||||
option most likely should not be used. It can be useful for debugging,
|
||||
or for static setups with a specially engineered ALSA configuration (in
|
||||
this case you should always force the same layout with ``--audio-channels``,
|
||||
or it will work only for files which use the layout implicit to your
|
||||
ALSA device).
|
||||
|
||||
.. note::
|
||||
|
||||
MPlayer and mplayer2 required you to replace any ',' with '.' and
|
||||
any ':' with '=' in the ALSA device name. mpv does not do this anymore.
|
||||
Instead, quote the device name:
|
||||
|
||||
``--ao=alsa:device=[plug:surround50]``
|
||||
|
||||
Note that the ``[`` and ``]`` simply quote the device name. With some
|
||||
shells (like zsh), you have to quote the option string to prevent the
|
||||
shell from interpreting the brackets instead of passing them to mpv.
|
||||
|
||||
Actually, you should use the ``--audio-device`` option, instead of
|
||||
setting the device directly.
|
||||
See `ALSA audio output options`_ for options specific to this AO.
|
||||
|
||||
.. warning::
|
||||
|
||||
Handling of multichannel/surround audio changed in mpv 0.8.0 from the
|
||||
behavior in MPlayer/mplayer2 and older versions of mpv.
|
||||
|
||||
The old behavior is that the player always downmixed to stereo by
|
||||
default. The ``--audio-channels`` (or ``--channels`` before that) option
|
||||
had to be set to get multichannel audio. Then playing stereo would
|
||||
use the ``default`` device (which typically allows multiple programs
|
||||
to play audio at the same time via dmix), while playing anything with
|
||||
more channels would open one of the hardware devices, e.g. via the
|
||||
``surround51`` alias (typically with exclusive access). Whether the
|
||||
player would use exclusive access or not would depend on the file
|
||||
being played.
|
||||
|
||||
The new behavior since mpv 0.8.0 always enables multichannel audio,
|
||||
i.e. ``--audio-channels=auto`` is the default. However, since ALSA
|
||||
provides no good way to play multichannel audio in a non-exclusive
|
||||
way (without blocking other applications from using audio), the player
|
||||
is restricted to the capabilities of the ``default`` device by default,
|
||||
which means it supports only stereo and mono (at least with current
|
||||
typical ALSA configurations). But if a hardware device is selected,
|
||||
then multichannel audio will typically work.
|
||||
|
||||
The short story is: if you want multichannel audio with ALSA, use
|
||||
``--audio-device`` to select the device (use ``--audio-device=help``
|
||||
to get a list of all devices and their mpv name).
|
||||
To get multichannel/surround audio, use ``--audio-channels=auto``. The
|
||||
default for this option is ``auto-safe``, which makes this audio otuput
|
||||
explicitly reject multichannel output, as there is no way to detect
|
||||
whether a certain channel layout is actually supported.
|
||||
|
||||
You can also try `using the upmix plugin <http://git.io/vfuAy>`_.
|
||||
This setup enables multichannel audio on the ``default`` device
|
||||
@@ -113,31 +39,36 @@ Available audio output drivers are:
|
||||
``oss``
|
||||
OSS audio output driver
|
||||
|
||||
``<dsp-device>``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--oss-device``
|
||||
Sets the audio output device (default: ``/dev/dsp``).
|
||||
``<mixer-device>``
|
||||
Deprecated, use ``--audio-device``.
|
||||
``--oss-mixer-device``
|
||||
Sets the audio mixer device (default: ``/dev/mixer``).
|
||||
``<mixer-channel>``
|
||||
``--oss-mixer-channel``
|
||||
Sets the audio mixer channel (default: ``pcm``). Other valid values
|
||||
include **vol, pcm, line**. For a complete list of options look for
|
||||
``SOUND_DEVICE_NAMES`` in ``/usr/include/linux/soundcard.h``.
|
||||
|
||||
``jack``
|
||||
JACK (Jack Audio Connection Kit) audio output driver
|
||||
JACK (Jack Audio Connection Kit) audio output driver.
|
||||
|
||||
``port=<name>``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--jack-port=<name>``
|
||||
Connects to the ports with the given name (default: physical ports).
|
||||
``name=<client>``
|
||||
``--jack-name=<client>``
|
||||
Client name that is passed to JACK (default: ``mpv``). Useful
|
||||
if you want to have certain connections established automatically.
|
||||
``(no-)autostart``
|
||||
``--jack-autostart=<yes|no>``
|
||||
Automatically start jackd if necessary (default: disabled). Note that
|
||||
this tends to be unreliable and will flood stdout with server messages.
|
||||
``(no-)connect``
|
||||
``--jack-connect=<yes|no>``
|
||||
Automatically create connections to output ports (default: enabled).
|
||||
When enabled, the maximum number of output channels will be limited to
|
||||
the number of available output ports.
|
||||
``std-channel-layout=waveext|any``
|
||||
``--jack-std-channel-layout=<waveext|any>``
|
||||
Select the standard channel layout (default: waveext). JACK itself has no
|
||||
notion of channel layouts (i.e. assigning which speaker a given
|
||||
channel is supposed to map to) - it just takes whatever the application
|
||||
@@ -157,7 +88,9 @@ Available audio output drivers are:
|
||||
Automatically redirects to ``coreaudio_exclusive`` when playing compressed
|
||||
formats.
|
||||
|
||||
``change-physical-format=<yes|no>``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--coreaudio-change-physical-format=<yes|no>``
|
||||
Change the physical format to one similar to the requested audio format
|
||||
(default: no). This has the advantage that multichannel audio output
|
||||
will actually work. The disadvantage is that it will change the
|
||||
@@ -165,11 +98,13 @@ Available audio output drivers are:
|
||||
setting in the ``Audio Devices`` dialog in the ``Audio MIDI Setup``
|
||||
utility. Note that this does not affect the selected speaker setup.
|
||||
|
||||
``exclusive``
|
||||
``--coreaudio-exclusive``
|
||||
Deprecated, use ``--audio-exclusive``.
|
||||
Use exclusive mode access. This merely redirects to
|
||||
``coreaudio_exclusive``, but should be preferred over using that AO
|
||||
directly.
|
||||
|
||||
|
||||
``coreaudio_exclusive`` (Mac OS X only)
|
||||
Native Mac OS X audio output driver using direct device access and
|
||||
exclusive mode (bypasses the sound server).
|
||||
@@ -183,18 +118,21 @@ Available audio output drivers are:
|
||||
``pulse``
|
||||
PulseAudio audio output driver
|
||||
|
||||
``[<host>][:<output sink>]``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--pulse-host=<host>``, ``--pulse-sink=<sink>``
|
||||
Specify the host and optionally output sink to use. An empty <host>
|
||||
string uses a local connection, "localhost" uses network transfer
|
||||
(most likely not what you want).
|
||||
Deprecated, use ``--audio-device``.
|
||||
|
||||
``buffer=<1-2000|native>``
|
||||
``--pulse-buffer=<1-2000|native>``
|
||||
Set the audio buffer size in milliseconds. A higher value buffers
|
||||
more data, and has a lower probability of buffer underruns. A smaller
|
||||
value makes the audio stream react faster, e.g. to playback speed
|
||||
changes. Default: 250.
|
||||
|
||||
``latency-hacks=<yes|no>``
|
||||
``--pulse-latency-hacks=<yes|no>``
|
||||
Enable hacks to workaround PulseAudio timing bugs (default: no). If
|
||||
enabled, mpv will do elaborate latency calculations on its own. If
|
||||
disabled, it will use PulseAudio automatically updated timing
|
||||
@@ -214,61 +152,67 @@ Available audio output drivers are:
|
||||
environments, such as systems where none of the other drivers
|
||||
are available.
|
||||
|
||||
``buflen=<length>``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--sdl-buflen=<length>``
|
||||
Sets the audio buffer length in seconds. Is used only as a hint by the
|
||||
sound system. Playing a file with ``-v`` will show the requested and
|
||||
obtained exact buffer size. A value of 0 selects the sound system
|
||||
default.
|
||||
|
||||
``bufcnt=<count>``
|
||||
``--sdl-bufcnt=<count>``
|
||||
Sets the number of extra audio buffers in mpv. Usually needs not be
|
||||
changed.
|
||||
|
||||
``null``
|
||||
Produces no audio output but maintains video playback speed. Use
|
||||
``--ao=null:untimed`` for benchmarking.
|
||||
Produces no audio output but maintains video playback speed. You can use
|
||||
``--ao=null --ao-null-untimed`` for benchmarking.
|
||||
|
||||
``untimed``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--ao-null-untimed``
|
||||
Do not simulate timing of a perfect audio device. This means audio
|
||||
decoding will go as fast as possible, instead of timing it to the
|
||||
system clock.
|
||||
|
||||
``buffer``
|
||||
``--ao-null-buffer``
|
||||
Simulated buffer length in seconds.
|
||||
|
||||
``outburst``
|
||||
``--ao-null-outburst``
|
||||
Simulated chunk size in samples.
|
||||
|
||||
``speed``
|
||||
``--ao-null-speed``
|
||||
Simulated audio playback speed as a multiplier. Usually, a real audio
|
||||
device will not go exactly as fast as the system clock. It will deviate
|
||||
just a little, and this option helps to simulate this.
|
||||
|
||||
``latency``
|
||||
``--ao-null-latency``
|
||||
Simulated device latency. This is additional to EOF.
|
||||
|
||||
``broken-eof``
|
||||
``--ao-null-broken-eof``
|
||||
Simulate broken audio drivers, which always add the fixed device
|
||||
latency to the reported audio playback position.
|
||||
|
||||
``broken-delay``
|
||||
``--ao-null-broken-delay``
|
||||
Simulate broken audio drivers, which don't report latency correctly.
|
||||
|
||||
``channel-layouts``
|
||||
``--ao-null-channel-layouts``
|
||||
If not empty, this is a ``,`` separated list of channel layouts the
|
||||
AO allows. This can be used to test channel layout selection.
|
||||
|
||||
``pcm``
|
||||
Raw PCM/WAVE file writer audio output
|
||||
|
||||
``(no-)waveheader``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--ao-pcm-waveheader=<yes|no>``
|
||||
Include or do not include the WAVE header (default: included). When
|
||||
not included, raw PCM will be generated.
|
||||
``file=<filename>``
|
||||
``--ao-pcm-file=<filename>``
|
||||
Write the sound to ``<filename>`` instead of the default
|
||||
``audiodump.wav``. If ``no-waveheader`` is specified, the default is
|
||||
``audiodump.pcm``.
|
||||
``(no-)append``
|
||||
``--ao-pcm-append=<yes|no>``
|
||||
Append to the file, instead of overwriting it. Always use this with the
|
||||
``no-waveheader`` option - with ``waveheader`` it's broken, because
|
||||
it will write a WAVE header every time the file is opened.
|
||||
@@ -280,14 +224,19 @@ Available audio output drivers are:
|
||||
confused with RoarAudio, which is something completely
|
||||
different.
|
||||
|
||||
``host=<name/path>``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--rsound-host=<name/path>``
|
||||
Set the address of the server (default: localhost). Can be either a
|
||||
network hostname for TCP connections or a Unix domain socket path
|
||||
starting with '/'.
|
||||
``port=<number>``
|
||||
``--rsound-port=<number>``
|
||||
Set the TCP port used for connecting to the server (default: 12345).
|
||||
Not used if connecting to a Unix domain socket.
|
||||
|
||||
These options are deprecated. If anyone cares enough, their functionality
|
||||
can be added back using ``--audio-device``.
|
||||
|
||||
``sndio``
|
||||
Audio output to the OpenBSD sndio sound system
|
||||
|
||||
@@ -296,16 +245,24 @@ Available audio output drivers are:
|
||||
(Note: only supports mono, stereo, 4.0, 5.1 and 7.1 channel
|
||||
layouts.)
|
||||
|
||||
``device=<device>``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--ao-sndio-device=<device>``
|
||||
sndio device to use (default: ``$AUDIODEVICE``, resp. ``snd0``).
|
||||
Deprecated, use ``--audio-device``.
|
||||
|
||||
``wasapi``
|
||||
Audio output to the Windows Audio Session API.
|
||||
|
||||
``exclusive``
|
||||
The following global options are supported by this audio output:
|
||||
|
||||
``--ao-wasapi-exclusive``
|
||||
Deprecated, use ``--audio-exclusive``.
|
||||
Requests exclusive, direct hardware access. By definition prevents
|
||||
sound playback of any other program until mpv exits.
|
||||
``device=<id>``
|
||||
``--ao-wasapi-device=<id>``
|
||||
Deprecated, use ``--audio-device``.
|
||||
|
||||
Uses the requested endpoint instead of the system's default audio
|
||||
endpoint. Both an ordinal number (0,1,2,...) and the GUID
|
||||
String are valid; the GUID string is guaranteed to not change
|
||||
@@ -313,8 +270,3 @@ Available audio output drivers are:
|
||||
|
||||
Also supports searching active devices by human-readable name. If more
|
||||
than one device matches the name, refuses loading it.
|
||||
|
||||
This option is mostly deprecated in favour of the more general
|
||||
``--audio-device`` option. That said, ``--audio-device=help`` will give
|
||||
a list of valid device GUIDs (prefixed with ``wasapi/``), as well as
|
||||
their human readable names, which should work here.
|
||||
@@ -8,6 +8,11 @@ There is no real changelog, but you can look at the following things:
|
||||
|
||||
https://github.com/mpv-player/mpv/releases
|
||||
* The git log, which is the "real" changelog
|
||||
* The files ``client-api-changes.rst`` and ``interface-changes.rst`` in the
|
||||
``DOCS`` sub directoryon the git repository, which document API and user
|
||||
interface changes (the latter usually documents breaking changes only, rather
|
||||
than additions).
|
||||
* The file ``mplayer-changes.rst`` in the ``DOCS`` sub directory on the git
|
||||
repository, which used to be in place of this section. It documents some
|
||||
changes that happened since mplayer2 forked off MPlayer.
|
||||
changes that happened since mplayer2 forked off MPlayer. (Not updated
|
||||
anymore.)
|
||||
+140
-193
@@ -95,7 +95,7 @@ List of Input Commands
|
||||
relative (default)
|
||||
Seek relative to current position (a negative value seeks backwards).
|
||||
absolute
|
||||
Seek to a given time.
|
||||
Seek to a given time (a negative value starts from the end of the file).
|
||||
absolute-percent
|
||||
Seek to a given percent position.
|
||||
relative-percent
|
||||
@@ -526,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
|
||||
@@ -544,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.)
|
||||
|
||||
@@ -665,13 +665,6 @@ Input Commands that are Possibly Subject to Change
|
||||
(the ``ab-loop-a`` property); the second the ``B`` point, and the third
|
||||
will clear both points.
|
||||
|
||||
``vo-cmdline "<args>"``
|
||||
Reset the sub-option of the current VO. Currently works with ``opengl``
|
||||
(including ``opengl-hq``). The argument is the sub-option string usually
|
||||
passed to the VO on the command line. Not all sub-options can be set, but
|
||||
those which can will be reset even if they don't appear in the argument.
|
||||
This command might be changed or removed in the future.
|
||||
|
||||
``drop-buffers``
|
||||
Drop audio/video/demuxer buffers, and restart from fresh. Might help with
|
||||
unseekable streams that are going out of sync.
|
||||
@@ -698,6 +691,17 @@ Input Commands that are Possibly Subject to Change
|
||||
``af-command "<label>" "<cmd>" "<args>"``
|
||||
Same as ``vf-command``, but for audio filters.
|
||||
|
||||
``apply-profile "<name>"``
|
||||
Apply the contents of a named profile. This is like using ``profile=name``
|
||||
in a config file, except you can map it to a key binding to change it at
|
||||
runtime.
|
||||
|
||||
There is no such thing as "unapplying" a profile - applying a profile
|
||||
merely sets all option values listed within the profile.
|
||||
|
||||
``load-script "<path>"``
|
||||
Load a script, similar to the ``--script`` option.
|
||||
|
||||
Undocumented commands: ``tv-last-channel`` (TV/DVB only),
|
||||
``ao-reload`` (experimental/internal).
|
||||
|
||||
@@ -838,20 +842,12 @@ an option at runtime.
|
||||
Property list
|
||||
-------------
|
||||
|
||||
``osd-level`` (RW)
|
||||
See ``--osd-level``.
|
||||
.. note::
|
||||
|
||||
``osd-scale`` (RW)
|
||||
OSD font size multiplier, see ``--osd-scale``.
|
||||
|
||||
``loop`` (RW)
|
||||
See ``--loop``.
|
||||
|
||||
``loop-file`` (RW)
|
||||
See ``--loop-file`` (uses ``yes``/``no``).
|
||||
|
||||
``speed`` (RW)
|
||||
See ``--speed``.
|
||||
Most options can be set as runtime via properties as well. Just remove the
|
||||
leading ``--`` from the option name. These are not documented. Only
|
||||
properties which do not exist as option with the same name, or which have
|
||||
very different behavior from the options are documented below.
|
||||
|
||||
``audio-speed-correction``, ``video-speed-correction``
|
||||
Factor multiplied with ``speed`` at which the player attempts to play the
|
||||
@@ -911,9 +907,11 @@ Property list
|
||||
list of format names, e.g. mp4 is ``mov,mp4,m4a,3gp,3g2,mj2`` (the list
|
||||
may grow in the future for any format).
|
||||
|
||||
``demuxer``
|
||||
``current-demuxer``
|
||||
Name of the current demuxer. (This is useless.)
|
||||
|
||||
(Renamed from ``demuxer``.)
|
||||
|
||||
``stream-path``
|
||||
Filename (full path) of the stream layer filename. (This is probably
|
||||
useless. It looks like this can be different from ``path`` only when
|
||||
@@ -989,6 +987,12 @@ Property list
|
||||
Remaining length of the file in seconds. Note that the file duration is not
|
||||
always exactly known, so this is an estimate.
|
||||
|
||||
``audio-pts`` (R)
|
||||
Current audio playback position in current file in seconds. Unlike time-pos,
|
||||
this updates more often than once per frame. For audio-only files, it is
|
||||
mostly equivalent to time-pos, while for video-only files this property is
|
||||
not available.
|
||||
|
||||
``playtime-remaining``
|
||||
``time-remaining`` scaled by the current ``speed``.
|
||||
|
||||
@@ -1078,10 +1082,6 @@ Property list
|
||||
"title" MPV_FORMAT_STRING
|
||||
"default" MPV_FORMAT_FLAG
|
||||
|
||||
``ab-loop-a``, ``ab-loop-b`` (RW)
|
||||
Set/get A-B loop points. See corresponding options and ``ab-loop`` command
|
||||
for details.
|
||||
|
||||
``angle`` (RW)
|
||||
Current DVD angle.
|
||||
|
||||
@@ -1154,13 +1154,12 @@ Property list
|
||||
``af-metadata/<filter-label>``
|
||||
Equivalent to ``vf-metadata/<filter-label>``, but for audio filters.
|
||||
|
||||
``pause`` (RW)
|
||||
Pause status. This is usually ``yes`` or ``no``. See ``--pause``.
|
||||
|
||||
``idle``
|
||||
``idle-active``
|
||||
Return ``yes`` if no file is loaded, but the player is staying around
|
||||
because of the ``--idle`` option.
|
||||
|
||||
(Renamed from ``idle``.)
|
||||
|
||||
``core-idle``
|
||||
Return ``yes`` if the playback core is paused, otherwise ``no``. This can
|
||||
be different ``pause`` in special situations, such as when the player
|
||||
@@ -1236,25 +1235,12 @@ Property list
|
||||
is loaded, or when switching ordered chapter segments. This is because
|
||||
the same underlying code is used for seeking and resyncing.)
|
||||
|
||||
``hr-seek`` (RW)
|
||||
See ``--hr-seek``.
|
||||
|
||||
``mixer-active``
|
||||
Return ``yes`` if the audio mixer is active, ``no`` otherwise.
|
||||
|
||||
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).
|
||||
|
||||
``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``).
|
||||
|
||||
``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
|
||||
@@ -1266,9 +1252,6 @@ Property list
|
||||
Similar to ``ao-volume``, but controls the mute state. May be unimplemented
|
||||
even if ``ao-volume`` works.
|
||||
|
||||
``audio-delay`` (RW)
|
||||
See ``--audio-delay``.
|
||||
|
||||
``audio-codec``
|
||||
Audio codec selected for decoding.
|
||||
|
||||
@@ -1316,28 +1299,6 @@ Property list
|
||||
Same as ``audio-params``, but the format of the data written to the audio
|
||||
API.
|
||||
|
||||
``aid`` (RW)
|
||||
Current audio track (similar to ``--aid``).
|
||||
|
||||
``audio`` (RW)
|
||||
Alias for ``aid``.
|
||||
|
||||
``balance`` (RW)
|
||||
Audio channel balance. (The implementation of this feature is rather odd.
|
||||
It doesn't change the volumes of each channel, but instead sets up a pan
|
||||
matrix to mix the left and right channels.)
|
||||
|
||||
Deprecated.
|
||||
|
||||
``fullscreen`` (RW)
|
||||
See ``--fullscreen``.
|
||||
|
||||
``deinterlace`` (RW)
|
||||
See ``--deinterlace``.
|
||||
|
||||
``field-dominance`` (RW)
|
||||
See ``--field-dominance``
|
||||
|
||||
``colormatrix`` (R)
|
||||
Redirects to ``video-params/colormatrix``. This parameter (as well as
|
||||
similar ones) can be overridden with the ``format`` video filter.
|
||||
@@ -1345,42 +1306,9 @@ Property list
|
||||
``colormatrix-input-range`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``video-output-levels`` (RW)
|
||||
See ``--video-output-levels``,
|
||||
|
||||
``colormatrix-primaries`` (R)
|
||||
See ``colormatrix``.
|
||||
|
||||
``taskbar-progress`` (RW)
|
||||
See ``--taskbar-progress``.
|
||||
|
||||
``ontop`` (RW)
|
||||
See ``--ontop``.
|
||||
|
||||
``border`` (RW)
|
||||
See ``--border``.
|
||||
|
||||
``on-all-workspaces`` (RW)
|
||||
See ``--on-all-workspaces``. Unsetting may not work on all WMs.
|
||||
|
||||
``framedrop`` (RW)
|
||||
See ``--framedrop``.
|
||||
|
||||
``gamma`` (RW)
|
||||
See ``--gamma``.
|
||||
|
||||
``brightness`` (RW)
|
||||
See ``--brightness``.
|
||||
|
||||
``contrast`` (RW)
|
||||
See ``--contrast``.
|
||||
|
||||
``saturation`` (RW)
|
||||
See ``--saturation``.
|
||||
|
||||
``hue`` (RW)
|
||||
See ``--hue``.
|
||||
|
||||
``hwdec`` (RW)
|
||||
Reflects the ``--hwdec`` option.
|
||||
|
||||
@@ -1413,27 +1341,6 @@ Property list
|
||||
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``
|
||||
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
|
||||
advance, but only when the decoder attempted to create the hw decoder
|
||||
successfully. Also, hw decoders with ``-copy`` suffix will return ``no``
|
||||
while no video is being decoded. All this reflects how detecting hw decoders
|
||||
are detected and used internally in mpv.
|
||||
|
||||
``panscan`` (RW)
|
||||
See ``--panscan``.
|
||||
|
||||
``video-format``
|
||||
Video format as string.
|
||||
|
||||
@@ -1534,6 +1441,9 @@ Property list
|
||||
These have the same values as ``video-out-params/dw`` and
|
||||
``video-out-params/dh``.
|
||||
|
||||
``video-dec-params``
|
||||
Exactly like ``video-params``, but no overrides applied.
|
||||
|
||||
``video-out-params``
|
||||
Same as ``video-params``, but after video filters have been applied. If
|
||||
there are no video filters in use, this will contain the same values as
|
||||
@@ -1556,10 +1466,12 @@ Property list
|
||||
``video-frame-info/tff``
|
||||
``video-frame-info/repeat``
|
||||
|
||||
``fps``
|
||||
``container-fps``
|
||||
Container FPS. This can easily contain bogus values. For videos that use
|
||||
modern container formats or video codecs, this will often be incorrect.
|
||||
|
||||
(Renamed from ``fps``.)
|
||||
|
||||
``estimated-vf-fps``
|
||||
Estimated/measured FPS of the video filter chain output. (If no filters
|
||||
are used, this corresponds to decoder output.) This uses the average of
|
||||
@@ -1580,7 +1492,10 @@ Property list
|
||||
|
||||
``display-names``
|
||||
Names of the displays that the mpv window covers. On X11, these
|
||||
are the xrandr names (LVDS1, HDMI1, DP1, VGA1, etc.).
|
||||
are the xrandr names (LVDS1, HDMI1, DP1, VGA1, etc.). On Windows, these
|
||||
are the GDI names (\\.\DISPLAY1, \\.\DISPLAY2, etc.) and the first display
|
||||
in the list will be the one that Windows considers associated with the
|
||||
window (as determined by the MonitorFromWindow API.)
|
||||
|
||||
``display-fps`` (RW)
|
||||
The refresh rate of the current display. Currently, this is the lowest FPS
|
||||
@@ -1600,6 +1515,9 @@ Property list
|
||||
``video-aspect`` (RW)
|
||||
Video aspect, see ``--video-aspect``.
|
||||
|
||||
If video is active, this reports the effective aspect value, instead of
|
||||
the value of the ``--video-aspect`` option.
|
||||
|
||||
``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
|
||||
@@ -1608,71 +1526,24 @@ Property list
|
||||
``osd-par``
|
||||
Last known OSD display pixel aspect (can be 0).
|
||||
|
||||
``vid`` (RW)
|
||||
Current video track (similar to ``--vid``).
|
||||
|
||||
``video`` (RW)
|
||||
Alias for ``vid``.
|
||||
|
||||
``video-align-x``, ``video-align-y`` (RW)
|
||||
See ``--video-align-x`` and ``--video-align-y``.
|
||||
|
||||
``video-pan-x``, ``video-pan-y`` (RW)
|
||||
See ``--video-pan-x`` and ``--video-pan-y``.
|
||||
|
||||
``video-zoom`` (RW)
|
||||
See ``--video-zoom``.
|
||||
|
||||
``video-unscaled`` (W)
|
||||
See ``--video-unscaled``.
|
||||
|
||||
``program`` (W)
|
||||
Switch TS program (write-only).
|
||||
|
||||
``dvb-channel`` (W)
|
||||
Pair of integers: card,channel of current DVB stream.
|
||||
Can be switched to switch to another channel on the same card.
|
||||
Can be switched to switch to another channel on the same card.
|
||||
|
||||
``dvb-channel-name`` (RW)
|
||||
Name of current DVB program.
|
||||
On write, a channel-switch to the named channel on the same
|
||||
card is performed. Can also be used for channel switching.
|
||||
card is performed. Can also be used for channel switching.
|
||||
|
||||
``sid`` (RW)
|
||||
Current subtitle track (similar to ``--sid``).
|
||||
``sub-text``
|
||||
Return the current subtitle text. Formatting is stripped. If a subtitle
|
||||
is selected, but no text is currently visible, or the subtitle is not
|
||||
text-based (i.e. DVD/BD subtitles), an empty string is returned.
|
||||
|
||||
``secondary-sid`` (RW)
|
||||
Secondary subtitle track (see ``--secondary-sid``).
|
||||
|
||||
``sub`` (RW)
|
||||
Alias for ``sid``.
|
||||
|
||||
``sub-delay`` (RW)
|
||||
See ``--sub-delay``.
|
||||
|
||||
``sub-pos`` (RW)
|
||||
See ``--sub-pos``.
|
||||
|
||||
``sub-visibility`` (RW)
|
||||
See ``--sub-visibility``.
|
||||
|
||||
``sub-forced-only`` (RW)
|
||||
See ``--sub-forced-only``.
|
||||
|
||||
``sub-scale`` (RW)
|
||||
Subtitle font size multiplier.
|
||||
|
||||
``ass-force-margins`` (RW)
|
||||
See ``--ass-force-margins``.
|
||||
|
||||
``sub-use-margins`` (RW)
|
||||
See ``--sub-use-margins``.
|
||||
|
||||
``ass-vsfilter-aspect-compat`` (RW)
|
||||
See ``--ass-vsfilter-aspect-compat``.
|
||||
|
||||
``ass-style-override`` (RW)
|
||||
See ``--ass-style-override``.
|
||||
This property is experimental and might be removed in the future.
|
||||
|
||||
``stream-capture`` (RW)
|
||||
A filename, see ``--stream-capture``. Setting this will start capture using
|
||||
@@ -1884,11 +1755,8 @@ Property list
|
||||
"title" MPV_FORMAT_STRING
|
||||
"time" MPV_FORMAT_DOUBLE
|
||||
|
||||
``af`` (RW)
|
||||
See ``--af`` and the ``af`` command.
|
||||
|
||||
``vf`` (RW)
|
||||
See ``--vf`` and the ``vf`` command.
|
||||
``af``, ``vf`` (RW)
|
||||
See ``--vf``/``--af`` and the ``vf``/``af`` command.
|
||||
|
||||
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
|
||||
@@ -1906,12 +1774,6 @@ Property list
|
||||
|
||||
It's also possible to write the property using this format.
|
||||
|
||||
``video-rotate`` (RW)
|
||||
See ``--video-rotate`` option.
|
||||
|
||||
``video-stereo-mode`` (RW)
|
||||
See ``--video-stereo-mode`` option.
|
||||
|
||||
``seekable``
|
||||
Return whether it's generally possible to seek in the current file.
|
||||
|
||||
@@ -2050,8 +1912,9 @@ Property list
|
||||
|
||||
The ``name`` is what is to be passed to the ``--audio-device`` option (and
|
||||
often a rather cryptic audio API-specific ID), while ``description`` is
|
||||
human readable free form text. The description is an empty string if none
|
||||
was received.
|
||||
human readable free form text. The description is set to the device name
|
||||
(minus mpv-specific ``<driver>/`` prefix) if no description is available
|
||||
or the description would have been an empty string.
|
||||
|
||||
The special entry with the name set to ``auto`` selects the default audio
|
||||
output driver and the default device.
|
||||
@@ -2151,6 +2014,10 @@ Property list
|
||||
require reloading the file for changes to take effect. If there is an
|
||||
equivalent property, prefer setting the property instead.
|
||||
|
||||
There shouldn't be any reason to access ``options/<name>`` instead of
|
||||
``<name>``, except in situations in which the properties have different
|
||||
behavior or conflicting semantics.
|
||||
|
||||
``file-local-options/<name>``
|
||||
Similar to ``options/<name>``, but when setting an option through this
|
||||
property, the option is reset to its old value once the current file has
|
||||
@@ -2204,6 +2071,86 @@ Property list
|
||||
``property-list``
|
||||
Return the list of top-level properties.
|
||||
|
||||
``profile-list``
|
||||
Return the list of profiles and their contents. This is highly
|
||||
implementation-specific, and may change any time. Currently, it returns
|
||||
an array of options for each profile. Each option has a name and a value,
|
||||
with the value currently always being a string. Note that the options array
|
||||
is not a map, as order matters and duplicate entries are possible. Recursive
|
||||
profiles are not expanded, and show up as special ``profile`` options.
|
||||
|
||||
Inconsistencies between options and properties
|
||||
----------------------------------------------
|
||||
|
||||
You can access (almost) all options as properties, though there are some
|
||||
caveats with some properties (due to historical reasons):
|
||||
|
||||
``vid``, ``aid``, ``sid``
|
||||
While playback is active, you can set existing tracks only. (The option
|
||||
allows setting any track ID, and which tracks to enable is chosen at
|
||||
loading time.)
|
||||
|
||||
Option changes at runtime are affected by this as well.
|
||||
|
||||
``deinterlace``
|
||||
While video is active, this behaves differently from the option. It will
|
||||
never return the ``auto`` value (but the state as observed by the video
|
||||
chain). If you set ``auto``, the property will set this as the option value,
|
||||
and will return the actual video chain state as observed instead of auto.
|
||||
|
||||
``video-aspect``
|
||||
While video is active, always returns the effective aspect ratio. Setting
|
||||
a special value (like ``no``, values ``<= 0``) will make the property
|
||||
set this as option, and return whatever actual aspect was derived from the
|
||||
option setting.
|
||||
|
||||
``brightness`` (and other color options)
|
||||
If ``--vo=xv`` is used, these properties may return the adapter's current
|
||||
values instead of the option values.
|
||||
|
||||
``display-fps``
|
||||
If a VO is created, this will return either the actual display FPS, or
|
||||
an invalid value, instead of the option value.
|
||||
|
||||
``vf``, ``af``
|
||||
If you set the properties during playback, and the filter chain fails to
|
||||
reinitialize, the new value will be rejected. Setting the option or
|
||||
setting the property outside of playback will always succeed/fail in the
|
||||
same way. Also, there are no ``vf-add`` etc. properties, but you can use
|
||||
the ``vf``/``af`` group of commands to achieve the same.
|
||||
|
||||
Option changes at runtime are affected by this as well.
|
||||
|
||||
``edition``
|
||||
While a file is loaded, the property will always return the effective
|
||||
edition, and setting the ``auto`` value will show somewhat strange behavior
|
||||
(the property eventually switching to whatever is the default edition).
|
||||
|
||||
``playlist``
|
||||
The property is read-only and returns the current internal playlist. The
|
||||
option is for loading playlist during command line parsing. For client API
|
||||
uses, you should use the ``loadlist`` command instead.
|
||||
|
||||
``window-scale``
|
||||
Might verify the set value when setting while a window is created.
|
||||
|
||||
``audio-file``, ``sub-file``, ``external-file``
|
||||
These options/properties are actually lists of filenames. To make the
|
||||
command-line interface easier, each ``--audio-file=...`` option appends
|
||||
the full string to the internal list. However, when used as properties,
|
||||
every time you set the property as a string the internal list will be
|
||||
replaced with a single entry containing the string you set. ``,`` or other
|
||||
separators are never used. You have to use ``MPV_FORMAT_NODE_ARRAY`` (or
|
||||
corresponding API, e.g. ``mp.set_property_native()`` with a table in Lua)
|
||||
to set multiple entries.
|
||||
|
||||
Strictly speaking, option access via API (e.g. ``mpv_set_option_string()``)
|
||||
has the same problem, and it's only a difference between CLI/API.
|
||||
|
||||
``playlist-pos``, ``chapter``
|
||||
These properties behave different from the deprecated options with the same
|
||||
names.
|
||||
|
||||
Property Expansion
|
||||
------------------
|
||||
|
||||
|
||||
@@ -241,16 +241,6 @@ extra commands can also be used as part of the protocol:
|
||||
By default, most events are enabled, and there is not much use for this
|
||||
command.
|
||||
|
||||
``suspend``
|
||||
Suspend the mpv main loop. There is a long-winded explanation of this in
|
||||
the C API function ``mpv_suspend()``. In short, this prevents the player
|
||||
from displaying the next video frame, so that you don't get blocked when
|
||||
trying to access the player.
|
||||
|
||||
``resume``
|
||||
Undo one ``suspend`` call. ``suspend`` increments an internal counter, and
|
||||
``resume`` decrements it. When 0 is reached, the player is actually resumed.
|
||||
|
||||
``get_version``
|
||||
Returns the client API version the C API of the remote mpv instance
|
||||
provides.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
EMBEDDING INTO OTHER PROGRAMS (LIBMPV)
|
||||
======================================
|
||||
|
||||
mpv can be embedded into other programs as video/audio playback backend. The
|
||||
recommended way to do so is using libmpv. See ``libmpv/client.h`` in the mpv
|
||||
source code repository. This provides a C API. Bindings for other languages
|
||||
might be available (see wiki).
|
||||
|
||||
Since libmpv merely allows access to underlying mechanisms that can control
|
||||
mpv, further documentation is spread over a few places:
|
||||
|
||||
- https://github.com/mpv-player/mpv/blob/master/libmpv/client.h
|
||||
- http://mpv.io/manual/master/#options
|
||||
- http://mpv.io/manual/master/#list-of-input-commands
|
||||
- http://mpv.io/manual/master/#properties
|
||||
- https://github.com/mpv-player/mpv-examples/tree/master/libmpv
|
||||
+25
-15
@@ -43,9 +43,8 @@ timers added with ``mp.add_timeout`` or similar.
|
||||
|
||||
When the player quits, all scripts will be asked to terminate. This happens via
|
||||
a ``shutdown`` event, which by default will make the event loop return. If your
|
||||
script got into an endless loop, mpv will probably behave fine during playback
|
||||
(unless the player is suspended, see ``mp.suspend``), but it won't terminate
|
||||
when quitting, because it's waiting on your script.
|
||||
script got into an endless loop, mpv will probably behave fine during playback,
|
||||
but it won't terminate when quitting, because it's waiting on your script.
|
||||
|
||||
Internally, the C code will call the Lua function ``mp_event_loop`` after
|
||||
loading a Lua script. This function is normally defined by the default prelude
|
||||
@@ -412,21 +411,16 @@ These also live in the ``mp`` module, but are documented separately as they
|
||||
are useful only in special situations.
|
||||
|
||||
``mp.suspend()``
|
||||
Suspend the mpv main loop. There is a long-winded explanation of this in
|
||||
the C API function ``mpv_suspend()``. In short, this prevents the player
|
||||
from displaying the next video frame, so that you don't get blocked when
|
||||
trying to access the player.
|
||||
|
||||
Before mpv 0.17.0, this was automatically called by the event handler.
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``mp.resume()``
|
||||
Undo one ``mp.suspend()`` call. ``mp.suspend()`` increments an internal
|
||||
counter, and ``mp.resume()`` decrements it. When 0 is reached, the player
|
||||
is actually resumed.
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``mp.resume_all()``
|
||||
This resets the internal suspend counter and resumes the player. (It's
|
||||
like calling ``mp.resume()`` until the player is actually resumed.)
|
||||
This function has been deprecated in mpv 0.21.0 and does nothing starting
|
||||
with mpv 0.23.0 (no replacement).
|
||||
|
||||
``mp.get_wakeup_pipe()``
|
||||
Calls ``mpv_get_wakeup_pipe()`` and returns the read end of the wakeup
|
||||
@@ -450,6 +444,13 @@ are useful only in special situations.
|
||||
``mp.get_wakeup_pipe()`` if you're interested in properly working
|
||||
notification of new events and working timers.
|
||||
|
||||
``mp.register_idle(fn)``
|
||||
Register an event loop idle handler. Idle handlers are called before the
|
||||
script goes to sleep after handling all new events. This can be used for
|
||||
example to delay processing of property change events: if you're observing
|
||||
multiple properties at once, you might not want to act on each property
|
||||
change, but only when all change notifications have been received.
|
||||
|
||||
``mp.enable_messages(level)``
|
||||
Set the minimum log level of which mpv message output to receive. These
|
||||
messages are normally printed to the terminal. By calling this function,
|
||||
@@ -633,7 +634,16 @@ strictly part of the guaranteed API.
|
||||
Set to ``true`` if the process has been killed by mpv as a result
|
||||
of ``cancellable`` being set to ``true``.
|
||||
|
||||
In all cases, ``mp.resume_all()`` is implicitly called.
|
||||
``utils.subprocess_detached(t)``
|
||||
Runs an external process and detaches it from mpv's control.
|
||||
|
||||
The parameter ``t`` is a table. The function reads the following entries:
|
||||
|
||||
``args``
|
||||
Array of strings of the same semantics as the ``args`` used in the
|
||||
``subprocess`` function.
|
||||
|
||||
The function returns ``nil``.
|
||||
|
||||
``utils.parse_json(str [, trail])``
|
||||
Parses the given string argument as JSON, and returns it as a Lua table. On
|
||||
|
||||
+73
-43
@@ -9,6 +9,8 @@ a media player
|
||||
:Manual section: 1
|
||||
:Manual group: multimedia
|
||||
|
||||
.. contents:: Table of Contents
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
@@ -143,11 +145,11 @@ Ctrl + and Ctrl -
|
||||
u
|
||||
Switch between applying no style overrides to SSA/ASS subtitles, and
|
||||
overriding them almost completely with the normal subtitle style. See
|
||||
``--ass-style-override`` for more info.
|
||||
``--sub-ass-style-override`` for more info.
|
||||
|
||||
V
|
||||
Toggle subtitle VSFilter aspect compatibility mode. See
|
||||
``--ass-vsfilter-aspect-compat`` for more info.
|
||||
``--sub-ass-vsfilter-aspect-compat`` for more info.
|
||||
|
||||
r and t
|
||||
Move subtitles up/down.
|
||||
@@ -208,9 +210,6 @@ Alt+2 (and command+2 on OSX)
|
||||
command + f (OSX only)
|
||||
Toggle fullscreen (see also ``--fs``).
|
||||
|
||||
command + [ and command + ] (OSX only)
|
||||
Set video window alpha.
|
||||
|
||||
(The following keys are valid if you have a keyboard with multimedia keys.)
|
||||
|
||||
PAUSE
|
||||
@@ -244,6 +243,16 @@ button 5 and button 6
|
||||
USAGE
|
||||
=====
|
||||
|
||||
Command line arguments starting with ``-`` are interpreted as options,
|
||||
everything else as filenames or URLs. All options except *flag* options (or
|
||||
choice options which include ``yes``) require a parameter in the form
|
||||
``--option=value``.
|
||||
|
||||
One exception is the lone ``-`` (without anything else), which means media data
|
||||
will be read from stdin. Also, ``--`` (without anything else) will make the
|
||||
player interpret all following arguments as filenames, even if they start with
|
||||
``-``. (To play a file named ``-``, you need to use ``./-``.)
|
||||
|
||||
Every *flag* option has a *no-flag* counterpart, e.g. the opposite of the
|
||||
``--fs`` option is ``--no-fs``. ``--fs=yes`` is same as ``--fs``, ``--fs=no``
|
||||
is the same as ``--no-fs``.
|
||||
@@ -251,6 +260,24 @@ is the same as ``--no-fs``.
|
||||
If an option is marked as *(XXX only)*, it will only work in combination with
|
||||
the *XXX* option or if *XXX* is compiled in.
|
||||
|
||||
Legacy option syntax
|
||||
--------------------
|
||||
|
||||
The ``--option=value`` syntax is not strictly enforced, and the alternative
|
||||
legacy syntax ``-option value`` and ``--option value`` will also work. This is
|
||||
mostly for compatibility with MPlayer. Using these should be avoided. Their
|
||||
semantics can change any time in the future.
|
||||
|
||||
For example, the alternative syntax will consider an argument following the
|
||||
option a filename. ``mpv -fs no`` will attempt to play a file named ``no``,
|
||||
because ``--fs`` is a flag option that requires no parameter. If an option
|
||||
changes and its parameter becomes optional, then a command line using the
|
||||
alternative syntax will break.
|
||||
|
||||
Currently, the parser makes no difference whether an option starts with ``--``
|
||||
or a single ``-``. This might also change in the future, and ``--option value``
|
||||
might always interpret ``value`` as filename in order to reduce ambiguities.
|
||||
|
||||
Escaping spaces and other special characters
|
||||
--------------------------------------------
|
||||
|
||||
@@ -268,15 +295,16 @@ command line.
|
||||
The suboption parser can quote strings with ``"`` and ``[...]``.
|
||||
Additionally, there is a special form of quoting with ``%n%`` described below.
|
||||
|
||||
For example, the ``opengl`` VO can take multiple options:
|
||||
For example, assume the hypothetical ``foo`` filter can take multiple options:
|
||||
|
||||
``mpv test.mkv --vo=opengl:scale=lanczos:icc-profile=file.icc,xv``
|
||||
``mpv test.mkv --vf=foo:option1=value1:option2:option3=value3,bar``
|
||||
|
||||
This passes ``scale=lanczos`` and ``icc-profile=file.icc`` to ``opengl``,
|
||||
and also specifies ``xv`` as fallback VO. If the icc-profile path contains
|
||||
spaces or characters like ``,`` or ``:``, you need to quote them:
|
||||
This passes ``option1`` and ``option3`` to the ``foo`` filter, with ``option2``
|
||||
as flag (implicitly ``option2=yes``), and adds a ``bar`` filter after that. If
|
||||
an option contains spaces or characters like ``,`` or ``:``, you need to quote
|
||||
them:
|
||||
|
||||
``mpv '--vo=opengl:icc-profile="file with spaces.icc",xv'``
|
||||
``mpv '--vf=foo:option1="option value with spaces",bar'``
|
||||
|
||||
Shells may actually strip some quotes from the string passed to the commandline,
|
||||
so the example quotes the string twice, ensuring that mpv receives the ``"``
|
||||
@@ -297,11 +325,11 @@ It is started with ``%`` and has the following format::
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --ao=pcm:file=%10%C:test.wav test.avi``
|
||||
``mpv '--vf=foo:option1=%11%quoted text' test.avi``
|
||||
|
||||
Or in a script:
|
||||
|
||||
``mpv --ao=pcm:file=%`expr length "$NAME"`%"$NAME" test.avi``
|
||||
``mpv --vf=foo:option1=%`expr length "$NAME"`%"$NAME" test.avi``
|
||||
|
||||
Suboptions passed to the client API are also subject to escaping. Using
|
||||
``mpv_set_option_string()`` is exactly like passing ``--name=data`` to the
|
||||
@@ -309,8 +337,7 @@ command line (but without shell processing of the string). Some options
|
||||
support passing values in a more structured way instead of flat strings, and
|
||||
can avoid the suboption parsing mess. For example, ``--vf`` supports
|
||||
``MPV_FORMAT_NODE``, which lets you pass suboptions as a nested data structure
|
||||
of maps and arrays. (``--vo`` supports this in the same way, although this
|
||||
fact is undocumented.)
|
||||
of maps and arrays.
|
||||
|
||||
Paths
|
||||
-----
|
||||
@@ -431,7 +458,7 @@ tree and play the longest title.
|
||||
a bitmap video stream which can be superimposed over the main
|
||||
movie. mpv's subtitle styling and positioning options and keyboard
|
||||
shortcuts generally do not work with image-based subtitles.
|
||||
Exceptions include options like ``--stretch-dvd-subs`` and
|
||||
Exceptions include options like ``--stretch-dvd-subs`` and
|
||||
``--stretch-image-subs-to-screen``.
|
||||
|
||||
|
||||
@@ -528,7 +555,8 @@ profile name ``default`` to continue with normal options.
|
||||
|
||||
[slow]
|
||||
profile-desc="some profile name"
|
||||
vo=opengl:scale=ewa_lanczos:scale-radius=16
|
||||
# reference a builtin profile
|
||||
profile=opengl-hq
|
||||
|
||||
[fast]
|
||||
vo=vdpau
|
||||
@@ -549,10 +577,6 @@ Some profiles are loaded automatically. The following example demonstrates this:
|
||||
|
||||
::
|
||||
|
||||
[vo.vdpau]
|
||||
# Use hardware decoding
|
||||
hwdec=vdpau
|
||||
|
||||
[protocol.dvd]
|
||||
profile-desc="profile for dvd:// streams"
|
||||
alang=en
|
||||
@@ -561,11 +585,7 @@ Some profiles are loaded automatically. The following example demonstrates this:
|
||||
profile-desc="profile for .flv files"
|
||||
vf=flip
|
||||
|
||||
[ao.alsa]
|
||||
device=spdif
|
||||
|
||||
The profile name follows the schema ``type.name``, where type can be ``vo``
|
||||
to match the value the ``--vo`` option is set to, ``ao`` for ``--ao``,
|
||||
The profile name follows the schema ``type.name``, where type can be
|
||||
``protocol`` for the input/output protocol in use (see ``--list-protocols``),
|
||||
and ``extension`` for the extension of the path of the currently played file
|
||||
(*not* the file format).
|
||||
@@ -653,6 +673,15 @@ PROTOCOLS
|
||||
either aliases to documented protocols, or are just redirections to
|
||||
protocols implemented and documented in FFmpeg.
|
||||
|
||||
``ytdl://...``
|
||||
By default, the youtube-dl hook script (enabled by default for mpv CLI)
|
||||
only looks at http URLs. Prefixing an URL with ``ytdl://`` forces it to
|
||||
be always processed by the script. This can also be used to invoke special
|
||||
youtube-dl functionality like playing a video by ID or invoking search.
|
||||
|
||||
Keep in mind that you can't pass youtube-dl command line options by this,
|
||||
and you have to use ``--ytdl-raw-options`` instead.
|
||||
|
||||
``-``
|
||||
Play data from stdin.
|
||||
|
||||
@@ -688,7 +717,7 @@ PROTOCOLS
|
||||
``mf://[filemask|@listfile]`` ``--mf-...``
|
||||
Play a series of images as video.
|
||||
|
||||
``cdda://track[-endtrack][:speed][/device]`` ``--cdrom-device=PATH`` ``--cdda-...``
|
||||
``cdda://[device]`` ``--cdrom-device=PATH`` ``--cdda-...``
|
||||
Play CD.
|
||||
|
||||
``lavf://...``
|
||||
@@ -743,24 +772,32 @@ Currently this happens only in the following cases:
|
||||
or file associations provided by desktop environments)
|
||||
- if started from explorer.exe on Windows (technically, if it was started on
|
||||
Windows, and all of the stdout/stderr/stdin handles are unset)
|
||||
- manually adding ``--profile=pseudo-gui`` to the command line
|
||||
- started out of the bundle on OSX
|
||||
- if you manually use ``--player-operation-mode=pseudo-gui`` on the command line
|
||||
|
||||
This mode implicitly adds ``--profile=pseudo-gui`` to the command line, with
|
||||
the ``pseudo-gui`` profile being predefined with the following contents:
|
||||
This mode applies options from the builtin profile ``builtin-pseudo-gui``, but
|
||||
only if these haven't been set in the user's config file or on the command line.
|
||||
Also, for compatibility with the old pseudo-gui behavior, the options in the
|
||||
``pseudo-gui`` profile are applied unconditionally. In addition, the profile
|
||||
makes sure to enable the pseudo-GUI mode, so that ``--profile=pseudo-gui``
|
||||
works like in older mpv releases. The profiles are currently defined as follows:
|
||||
|
||||
::
|
||||
|
||||
[pseudo-gui]
|
||||
[builtin-pseudo-gui]
|
||||
terminal=no
|
||||
force-window=yes
|
||||
idle=once
|
||||
screenshot-directory=~~desktop/
|
||||
[pseudo-gui]
|
||||
player-operation-mode=pseudo-gui
|
||||
|
||||
This follows the mpv config file format. To customize pseudo-GUI mode, you can
|
||||
put your own ``pseudo-gui`` profile into your ``mpv.conf``. This profile will
|
||||
enhance the default profile, rather than overwrite it.
|
||||
.. warning::
|
||||
|
||||
The profile always overrides other settings in ``mpv.conf``.
|
||||
Currently, you can extend the ``pseudo-gui`` profile in the config file the
|
||||
normal way. This is deprecated. In future mpv releases, the behavior might
|
||||
change, and not apply your additional settings, and/or use a different
|
||||
profile name.
|
||||
|
||||
|
||||
.. include:: options.rst
|
||||
@@ -785,14 +822,7 @@ The profile always overrides other settings in ``mpv.conf``.
|
||||
|
||||
.. include:: changes.rst
|
||||
|
||||
|
||||
EMBEDDING INTO OTHER PROGRAMS (LIBMPV)
|
||||
======================================
|
||||
|
||||
mpv can be embedded into other programs as video/audio playback backend. The
|
||||
recommended way to do so is using libmpv. See ``libmpv/client.h`` in the mpv
|
||||
source code repository. This provides a C API. Bindings for other languages
|
||||
might be available (see wiki).
|
||||
.. include:: libmpv.rst
|
||||
|
||||
ENVIRONMENT VARIABLES
|
||||
=====================
|
||||
|
||||
+1299
-256
File diff suppressed because it is too large.
Load diff
+142
-126
@@ -20,22 +20,25 @@ The Interface
|
||||
|
||||
::
|
||||
|
||||
+------------------+-----------+--------------------+
|
||||
| playlist prev | title | playlist next |
|
||||
+-------+------+---+--+------+-+----+------+--------+
|
||||
| audio | skip | seek | | seek | skip | full |
|
||||
+-------+ back | back | play | frwd | frwd | screen |
|
||||
| sub | | | | | | |
|
||||
+-------+------+------+------+------+------+--------+
|
||||
| seekbar |
|
||||
+----------------+--------------+-------------------+
|
||||
| time passed | cache status | time remaining |
|
||||
+----------------+--------------+-------------------+
|
||||
+---------+----------+----------------------------+-------------+
|
||||
| pl prev | pl next | title | cache |
|
||||
+------+--+---+------+---------+-----------+------+-------+-----+
|
||||
| play | skip | skip | time | seekbar | time | audio | sub |
|
||||
| | back | frwd | elapsed | | left | | |
|
||||
+------+------+------+---------+-----------+------+-------+-----+
|
||||
|
||||
|
||||
playlist prev
|
||||
pl prev
|
||||
============= ================================================
|
||||
left-click play previous file in playlist
|
||||
right-click show playlist
|
||||
shift+L-click show playlist
|
||||
============= ================================================
|
||||
|
||||
pl next
|
||||
============= ================================================
|
||||
left-click play next file in playlist
|
||||
right-click show playlist
|
||||
shift+L-click show playlist
|
||||
============= ================================================
|
||||
|
||||
@@ -47,55 +50,33 @@ title
|
||||
right-click show filename
|
||||
============= ================================================
|
||||
|
||||
playlist next
|
||||
============= ================================================
|
||||
left-click play next file in playlist
|
||||
shift+L-click show playlist
|
||||
============= ================================================
|
||||
|
||||
audio and sub
|
||||
| Displays selected track and amount of available tracks
|
||||
|
||||
============= ================================================
|
||||
left-click cycle audio/sub tracks forward
|
||||
right-click cycle audio/sub tracks backwards
|
||||
shift+L-click show available audio/sub tracks
|
||||
============= ================================================
|
||||
|
||||
skip back
|
||||
============= ================================================
|
||||
left-click go to beginning of chapter / previous chapter
|
||||
shift+L-click show chapters
|
||||
============= ================================================
|
||||
|
||||
seek back
|
||||
============= ================================================
|
||||
left-click skip back 5 seconds
|
||||
right-click skip back 30 seconds
|
||||
shift-L-click skip back 1 frame
|
||||
============= ================================================
|
||||
cache
|
||||
| Shows current cache fill status
|
||||
|
||||
play
|
||||
============= ================================================
|
||||
left-click toggle play/pause
|
||||
============= ================================================
|
||||
|
||||
seek frwd
|
||||
skip back
|
||||
============= ================================================
|
||||
left-click skip forward 10 seconds
|
||||
right-click skip forward 60 seconds
|
||||
shift-L-click skip forward 1 frame
|
||||
left-click go to beginning of chapter / previous chapter
|
||||
right-click show chapters
|
||||
shift+L-click show chapters
|
||||
============= ================================================
|
||||
|
||||
skip frwd
|
||||
============= ================================================
|
||||
left-click go to next chapter
|
||||
right-click show chapters
|
||||
shift+L-click show chapters
|
||||
============= ================================================
|
||||
|
||||
fullscreen
|
||||
time elapsed
|
||||
| Shows current playback position timestamp
|
||||
|
||||
============= ================================================
|
||||
left-click toggle fullscreen
|
||||
left-click toggle displaying timecodes with milliseconds
|
||||
============= ================================================
|
||||
|
||||
seekbar
|
||||
@@ -105,23 +86,22 @@ seekbar
|
||||
left-click seek to position
|
||||
============= ================================================
|
||||
|
||||
time passed
|
||||
| Shows current playback position timestamp
|
||||
|
||||
============= ================================================
|
||||
left-click toggle displaying timecodes with milliseconds
|
||||
============= ================================================
|
||||
|
||||
cache status
|
||||
| Shows current cache fill status (only visible when below 45%)
|
||||
|
||||
time remaining
|
||||
time left
|
||||
| Shows remaining playback time timestamp
|
||||
|
||||
============= ================================================
|
||||
left-click toggle between total and remaining time
|
||||
============= ================================================
|
||||
|
||||
audio and sub
|
||||
| Displays selected track and amount of available tracks
|
||||
|
||||
============= ================================================
|
||||
left-click cycle audio/sub tracks forward
|
||||
right-click cycle audio/sub tracks backwards
|
||||
shift+L-click show available audio/sub tracks
|
||||
============= ================================================
|
||||
|
||||
Key Bindings
|
||||
~~~~~~~~~~~~
|
||||
|
||||
@@ -167,85 +147,117 @@ Example::
|
||||
Configurable Options
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
``showwindowed``
|
||||
| Default: yes
|
||||
| Enable the OSC when windowed
|
||||
|
||||
``showfullscreen``
|
||||
| Default: yes
|
||||
| Enable the OSC when fullscreen
|
||||
|
||||
``scalewindowed``
|
||||
| Default: 1.0
|
||||
| Scale factor of the OSC when windowed
|
||||
|
||||
``scalefullscreen``
|
||||
| Default: 1.0
|
||||
| Scale factor of the OSC when fullscreen
|
||||
|
||||
``scaleforcedwindow``
|
||||
| Default: 2.0
|
||||
| Scale factor of the OSC when rendered on a forced (dummy) window
|
||||
|
||||
``vidscale``
|
||||
| Default: yes
|
||||
| Scale the OSC with the video
|
||||
| ``no`` tries to keep the OSC size constant as much as the window size allows
|
||||
|
||||
``valign``
|
||||
| Default: 0.8
|
||||
| Vertical alignment, -1 (top) to 1 (bottom)
|
||||
|
||||
``halign``
|
||||
| Default: 0.0
|
||||
| Horizontal alignment, -1 (left) to 1 (right)
|
||||
|
||||
``boxalpha``
|
||||
| Default: 80
|
||||
| Alpha of the background box, 0 (opaque) to 255 (fully transparent)
|
||||
|
||||
``hidetimeout``
|
||||
| Default: 500
|
||||
| Duration in ms until the OSC hides if no mouse movement, must not be
|
||||
negative
|
||||
|
||||
``fadeduration``
|
||||
| Default: 200
|
||||
| Duration of fade out in ms, 0 = no fade
|
||||
|
||||
``deadzonesize``
|
||||
| Default: 0
|
||||
| Size of the deadzone. The deadzone is an area that makes the mouse act
|
||||
like leaving the window. Movement there won't make the OSC show up and
|
||||
it will hide immediately if the mouse enters it. The deadzone starts
|
||||
at the window border opposite to the OSC and the size controls how much
|
||||
of the window it will span. Values between 0 and 1.
|
||||
|
||||
``minmousemove``
|
||||
| Default: 3
|
||||
| Minimum amount of pixels the mouse has to move between ticks to make
|
||||
the OSC show up
|
||||
|
||||
``layout``
|
||||
| Default: box
|
||||
| The layout for the OSC. Currently available are: box, slimbox,
|
||||
bottombar and topbar.
|
||||
Default: bottombar
|
||||
|
||||
The layout for the OSC. Currently available are: box, slimbox,
|
||||
bottombar and topbar. Default pre-0.21.0 was 'box'.
|
||||
|
||||
``seekbarstyle``
|
||||
| Default: slider
|
||||
| Sets the style of the seekbar, slider (diamond marker) or bar (fill)
|
||||
Default: bar
|
||||
|
||||
Sets the style of the seekbar, slider (diamond marker), knob (circle
|
||||
marker with guide), or bar (fill).
|
||||
Default pre-0.21.0 was 'slider'.
|
||||
|
||||
``deadzonesize``
|
||||
Default: 0.5
|
||||
|
||||
Size of the deadzone. The deadzone is an area that makes the mouse act
|
||||
like leaving the window. Movement there won't make the OSC show up and
|
||||
it will hide immediately if the mouse enters it. The deadzone starts
|
||||
at the window border opposite to the OSC and the size controls how much
|
||||
of the window it will span. Values between 0.0 and 1.0, where 0 means the
|
||||
OSC will always popup with mouse movement in the window, and 1 means the
|
||||
OSC will only show up when the mouse hovers it. Default pre-0.21.0 was 0.
|
||||
|
||||
``minmousemove``
|
||||
Default: 0
|
||||
|
||||
Minimum amount of pixels the mouse has to move between ticks to make
|
||||
the OSC show up. Default pre-0.21.0 was 3.
|
||||
|
||||
``showwindowed``
|
||||
Default: yes
|
||||
|
||||
Enable the OSC when windowed
|
||||
|
||||
``showfullscreen``
|
||||
Default: yes
|
||||
|
||||
Enable the OSC when fullscreen
|
||||
|
||||
``scalewindowed``
|
||||
Default: 1.0
|
||||
|
||||
Scale factor of the OSC when windowed.
|
||||
|
||||
``scalefullscreen``
|
||||
Default: 1.0
|
||||
|
||||
Scale factor of the OSC when fullscreen
|
||||
|
||||
``scaleforcedwindow``
|
||||
Default: 2.0
|
||||
|
||||
Scale factor of the OSC when rendered on a forced (dummy) window
|
||||
|
||||
``vidscale``
|
||||
Default: yes
|
||||
|
||||
Scale the OSC with the video
|
||||
``no`` tries to keep the OSC size constant as much as the window size allows
|
||||
|
||||
``valign``
|
||||
Default: 0.8
|
||||
|
||||
Vertical alignment, -1 (top) to 1 (bottom)
|
||||
|
||||
``halign``
|
||||
Default: 0.0
|
||||
|
||||
Horizontal alignment, -1 (left) to 1 (right)
|
||||
|
||||
``barmargin``
|
||||
Default: 0
|
||||
|
||||
Margin from bottom (bottombar) or top (topbar), in pixels
|
||||
|
||||
``boxalpha``
|
||||
Default: 80
|
||||
|
||||
Alpha of the background box, 0 (opaque) to 255 (fully transparent)
|
||||
|
||||
``hidetimeout``
|
||||
Default: 500
|
||||
|
||||
Duration in ms until the OSC hides if no mouse movement, must not be
|
||||
negative
|
||||
|
||||
``fadeduration``
|
||||
Default: 200
|
||||
|
||||
Duration of fade out in ms, 0 = no fade
|
||||
|
||||
``tooltipborder``
|
||||
Default: 1
|
||||
|
||||
Size of the tooltip outline when using bottombar or topbar layouts
|
||||
|
||||
``timetotal``
|
||||
| Default: no
|
||||
| Show total time instead of time remaining
|
||||
Default: no
|
||||
|
||||
Show total time instead of time remaining
|
||||
|
||||
``timems``
|
||||
| Default: no
|
||||
| Display timecodes with milliseconds
|
||||
Default: no
|
||||
|
||||
Display timecodes with milliseconds
|
||||
|
||||
``visibility``
|
||||
| Default: auto (auto hide/show on mouse move)
|
||||
| Also supports ``never`` and ``always``
|
||||
Default: auto (auto hide/show on mouse move)
|
||||
|
||||
Also supports ``never`` and ``always``
|
||||
|
||||
Script Commands
|
||||
~~~~~~~~~~~~~~~
|
||||
@@ -269,3 +281,7 @@ to set auto mode (the default) with ``b``::
|
||||
a script-message osc-visibility never
|
||||
b script-message osc-visibility auto
|
||||
|
||||
``osc-playlist``, ``osc-chapterlist``, ``osc-tracklist``
|
||||
Shows a limited view of the respective type of list using the OSC. First
|
||||
argument is duration in seconds.
|
||||
|
||||
@@ -806,13 +806,6 @@ Available filters are:
|
||||
1-9
|
||||
Apply high quality VDPAU scaling (needs capable hardware).
|
||||
|
||||
``vdpaurb``
|
||||
VDPAU video read back. Works with ``--vo=vdpau`` and ``--vo=opengl`` only.
|
||||
This filter will read back frames decoded by VDPAU so that other filters,
|
||||
which are not normally compatible with VDPAU, can be used like normal.
|
||||
This filter must be specified before ``vdpaupp`` in the filter chain if
|
||||
``vdpaupp`` is used.
|
||||
|
||||
``d3d11vpp``
|
||||
Direct3D 11 video post processing. Currently requires D3D11 hardware
|
||||
decoding for use.
|
||||
|
||||
+139
-958
File diff suppressed because it is too large.
Load diff
@@ -7,8 +7,8 @@ large part of which is incompatible or completely changes how the player
|
||||
behaves. Although there are still many similarities to its ancestors, **mpv**
|
||||
should generally be treated as a completely different program.
|
||||
|
||||
.. note::
|
||||
These lists are incomplete.
|
||||
.. admonition:: Warning
|
||||
This document is not updated anymore, and is incomplete and outdated.
|
||||
|
||||
General Changes from MPlayer to mpv
|
||||
-----------------------------------
|
||||
@@ -262,13 +262,14 @@ Command Line Switches
|
||||
``-ssf <sub>`` ``--sws-...``
|
||||
``-stop-xscreensaver`` ``--stop-screensaver``
|
||||
``-sub-fuzziness`` ``--sub-auto``
|
||||
``-sub-text-*`` ``--sub-*``
|
||||
``-sub`` ``--sub-file``
|
||||
``-subcp`` ``--sub-codepage``
|
||||
``-subdelay`` ``--sub-delay``
|
||||
``-subfile`` ``--sub-file``
|
||||
``-subfont-*`` ``--sub-text-*``, ``--osd-*``
|
||||
``-subfont-*`` ``--sub-*``, ``--osd-*``
|
||||
``-subfont-text-scale`` ``--sub-scale``
|
||||
``-subfont`` ``--sub-text-font``
|
||||
``-subfont`` ``--sub-font``
|
||||
``-subfps`` ``--sub-fps``
|
||||
``-subpos`` ``--sub-pos``
|
||||
``-sws`` ``--sws-scaler``
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
* [Overview](#overview)
|
||||
* [Downloads](#downloads)
|
||||
* [Changelog](#changelog)
|
||||
* [Compilation](#compilation)
|
||||
* [FFmpeg vs. Libav](#ffmpeg-vs-libav)
|
||||
* [Release cycle](#release-cycle)
|
||||
@@ -14,6 +15,7 @@
|
||||
* [Contributing](#contributing)
|
||||
* [Relation to MPlayer and mplayer2](#relation-to-mplayer-and-mplayer2)
|
||||
* [Wiki](https://github.com/mpv-player/mpv/wiki)
|
||||
* [FAQ](https://github.com/mpv-player/mpv/wiki/FAQ)
|
||||
* [Man pages](http://mpv.io/manual/master/)
|
||||
* [Contact](#contact)
|
||||
* [License](#license)
|
||||
@@ -47,6 +49,20 @@ Releases can be found on the [release list][releases].
|
||||
For semi-official builds and third-party packages please see
|
||||
[mpv.io](http://mpv.io/installation/).
|
||||
|
||||
## Changelog
|
||||
|
||||
|
||||
There is no completely changelog, however changes to the player core interface
|
||||
are listed in the [interface changelog][interface-changes].
|
||||
|
||||
Changes to the C API are documented in the [client API changelog][api-changes].
|
||||
|
||||
The [release list][releases] has a summary of most of the important changes
|
||||
on every release.
|
||||
|
||||
Changes to the default key bindings are inidcated in
|
||||
[restore-old-bindings.conf][restore-old-bindings].
|
||||
|
||||
## Compilation
|
||||
|
||||
|
||||
@@ -79,7 +95,7 @@ Essential dependencies (incomplete list):
|
||||
- Audio output development headers (libasound/ALSA, pulseaudio)
|
||||
- FFmpeg libraries (libavutil libavcodec libavformat libswscale libavfilter
|
||||
and either libswresample or libavresample)
|
||||
At least FFmpeg 2.4.0 or Libav 11 is required.
|
||||
At least FFmpeg 3.2.2 or Libav 12 is required.
|
||||
- zlib
|
||||
- iconv (normally provided by the system libc)
|
||||
- libass (OSD, OSC, text subtitles)
|
||||
@@ -197,7 +213,7 @@ In general, mpv should be considered a completely new program, rather than a
|
||||
MPlayer drop-in replacement.
|
||||
|
||||
If you are wondering what's different from mplayer2 and MPlayer, an incomplete
|
||||
list of changes is located [here][mplayer-changes].
|
||||
and now unmaintained list of changes is located [here][mplayer-changes].
|
||||
|
||||
## Contact
|
||||
|
||||
@@ -220,6 +236,9 @@ only if discretion is required.
|
||||
[release-policy]: https://github.com/mpv-player/mpv/blob/master/DOCS/release-policy.md
|
||||
[windows_compilation]: https://github.com/mpv-player/mpv/blob/master/DOCS/compile-windows.md
|
||||
[mplayer-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/mplayer-changes.rst
|
||||
[interface-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/interface-changes.rst
|
||||
[api-changes]: https://github.com/mpv-player/mpv/blob/master/DOCS/client-api-changes.rst
|
||||
[restore-old-bindings]: https://github.com/mpv-player/mpv/blob/master/etc/restore-old-bindings.conf
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+378
@@ -1,3 +1,381 @@
|
||||
Release 0.23.0
|
||||
==============
|
||||
|
||||
Now requires at least FFmpeg 3.2.2.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- vo_rpi: partially undeprecate
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- vo_opengl: hwdec_cuda: Support P016 output surfaces
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- charset_conv: drop enca and libguess support in favor of uchardet
|
||||
- vf_vdpaurb: remove this filter in favor of --hwdec=vdpau-copy
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- TOOLS/autoload: allow disabling through script-opts
|
||||
- demux, stream: add --access-references to prevent opening referenced files
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- options: deprecate codec family selection in --vd/--ad
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- macOS: remove --fs-black-out-screens
|
||||
- options: remove deprecated sub-option handling for --vo and --ao
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- Windows: window styles improvements (allow minimizing borderless/fullscreen window) (#2229, #2451)
|
||||
- ad_spdif: Fix crash when spdif muxer is not available
|
||||
- audio: fix --audio-stream-silence with ao_alsa
|
||||
- audio: fix --audio-stream-silence with ao_wasapi
|
||||
- build: drop build-time dependency on Perl
|
||||
- build: support linking ANGLE (previously loaded dynamically)
|
||||
- d3d11va: unconditionally load D3D DLLs (#3348)
|
||||
- demux_mkv: fix seeking in some broken files (#3920)
|
||||
- hwdec_cuda: allow building without CUDA SDK (load CUDA dynamically)
|
||||
- macOS: fix dropping of URLs containing query strings on the window
|
||||
- macOS: fullscreen refactoring (#2857, #3272, #1352, #2062, #3864)
|
||||
- macOS: support append file to paylist on drop (#2166)
|
||||
- macOS: update the menu and remove conflicting “Quit & remember position” item (#3865)
|
||||
- osc: don't hide playlist buttons, just disable
|
||||
- osc: fix possible race condition in right timecode
|
||||
- osc: topbar: use same styles as bottombar
|
||||
- player: don't print format detection error when aborting loading
|
||||
- vdpau: fix vaapi probing if libvdpau-va-gl1 is present
|
||||
- video: use demuxer-signaled duration for last video frame (#3924)
|
||||
|
||||
|
||||
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.22.0..v0.23.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.22.0...v0.23.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.22.0..v0.23.0
|
||||
|
||||
|
||||
Release 0.22.0
|
||||
==============
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- audio/out: add AudioUnit output driver for iOS
|
||||
- demux_mkv: parse Matroska colorimetry metadata
|
||||
- filter_kernels: add ability to taper kernels/windows, add tukey window
|
||||
- osc: add seekbarstyle=knob (#2365)
|
||||
- video/out: add tct as modern caca alternative for true-color and 256-color terminals
|
||||
- video: add --hwdec=vdpau-copy mode
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- vf_vdpaurb: deprecated in favor of --hwdec=vdpau-copy
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- --opengl-early-flush=auto
|
||||
- --scale-taper, --scale-wtaper
|
||||
- --scale-wblur
|
||||
- macOS: --hidpi-window-scale (#3716)
|
||||
- osc: add script message handlers for chapter/track/playlists
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- Apply --autofit-larger after --autofit-smaller (#3753)
|
||||
- Set subtitle track title to indicate hearing/visual impaired tracks
|
||||
- ao_alsa: disable chmap API use for mono/stereo (#2905, #3045)
|
||||
- build: add required failure message for libavfilter check (#3692)
|
||||
- build: fix compilation with mingw-w64/Clang (#3800)
|
||||
- build: make VideoToolbox available on iOS
|
||||
- command: fix reset-on-next-file=all and tv-freq option (#3708)
|
||||
- command: if window-scale can't be set properly, set it as option (#3724)
|
||||
- demux_mkv: don't recursively resolve timeline for opened reference files
|
||||
- demux_mkv: fix ordered chapter sources with ordered editions
|
||||
- opengl: compile against iOS OpenGLES implementation
|
||||
- options: handle legacy no-* sub-options
|
||||
- osc: add alpha animation to tooltip (fix lingering tooltip)
|
||||
- osc: change default deadzonesize to 0.5
|
||||
- osc: don't wrap the title
|
||||
- osc: fix crash after reaching a certain position in limited lists (#3691)
|
||||
- osc: fix crash with no chapters
|
||||
- osc: fix crashes when dragging seekbar across file changes (#3210)
|
||||
- osc: fix displaying only half of the entries when at the end of a list
|
||||
- osc: fix missing chapter ticks with seekbarstyle=bar
|
||||
- osc: slimbox: fix clipping with seekbarstyle=bar (#3737)
|
||||
- osc: top/bottombar: also scale when min-width is reached to match box/slimbox behavior
|
||||
- osc: top/bottombar: dynamically size timecodes according to timems
|
||||
- osc: top/bottombar: rescale layout to same size with scale=1
|
||||
- osc: top/bottombar: scale title if too large like box
|
||||
- player: consistently initialize screensaver state with --force-window
|
||||
- player: enable no-video subtitle display on coverart too
|
||||
- player: make --start-time work with --rebase-start-time=no
|
||||
- player: make sure non-video subtitle rendering is reset if video resumes (#3770)
|
||||
- player: removing last playlist entry while looping should not stop (#3808)
|
||||
- player: show subtitles on VO if --force-window is used (#3684)
|
||||
- player: speed up audio/video re-sync when there is a huge delay
|
||||
- vdpau: fix hwdec uninit (#3788)
|
||||
- vo_opengl: blend against background color for --alpha=blend
|
||||
- vo_opengl: context_rpi: fix stdatomic usage (#3699)
|
||||
- vo_opengl: fix --blend-subtitles handling (#3773)
|
||||
- vo_opengl: fix redrawing with hardware decoding (#3773)
|
||||
- vo_opengl: partially re-enable glFlush() calls (#3670)
|
||||
- ytdl_hook: sort chapters by time
|
||||
|
||||
|
||||
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.21.0..v0.22.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.21.0...v0.22.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.21.0..v0.22.0
|
||||
|
||||
|
||||
Release 0.21.0
|
||||
==============
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- config: allow profile forward-references in default profile
|
||||
- demux_lavf: "support" mov edit lists and log errors if used (FFmpeg only)
|
||||
- hwdec: Add support for CUDA and cuvid/NvDecode (mostly useful on Linux where VDPAU still lacks HEVC Main 10 support; keep using DXVA2 on Windows)
|
||||
- osc: add right-click behavior to playlist and chapter buttons
|
||||
- osc: add user-alterable margin for top/bottombar
|
||||
- rpi: add --hwdec=rpi-copy (#3604)
|
||||
- sd_lavc: enable teletext
|
||||
- vaapi: support drm devices when running in vaapi-copy mode
|
||||
- vd_lavc: Add hwdec wrapper for crystalhd
|
||||
- vo_opengl: add hw overlay support and use it for RPI
|
||||
- vo_opengl: basic mali fbdev support
|
||||
- vo_opengl: rpi: merge vo_rpi features
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- vo_opengl: remove pre/post/scale-shaders in favor of user-shaders
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- config: deprecate ao and vo auto-profiles
|
||||
- vo_rpi: deprecate this VO
|
||||
|
||||
|
||||
Options and Commands
|
||||
--------------------
|
||||
|
||||
Added
|
||||
~~~~~
|
||||
|
||||
- add --teletext-page option
|
||||
- af_pan: add af-command support to change the matrix
|
||||
- af_rubberband: add af-command and option to change the pitch
|
||||
- command: add a load-script command
|
||||
- command: add a video-dec-params property
|
||||
- command: add an apply-profile command
|
||||
- command: add audio-pts property to get the audio pts
|
||||
- command: add options to property list
|
||||
- command: add sub-text property for current subtitle text
|
||||
- command: export profile list as a property (#977)
|
||||
- options: add --hwdec=yes as alias for --hwdec=auto
|
||||
- player: add --player-operation-mode=pseudo-gui
|
||||
- player: add --video-osd=no option to disable video OSD (#3387)
|
||||
- player: add --watch-later-directory option
|
||||
- stream_bluray: select title by playlist (bd://mpls/[playlist])
|
||||
- vo_opengl: disable glFlush() by default, and add an option to enable it for testing
|
||||
|
||||
|
||||
Changed
|
||||
~~~~~~~
|
||||
|
||||
- af_rubberband: default to channels=together
|
||||
- command: allow absolute seeks relative to end of stream
|
||||
- command: make bitrate properties observable
|
||||
- command: make most options observable
|
||||
- msg: make --log-file and --dump-stats accept config path expansion (#3591)
|
||||
- options: deprecate --playlist-pos to --playlist-start
|
||||
- options: make input options generally runtime-settable
|
||||
- options: rename subtitle options (--sub-text- → --sub; --ass- → --sub-ass-)
|
||||
- osc: change default layout to bottombar, seekbarstyle to bar, use larger scalewindowed and scalefullscreen
|
||||
- player: make --terminal, --log-file, --dump-stats, --osc, --ytdl, --audio-*, --priority, --stop-screensaver (#3615) settable at runtime
|
||||
- vo_drm: change CLI options
|
||||
- Change connector selection to accept human readable names (such as eDP-1, HDMI-A-2) rather than arbitrary numbers.
|
||||
- Change GPU selection to accept GPU number rather than device paths.
|
||||
- Merge connector and GPU selection into one --drm-connector.
|
||||
- Add support for --drm-connector=help.
|
||||
- Add support for --drm-* in EGL backend.
|
||||
- vo_opengl: rename 3dlut-size to icc-3dlut-size
|
||||
|
||||
|
||||
Removed
|
||||
~~~~~~~
|
||||
|
||||
- command: remove hwdec-active and hwdec-detected properties
|
||||
- command: remove vo-cmdline
|
||||
- options: drop unreferenced --bluray-angle option
|
||||
- vo, ao: disable positional parameter suboptions
|
||||
- vo_xv: replace no-colorkey with ck-method=none
|
||||
|
||||
|
||||
Deprecated
|
||||
~~~~~~~~~~
|
||||
|
||||
- all VOs and AOs: deprecate sub-options, add them as global options
|
||||
- command: deprecate "cache" property, replace with "cache-percent"
|
||||
- deprecate --vo-defaults
|
||||
- deprecate --vo=opengl-hq alias in favor of opengl-hq profile
|
||||
- options: deprecate --mute=auto
|
||||
- vo_direct3d: deprecate direct3d_shaders alias, use shaders by default
|
||||
- vo_opengl: deprecate 'drm-egl' backend and introduce 'drm' instead
|
||||
|
||||
|
||||
Fixes and Minor Enhancements
|
||||
----------------------------
|
||||
|
||||
- TOOLS/zsh.pl: die if we can't parse main options
|
||||
- TOOLS/zsh.pl: don't filter files by extension (#2273)
|
||||
- ao_alsa: try to fallback to "hdmi" before "iec958" for spdif
|
||||
- ao_rsound: fix compilation (#3501)
|
||||
- aspect: use nominal width instead of actual width for video-unscaled
|
||||
- audio/out: prevent underruns with spdif under certain conditions
|
||||
- audio: fix late audio start (#3610)
|
||||
- audio: fix missed wakeup when changing audio output device
|
||||
- audio: fix segfault when yanking USB DAC
|
||||
- charset_conv: Use CP949 instead of EUC-KR
|
||||
- command: don't log "ignore" command with -v verbosity
|
||||
- command: try selecting the next track if track switching fails (#3446)
|
||||
- demux_mkv: don't crash if --ordered-chapters-files fails
|
||||
- displayconfig: treat a refresh rate of 1 as invalid
|
||||
- ipc: log when listening to IPC socket (#3598)
|
||||
- macOS: fix fullscreen regression on 10.11 and newer (#3364)
|
||||
- mp_image: fix clearing to black with p010 format
|
||||
- osc: Fix scaling issues when toggling fullscreen (#3429)
|
||||
- osc: align text vertically in top/bottombar (#2093)
|
||||
- osc: change seekbar background's alpha scaling
|
||||
- osc: fix display of chapters and playlist scaling
|
||||
- osc: move tooltip to inside seekbar for top/bottombar
|
||||
- osc: show playlist/chapter list on prev/next instead of osd
|
||||
- path: default ~~ paths to home directory (#3591)
|
||||
- player: do not let pseudo-gui override user config settings
|
||||
- player: enable reading from stdin after loading input.conf
|
||||
- player: fix instant subtitle refresh on track switches
|
||||
- player: make --force-window work with opengl-cb
|
||||
- stream_file: don't use poll() on directories (would hang on macOS; #3530, #3649)
|
||||
- stream_lavf: fix determining seekability (#1701)
|
||||
- sub: actually apply text alignment options to non-ASS subtitles
|
||||
- terminal-win: support modifier keys in console input (#3625)
|
||||
- vo_drm: fix segfault when using invalid card
|
||||
- vo_opengl: apply 90° rotation to chroma texture size (#3568)
|
||||
- vo_opengl: fix incorrect video rendering after vdpau preemption recovery
|
||||
- vo_opengl: partially fix dumb-mode cropping with rotation
|
||||
- vo_opengl: rpi: use overlay for yuv420p too
|
||||
- w32_common: initialize playback status as soon as possible (#3482)
|
||||
- wayland: reject resize events with either dimension being 0 (#3679)
|
||||
- x11: fix external fullscreen update (#3570)
|
||||
- ytdl_hook: Add title to playlist items if available
|
||||
- ytdl_hook: Set aspect ratio for anamorphic video
|
||||
- ytdl_hook: Support playlist entries without subtitles
|
||||
- ytdl_hook: add chapters by parsing video's description
|
||||
- ytdl_hook: don't add subtitles with unknown duration
|
||||
- ytdl_hook: temporarily force disable dash segments formats
|
||||
|
||||
|
||||
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.20.0..v0.21.0`
|
||||
in the git repository or by visiting either
|
||||
https://github.com/mpv-player/mpv/compare/v0.20.0...v0.21.0 or
|
||||
https://git.srsfckn.biz/mpv/log/?qt=range&q=v0.20.0..v0.21.0
|
||||
|
||||
|
||||
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
|
||||
- command: add sub-speed property
|
||||
|
||||
|
||||
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.19.0..v0.20.0
|
||||
|
||||
|
||||
Release 0.19.0
|
||||
==============
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ pacman -S --noconfirm --needed \
|
||||
$MINGW_PACKAGE_PREFIX-libjpeg-turbo \
|
||||
$MINGW_PACKAGE_PREFIX-lua51 \
|
||||
$MINGW_PACKAGE_PREFIX-rubberband \
|
||||
$MINGW_PACKAGE_PREFIX-uchardet-git
|
||||
$MINGW_PACKAGE_PREFIX-uchardet
|
||||
|
||||
# Delete unused packages to reduce space used in the Appveyor cache
|
||||
pacman -Sc --noconfirm
|
||||
@@ -1,24 +0,0 @@
|
||||
#! /usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# Convert the contents of a file into a C string constant.
|
||||
# Note that the compiler will implicitly add an extra 0 byte at the end
|
||||
# of every string, so code using the string may need to remove that to get
|
||||
# the exact contents of the original file.
|
||||
# FIXME: why not a char array?
|
||||
|
||||
# treat only alphanumeric and punctuations (excluding " and ?) as safe
|
||||
my $unsafe_chars = qr{[^][A-Za-z0-9!#%&'()*+,./:;<=>^_{|}~ -]};
|
||||
|
||||
for my $file (@ARGV) {
|
||||
open my $fh, '<:raw', $file or next;
|
||||
print "/* Generated from $file */\n";
|
||||
while (<$fh>) {
|
||||
# replace unsafe chars with their equivalent octal escapes
|
||||
s/($unsafe_chars)/\\@{[sprintf '%03o', ord($1)]}/gos;
|
||||
print "\"$_\"\n"
|
||||
}
|
||||
close $fh;
|
||||
}
|
||||
Executable
+27
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Convert the contents of a file into a C string constant.
|
||||
# Note that the compiler will implicitly add an extra 0 byte at the end
|
||||
# of every string, so code using the string may need to remove that to get
|
||||
# the exact contents of the original file.
|
||||
|
||||
import sys
|
||||
|
||||
# Indexing a byte string yields int on Python 3.x, and a str on Python 2.x
|
||||
def pord(c):
|
||||
return ord(c) if type(c) == str else c
|
||||
|
||||
def main(infile):
|
||||
conv = ['\\' + ("%03o" % c) for c in range(256)]
|
||||
safe_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" \
|
||||
"0123456789!#%&'()*+,-./:;<=>?[]^_{|}~ "
|
||||
for c in safe_chars:
|
||||
conv[ord(c)] = c
|
||||
for c, esc in ("\nn", "\tt", r"\\", '""'):
|
||||
conv[ord(c)] = '\\' + esc
|
||||
for line in infile:
|
||||
sys.stdout.write('"' + ''.join(conv[pord(c)] for c in line) + '"\n')
|
||||
|
||||
with open(sys.argv[1], 'rb') as infile:
|
||||
sys.stdout.write("// Generated from %s\n\n" % sys.argv[1])
|
||||
main(infile)
|
||||
@@ -1,30 +0,0 @@
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: Module collection to parse Matroska files.
|
||||
package Parse::Matroska;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
C<use>s L<Parse::Matroska::Reader>. See the documentation
|
||||
of the modules mentioned in L</"SEE ALSO"> for more information
|
||||
in how to use this module.
|
||||
|
||||
It's intended for this module to contain high-level interfaces
|
||||
to the other modules in the distribution.
|
||||
|
||||
=head1 SOURCE CODE
|
||||
|
||||
L<https://github.com/Kovensky/Parse-Matroska>
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Parse::Matroska::Reader>, L<Parse::Matroska::Element>,
|
||||
L<Parse::Matroska::Definitions>.
|
||||
|
||||
=cut
|
||||
|
||||
use Parse::Matroska::Reader;
|
||||
|
||||
1;
|
||||
@@ -1,357 +0,0 @@
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: internal EBML grammar definitions
|
||||
package Parse::Matroska::Definitions;
|
||||
|
||||
use Parse::Matroska::Utils qw{uniq uncamelize};
|
||||
|
||||
use Exporter;
|
||||
our @ISA = qw{Exporter};
|
||||
our @EXPORT_OK = qw{elem_by_hexid %EBML_DEFINITION %MATROSKA_DEFINITION};
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Parse::Matroska::Definitions qw{elem_by_hexid};
|
||||
my $ebml_id = elem_by_hexid('1a45dfa3');
|
||||
print "EBML ID $ebml_id->{elid}'s name: $ebml_id->{name}";
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Contains the definition of the EBML grammar as expected in
|
||||
Matroska files. This module is meant mostly for internal use.
|
||||
|
||||
As this was extended from a script in mpv-player, some data
|
||||
generated is apparently useless for regular module users
|
||||
but is still relevant to the mpv-player script. Such data
|
||||
is annotated as being for mpv compatibility.
|
||||
|
||||
=head1 NOTE
|
||||
|
||||
The API of this module is not yet considered stable.
|
||||
|
||||
=head1 GLOBALS
|
||||
|
||||
These global variables are considered B<immutable>.
|
||||
|
||||
=head2 @Parse::Matroska::Definitions::global_elem_list
|
||||
|
||||
A global list of known matroska elements. Useful for
|
||||
mpv's matroska script, used for generating C headers
|
||||
that parse matroska.
|
||||
|
||||
=head2 %Parse::Matroska::Definitions::global_elem_dict
|
||||
|
||||
A global hash of known matroska elements. Used internally
|
||||
by L</elem_by_hexid($id)>.
|
||||
|
||||
=cut
|
||||
|
||||
@Parse::Matroska::Definitions::global_elem_list = ();
|
||||
%Parse::Matroska::Definitions::global_elem_dict = ();
|
||||
|
||||
=head2 %EBML_DEFINITION
|
||||
|
||||
Optionally-importable hash of known EBML IDs belonging
|
||||
to the EBML generic grammar.
|
||||
|
||||
=head2 %MATROSKA_DEFINITION
|
||||
|
||||
Optionally-importable hash of known EBML IDs belonging
|
||||
to the Matroska-specific grammar.
|
||||
|
||||
=cut
|
||||
|
||||
our %EBML_DEFINITION = define_ebml();
|
||||
our %MATROSKA_DEFINITION = define_matroska();
|
||||
|
||||
=method elem_by_hexid($id)
|
||||
|
||||
Returns an EBML Element Definition corresponding to the provided
|
||||
hexadecimal string. Returns C<undef> if the element is unknown.
|
||||
|
||||
=cut
|
||||
sub elem_by_hexid {
|
||||
my ($elid) = @_;
|
||||
return $Parse::Matroska::Definitions::global_elem_dict{$elid};
|
||||
}
|
||||
|
||||
################################################
|
||||
### Helper functions for document definition ###
|
||||
################################################
|
||||
|
||||
# used by elem when setting the 'valname' key
|
||||
use constant TYPE_MAP => {
|
||||
uint => 'uint64_t',
|
||||
str => 'char *',
|
||||
binary => 'struct bstr',
|
||||
ebml_id => 'uint32_t',
|
||||
float => 'double',
|
||||
sint => 'int64_t',
|
||||
};
|
||||
|
||||
# this will be localized to "MATROSKA" or "EBML" on the elem declarations
|
||||
our $ELEM_DEFINE_TYPE = undef;
|
||||
|
||||
=method elem($name,$elid,$valtype)
|
||||
|
||||
NOTE: never call this function yourself; it changes data structures
|
||||
that are considered immutable outside of this package.
|
||||
|
||||
Internal API function that generates the EBML Element Definitions.
|
||||
|
||||
This API function returns an array which first element is C<$elid>
|
||||
and the second is a generated hash. The generated hash is stored
|
||||
in the @global_elem_list and %global_elem_dict.
|
||||
|
||||
The generated hash contains:
|
||||
|
||||
=for :list
|
||||
= name
|
||||
The EBML Element's name, given through C<$name>.
|
||||
= elid
|
||||
The EBML Element's hex id, given through C<$elid>. Used for lookups by L</elem_by_hexid($id)>.
|
||||
= valtype
|
||||
The EBML Element's type, given through C<$valtype>, except when C<$valtype> is an arrayref.
|
||||
= multiple
|
||||
If C<$name> ends with a C<*>, this is set as true and strips the C<*> from L</name>. Used to
|
||||
mark elements that may be repeated.
|
||||
= subelements
|
||||
An arrayref of elements that may be children of this element, given through C<$valtype> if it
|
||||
is an arrayref. Sets L</valtype> to C<sub> if there are subelements.
|
||||
= subids
|
||||
An arrayref listing all the L</elid>s of subelements, C<uniq>ified.
|
||||
|
||||
The following elements are for mpv compatibility:
|
||||
|
||||
=for :list
|
||||
= definename
|
||||
Name used for generating C #defines.
|
||||
= fieldname
|
||||
Name used for generating C struct fields.
|
||||
= structname
|
||||
Name used for generating C struct names.
|
||||
= ebmltype
|
||||
A pre-#defined constant to describe the element's type.
|
||||
= valname
|
||||
Typename used when declaring a struct field referring to this element.
|
||||
|
||||
=cut
|
||||
sub elem {
|
||||
my %e = (name => shift, elid => shift, valtype => shift);
|
||||
|
||||
# strip * from name, set 'multiple' if there was one
|
||||
$e{multiple} = scalar $e{name} =~ s/\*$//;
|
||||
|
||||
# ELEM_DEFINE_TYPE is either MATROSKA or EBML
|
||||
$e{definename} = "${ELEM_DEFINE_TYPE}_ID_".uc($e{name});
|
||||
$e{fieldname} = uncamelize $e{name};
|
||||
$e{structname} = "ebml_$e{fieldname}";
|
||||
|
||||
if (ref $e{valtype} eq 'HASH') {
|
||||
$e{subelements} = $e{valtype};
|
||||
$e{subids} = uniq map { $_->{elid} } values %{$e{subelements}};
|
||||
$e{valtype} = 'sub';
|
||||
$e{ebmltype} = 'EBML_TYPE_SUBELEMENTS';
|
||||
$e{valname} = "struct $e{structname}";
|
||||
} else {
|
||||
$e{ebmltype} = "EBML_TYPE_\U$e{valtype}";
|
||||
die "Unrecognized value type $e{valtype}" unless
|
||||
defined ($e{valname} = TYPE_MAP->{$e{valtype}});
|
||||
}
|
||||
my $e = \%e;
|
||||
push @Parse::Matroska::Definitions::global_elem_list, $e;
|
||||
$Parse::Matroska::Definitions::global_elem_dict{$e{elid}} = $e;
|
||||
return ($e{elid}, $e);
|
||||
}
|
||||
|
||||
#############################################
|
||||
### EBML and Matroska document definitons ###
|
||||
#############################################
|
||||
|
||||
=method define_ebml
|
||||
|
||||
Internal function that defines the EBML generic grammar.
|
||||
|
||||
Must not be called from outside the package.
|
||||
|
||||
=cut
|
||||
sub define_ebml {
|
||||
local $ELEM_DEFINE_TYPE = 'EBML';
|
||||
return (
|
||||
elem('EBML', '1a45dfa3', {
|
||||
elem('EBMLVersion', '4286', 'uint'),
|
||||
elem('EBMLReadVersion', '42f7', 'uint'),
|
||||
elem('EBMLMaxIDLength', '42f2', 'uint'),
|
||||
elem('EBMLMaxSizeLength', '42f3', 'uint'),
|
||||
elem('DocType', '4282', 'str'),
|
||||
elem('DocTypeVersion', '4287', 'uint'),
|
||||
elem('DocTypeReadVersion', '4285', 'uint'),
|
||||
}),
|
||||
|
||||
elem('CRC32', 'bf', 'binary'),
|
||||
elem('Void', 'ec', 'binary'),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
=method define_matroska
|
||||
|
||||
Internal function that defines the Matroska-specific EBML grammar.
|
||||
|
||||
Must not be called from outside the package.
|
||||
|
||||
=cut
|
||||
sub define_matroska {
|
||||
local $ELEM_DEFINE_TYPE = 'MATROSKA';
|
||||
return (
|
||||
elem('Segment', '18538067', {
|
||||
elem('SeekHead*', '114d9b74', {
|
||||
elem('Seek*', '4dbb', {
|
||||
elem('SeekID', '53ab', 'ebml_id'),
|
||||
elem('SeekPosition', '53ac', 'uint'),
|
||||
}),
|
||||
}),
|
||||
|
||||
elem('Info*', '1549a966', {
|
||||
elem('SegmentUID', '73a4', 'binary'),
|
||||
elem('PrevUID', '3cb923', 'binary'),
|
||||
elem('NextUID', '3eb923', 'binary'),
|
||||
elem('TimecodeScale', '2ad7b1', 'uint'),
|
||||
elem('DateUTC', '4461', 'sint'),
|
||||
elem('Title', '7ba9', 'str'),
|
||||
elem('MuxingApp', '4d80', 'str'),
|
||||
elem('WritingApp', '5741', 'str'),
|
||||
elem('Duration', '4489', 'float'),
|
||||
}),
|
||||
|
||||
elem('Cluster*', '1f43b675', {
|
||||
elem('Timecode', 'e7', 'uint'),
|
||||
elem('BlockGroup*', 'a0', {
|
||||
elem('Block', 'a1', 'binary'),
|
||||
elem('BlockDuration', '9b', 'uint'),
|
||||
elem('ReferenceBlock*', 'fb', 'sint'),
|
||||
elem('DiscardPadding', '75A2', 'sint'),
|
||||
}),
|
||||
elem('SimpleBlock*', 'a3', 'binary'),
|
||||
}),
|
||||
|
||||
elem('Tracks*', '1654ae6b', {
|
||||
elem('TrackEntry*', 'ae', {
|
||||
elem('TrackNumber', 'd7', 'uint'),
|
||||
elem('TrackUID', '73c5', 'uint'),
|
||||
elem('TrackType', '83', 'uint'),
|
||||
elem('FlagEnabled', 'b9', 'uint'),
|
||||
elem('FlagDefault', '88', 'uint'),
|
||||
elem('FlagForced', '55aa', 'uint'),
|
||||
elem('FlagLacing', '9c', 'uint'),
|
||||
elem('MinCache', '6de7', 'uint'),
|
||||
elem('MaxCache', '6df8', 'uint'),
|
||||
elem('DefaultDuration', '23e383', 'uint'),
|
||||
elem('TrackTimecodeScale', '23314f', 'float'),
|
||||
elem('MaxBlockAdditionID', '55ee', 'uint'),
|
||||
elem('Name', '536e', 'str'),
|
||||
elem('Language', '22b59c', 'str'),
|
||||
elem('CodecID', '86', 'str'),
|
||||
elem('CodecPrivate', '63a2', 'binary'),
|
||||
elem('CodecName', '258688', 'str'),
|
||||
elem('CodecDecodeAll', 'aa', 'uint'),
|
||||
elem('CodecDelay', '56AA', 'uint'),
|
||||
elem('SeekPreRoll', '56BB', 'uint'),
|
||||
elem('Video', 'e0', {
|
||||
elem('FlagInterlaced', '9a', 'uint'),
|
||||
elem('PixelWidth', 'b0', 'uint'),
|
||||
elem('PixelHeight', 'ba', 'uint'),
|
||||
elem('DisplayWidth', '54b0', 'uint'),
|
||||
elem('DisplayHeight', '54ba', 'uint'),
|
||||
elem('DisplayUnit', '54b2', 'uint'),
|
||||
elem('FrameRate', '2383e3', 'float'),
|
||||
elem('ColourSpace', '2eb524', 'binary'),
|
||||
elem('StereoMode', '53b8', 'uint'),
|
||||
}),
|
||||
elem('Audio', 'e1', {
|
||||
elem('SamplingFrequency', 'b5', 'float'),
|
||||
elem('OutputSamplingFrequency', '78b5', 'float'),
|
||||
elem('Channels', '9f', 'uint'),
|
||||
elem('BitDepth', '6264', 'uint'),
|
||||
}),
|
||||
elem('ContentEncodings', '6d80', {
|
||||
elem('ContentEncoding*', '6240', {
|
||||
elem('ContentEncodingOrder', '5031', 'uint'),
|
||||
elem('ContentEncodingScope', '5032', 'uint'),
|
||||
elem('ContentEncodingType', '5033', 'uint'),
|
||||
elem('ContentCompression', '5034', {
|
||||
elem('ContentCompAlgo', '4254', 'uint'),
|
||||
elem('ContentCompSettings', '4255', 'binary'),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
elem('Cues', '1c53bb6b', {
|
||||
elem('CuePoint*', 'bb', {
|
||||
elem('CueTime', 'b3', 'uint'),
|
||||
elem('CueTrackPositions*', 'b7', {
|
||||
elem('CueTrack', 'f7', 'uint'),
|
||||
elem('CueClusterPosition', 'f1', 'uint'),
|
||||
elem('CueRelativePosition','f0', 'uint'),
|
||||
elem('CueDuration', 'b2', 'uint'),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
elem('Attachments', '1941a469', {
|
||||
elem('AttachedFile*', '61a7', {
|
||||
elem('FileDescription', '467e', 'str'),
|
||||
elem('FileName', '466e', 'str'),
|
||||
elem('FileMimeType', '4660', 'str'),
|
||||
elem('FileData', '465c', 'binary'),
|
||||
elem('FileUID', '46ae', 'uint'),
|
||||
}),
|
||||
}),
|
||||
|
||||
elem('Chapters', '1043a770', {
|
||||
elem('EditionEntry*', '45b9', {
|
||||
elem('EditionUID', '45bc', 'uint'),
|
||||
elem('EditionFlagHidden', '45bd', 'uint'),
|
||||
elem('EditionFlagDefault', '45db', 'uint'),
|
||||
elem('EditionFlagOrdered', '45dd', 'uint'),
|
||||
elem('ChapterAtom*', 'b6', {
|
||||
elem('ChapterUID', '73c4', 'uint'),
|
||||
elem('ChapterTimeStart', '91', 'uint'),
|
||||
elem('ChapterTimeEnd', '92', 'uint'),
|
||||
elem('ChapterFlagHidden', '98', 'uint'),
|
||||
elem('ChapterFlagEnabled', '4598', 'uint'),
|
||||
elem('ChapterSegmentUID', '6e67', 'binary'),
|
||||
elem('ChapterSegmentEditionUID', '6ebc', 'uint'),
|
||||
elem('ChapterDisplay*', '80', {
|
||||
elem('ChapString', '85', 'str'),
|
||||
elem('ChapLanguage*', '437c', 'str'),
|
||||
elem('ChapCountry*', '437e', 'str'),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
elem('Tags*', '1254c367', {
|
||||
elem('Tag*', '7373', {
|
||||
elem('Targets', '63c0', {
|
||||
elem('TargetTypeValue', '68ca', 'uint'),
|
||||
elem('TargetTrackUID', '63c5', 'uint'),
|
||||
elem('TargetEditionUID', '63c9', 'uint'),
|
||||
elem('TargetChapterUID', '63c4', 'uint'),
|
||||
elem('TargetAttachmentUID', '63c6', 'uint'),
|
||||
}),
|
||||
elem('SimpleTag*', '67c8', {
|
||||
elem('TagName', '45a3', 'str'),
|
||||
elem('TagLanguage', '447a', 'str'),
|
||||
elem('TagString', '4487', 'str'),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,331 +0,0 @@
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: a mid-level representation of an EBML element
|
||||
package Parse::Matroska::Element;
|
||||
|
||||
use Carp;
|
||||
use List::Util qw{first};
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Parse::Matroska::Reader;
|
||||
my $reader = Parse::Matroska::Reader->new($path);
|
||||
my $elem = $reader->read_element;
|
||||
|
||||
print "ID: $elem->{elid}\n";
|
||||
print "Name: $elem->{name}\n";
|
||||
print "Length: $elem->{content_len}\n";
|
||||
print "Type: $elem->{type}\n";
|
||||
print "Child count: ", scalar(@{$elem->all_children}), "\n";
|
||||
if ($elem->{type} eq 'sub') {
|
||||
while (my $chld = $elem->next_child) {
|
||||
print "Child Name: $chld->{name}\n";
|
||||
}
|
||||
} else {
|
||||
print "Value: ", $elem->get_value, "\n";
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Represents a single Matroska element as decoded by
|
||||
L<Parse::Matroska::Reader>. This is essentially a hash
|
||||
augmented with functions for delay-loading of binary
|
||||
values and children elements.
|
||||
|
||||
=head1 NOTE
|
||||
|
||||
The API of this module is not yet considered stable.
|
||||
|
||||
=attr elid
|
||||
|
||||
The EBML Element ID, suitable for passing to
|
||||
L<Parse::Matroska::Definitions/elem_by_hexid>.
|
||||
|
||||
=attr name
|
||||
|
||||
The EBML Element's name.
|
||||
|
||||
=attr type
|
||||
|
||||
The EBML Element's type. Can be C<uint>, C<sint>,
|
||||
C<float>, C<ebml_id>, C<str> or C<binary>. See L</value>
|
||||
for details.
|
||||
|
||||
Equivalent to
|
||||
C<elem_by_hexid($elem-E<gt>{value})-E<gt>{valtype}>.
|
||||
|
||||
=attr value
|
||||
|
||||
The EBML Element's value. Should be obtained through
|
||||
L</get_value>.
|
||||
|
||||
Is an unicode string if the L</type> is C<str>, that is,
|
||||
the string has already been decoded by L<Encode/decode>.
|
||||
|
||||
Is C<undef> if the L</type> is C<binary> and the contents
|
||||
were delay-loaded and not yet read. L</get_value> will
|
||||
do the delayed load if needed.
|
||||
|
||||
Is an arrayref if the L</type> is C<sub>, containing
|
||||
the children nodes that were already loaded.
|
||||
|
||||
Is a hashref if the L</type> is C<ebml_id>, containing
|
||||
the referred element's information as defined in
|
||||
L<Parse::Matroska::Definitions>. Calling
|
||||
C<elem_by_hexid($elem-E<gt>{value}-E<gt>{elid})> will
|
||||
return the same object as $elem->{value}.
|
||||
|
||||
=attr full_len
|
||||
|
||||
The entire length of this EBML Element, including
|
||||
the header's.
|
||||
|
||||
=attr size_len
|
||||
|
||||
The length of the size marker. Used when calculating
|
||||
L</full_len> from L</content_len>
|
||||
|
||||
=attr content_len
|
||||
|
||||
The length of the contents of this EBML Element,
|
||||
which excludes the header.
|
||||
|
||||
=attr reader
|
||||
|
||||
A weakened reference to the associated
|
||||
L<Parse::Matroska::Reader>.
|
||||
|
||||
=method new(%hash)
|
||||
|
||||
Creates a new Element initialized with the hash
|
||||
given as argument.
|
||||
|
||||
=cut
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->initialize(@_);
|
||||
return $self;
|
||||
}
|
||||
|
||||
=method initialize(%hash)
|
||||
|
||||
Called by L</new> on initialization.
|
||||
|
||||
=cut
|
||||
sub initialize {
|
||||
my ($self, %args) = @_;
|
||||
for (keys %args) {
|
||||
$self->{$_} = $args{$_};
|
||||
}
|
||||
$self->{depth} = 0 unless $self->{depth};
|
||||
}
|
||||
|
||||
=method skip
|
||||
|
||||
Called by the user to ignore the contents of this EBML node.
|
||||
Needed when ignoring the children of a node.
|
||||
|
||||
=cut
|
||||
sub skip {
|
||||
my ($self) = @_;
|
||||
my $reader = $self->{reader};
|
||||
return unless $reader; # we don't have to skip if there's no reader
|
||||
my $pos = $reader->getpos;
|
||||
croak "Too late to skip, reads were already done"
|
||||
if $pos ne $self->{data_pos};
|
||||
$reader->skip($self->{content_len});
|
||||
}
|
||||
|
||||
=method get_value($keep_bin)
|
||||
|
||||
Returns the value contained by this EBML element.
|
||||
|
||||
If the element has children, returns an arrayref to
|
||||
the children elements that were already encountered.
|
||||
|
||||
If the element's type is C<binary> and the value was
|
||||
delay-loaded, does the reading now.
|
||||
|
||||
If $keep_bin is true, the delay-loaded data is kept
|
||||
as the L</value>, otherwise, further calls to
|
||||
C<get_value> will reread the data from the L</reader>.
|
||||
|
||||
=cut
|
||||
sub get_value {
|
||||
my ($self, $keep_bin) = @_;
|
||||
|
||||
return undef if $self->{type} eq 'skip';
|
||||
return $self->{value} if $self->{value};
|
||||
|
||||
my $reader = $self->{reader} or
|
||||
croak "The associated Reader has been deleted";
|
||||
|
||||
# delay-loaded 'binary'
|
||||
if ($self->{type} eq 'binary') {
|
||||
croak "Cannot seek in the current Reader" unless $self->{data_pos};
|
||||
# seek to the data position...
|
||||
$reader->setpos($self->{data_pos});
|
||||
# read the data, keeping it in value if requested
|
||||
if ($keep_bin) {
|
||||
$self->{value} = $reader->readlen($self->{content_len});
|
||||
return $self->{value};
|
||||
} else {
|
||||
return $reader->readlen($self->{content_len});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
=method next_child($read_bin)
|
||||
|
||||
Builtin iterator; reads and returns the next child element.
|
||||
Always returns undef if the type isn't C<sub>.
|
||||
|
||||
Returns undef at the end of the iterator and resets itself to
|
||||
point to the first element; so calling L</next_child($read_bin)>
|
||||
after the iterator returned C<undef> will return the first child.
|
||||
|
||||
The optional C<$read_bin> parameter has the children elements
|
||||
not delay-load their value if their type is C<binary>.
|
||||
|
||||
If all children elements have already been read, return
|
||||
each element in-order as would be given by
|
||||
L</all_children($recurse,$read_bin)>.
|
||||
|
||||
=cut
|
||||
sub next_child {
|
||||
my ($self, $read_bin) = @_;
|
||||
return unless $self->{type} eq 'sub';
|
||||
|
||||
if ($self->{_all_children_read}) {
|
||||
my $idx = $self->{_last_child} ||= 0;
|
||||
if ($idx == @{$self->{value}}) {
|
||||
# reset the iterator, returning undef once
|
||||
$self->{_last_child} = 0;
|
||||
return;
|
||||
}
|
||||
my $ret = $self->{value}->[$idx];
|
||||
|
||||
++$idx;
|
||||
$self->{_last_child} = $idx;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
my $len = defined $self->{remaining_len}
|
||||
? $self->{remaining_len}
|
||||
: $self->{content_len};
|
||||
|
||||
if ($len == 0) {
|
||||
# we've read all children; switch into $self->{value} iteration mode
|
||||
$self->{_all_children_read} = 1;
|
||||
# return undef since the iterator will reset
|
||||
return;
|
||||
}
|
||||
|
||||
$self->{pos_offset} ||= 0;
|
||||
my $pos = $self->{data_pos};
|
||||
my $reader = $self->{reader} or croak "The associated reader has been deleted";
|
||||
$reader->setpos($pos);
|
||||
$reader->{fh}->seek($self->{pos_offset}, 1) if $pos;
|
||||
|
||||
my $chld = $reader->read_element($read_bin);
|
||||
return undef unless defined $chld;
|
||||
$self->{pos_offset} += $chld->{full_len};
|
||||
|
||||
$self->{remaining_len} = $len - $chld->{full_len};
|
||||
|
||||
if ($self->{remaining_len} < 0) {
|
||||
croak "Child elements consumed $self->{remaining_len} more bytes than parent $self->{name} contained";
|
||||
}
|
||||
|
||||
$chld->{depth} = $self->{depth} + 1;
|
||||
$self->{value} ||= [];
|
||||
|
||||
push @{$self->{value}}, $chld;
|
||||
|
||||
return $chld;
|
||||
}
|
||||
|
||||
=method all_children($recurse,$read_bin)
|
||||
|
||||
Calls L</populate_children($recurse,$read_bin)> on self
|
||||
and returns an arrayref with the children nodes.
|
||||
|
||||
Both C<$recurse> and C<$read_bin> are optional and default
|
||||
to false.
|
||||
|
||||
=cut
|
||||
sub all_children {
|
||||
my ($self, $recurse, $read_bin) = @_;
|
||||
$self->populate_children($recurse, $read_bin);
|
||||
return $self->{value};
|
||||
}
|
||||
|
||||
=method children_by_name($name)
|
||||
|
||||
Searches in the already read children elements for all
|
||||
elements with the EBML name C<$name>. Returns an array
|
||||
containing all found elements. On scalar context,
|
||||
returns only the first element found.
|
||||
|
||||
Croaks if the element's C<type> isn't C<sub>.
|
||||
|
||||
=cut
|
||||
sub children_by_name {
|
||||
my ($self, $name) = @_;
|
||||
return unless defined wantarray; # don't do work if work isn't wanted
|
||||
croak "Element can't have children" unless $self->{type} eq 'sub';
|
||||
|
||||
my @found = grep { $_->{name} eq $name } @{$self->{value}};
|
||||
return @found if wantarray; # list
|
||||
return shift @found if defined wantarray; # scalar
|
||||
}
|
||||
|
||||
=method populate_children($recurse,$read_bin)
|
||||
|
||||
Populates the internal array of children elements, that is,
|
||||
requests that the associated L<Matroska::Parser::Reader> reads
|
||||
all children elements. Returns itself.
|
||||
|
||||
Returns false if the element's C<type> isn't C<sub>.
|
||||
|
||||
If C<$recurse> is provided and is true, the method will call
|
||||
itself in the children elements with the same parameters it
|
||||
received; this will build a full EBML tree.
|
||||
|
||||
If C<$read_bin> is provided and is true, disables delay-loading
|
||||
of the contents of C<binary>-type nodes, reading the contents
|
||||
to memory.
|
||||
|
||||
If both C<$recurse> and C<$read_bin> are true, entire EBML trees
|
||||
can be loaded without requiring seeks, thus behaving correctly
|
||||
on unseekable streams. If C<$read_bin> is false, the entire EBML
|
||||
tree is still loaded, but calling L</get_value> on C<binary>-type
|
||||
nodes will produce an error on unseekable streams.
|
||||
|
||||
=cut
|
||||
sub populate_children {
|
||||
my ($self, $recurse, $read_bin) = @_;
|
||||
|
||||
return unless $self->{type} eq 'sub';
|
||||
|
||||
if (@{$self->{value}} && $recurse) {
|
||||
# only recurse
|
||||
foreach (@{$self->{value}}) {
|
||||
$_->populate_children($recurse, $read_bin);
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
while (my $chld = $self->next_child($read_bin)) {
|
||||
$chld->populate_children($recurse, $read_bin) if $recurse;
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,426 +0,0 @@
|
||||
use 5.008;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: a low-level reader for EBML files
|
||||
package Parse::Matroska::Reader;
|
||||
|
||||
use Parse::Matroska::Definitions qw{elem_by_hexid};
|
||||
use Parse::Matroska::Element;
|
||||
|
||||
use Carp;
|
||||
use Scalar::Util qw{openhandle weaken};
|
||||
use IO::Handle;
|
||||
use IO::File;
|
||||
use List::Util qw{first};
|
||||
use Encode;
|
||||
|
||||
use constant BIGINT_TRY => 'Pari,GMP,FastCalc';
|
||||
use Math::BigInt try => BIGINT_TRY;
|
||||
use Math::BigRat try => BIGINT_TRY;
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Parse::Matroska::Reader;
|
||||
my $reader = Parse::Matroska::Reader->new($path);
|
||||
$reader->close;
|
||||
$reader->open(\$string_with_matroska_data);
|
||||
|
||||
my $elem = $reader->read_element;
|
||||
print "Element ID: $elem->{elid}\n";
|
||||
print "Element name: $elem->{name}\n";
|
||||
if ($elem->{type} ne 'sub') {
|
||||
print "Element value: $elem->get_value\n";
|
||||
} else {
|
||||
while (my $child = $elem->next_child) {
|
||||
print "Child element: $child->{name}\n";
|
||||
}
|
||||
}
|
||||
$reader->close;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Reads EBML data, which is used in Matroska files.
|
||||
This is a low-level reader which is meant to be used as a backend
|
||||
for higher level readers. TODO: write the high level readers :)
|
||||
|
||||
=head1 NOTE
|
||||
|
||||
The API of this module is not yet considered stable.
|
||||
|
||||
=method new
|
||||
|
||||
Creates a new reader.
|
||||
Calls L</open($arg)> with its arguments if provided.
|
||||
|
||||
=cut
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = {};
|
||||
bless $self, $class;
|
||||
|
||||
$self->open(@_) if @_;
|
||||
return $self;
|
||||
}
|
||||
|
||||
=method open($arg)
|
||||
|
||||
Creates the internal filehandle. The argument can be:
|
||||
|
||||
=for :list
|
||||
* An open filehandle or L<IO::Handle> object.
|
||||
The filehandle is not C<dup()>ed, so calling L</close> in this
|
||||
object will close the given filehandle as well.
|
||||
* A scalar containing a path to a file.
|
||||
* On perl v5.14 or newer, a scalarref pointing to EBML data.
|
||||
For similar functionality in older perls, give an L<IO::String> object
|
||||
or the handle to an already C<open>ed scalarref.
|
||||
|
||||
=cut
|
||||
sub open {
|
||||
my ($self, $arg) = @_;
|
||||
$self->{fh} = openhandle($arg) || IO::File->new($arg, "<:raw")
|
||||
or croak "Can't open $arg: $!";
|
||||
}
|
||||
|
||||
=method close
|
||||
|
||||
Closes the internal filehandle.
|
||||
|
||||
=cut
|
||||
sub close {
|
||||
my ($self) = @_;
|
||||
$self->{fh}->close;
|
||||
delete $self->{fh};
|
||||
}
|
||||
|
||||
# equivalent to $self->readlen(1), possibly faster
|
||||
sub _getc {
|
||||
my ($self) = @_;
|
||||
my $c = $self->{fh}->getc;
|
||||
croak "Can't do read of length 1: $!" if !defined $c && $!;
|
||||
return $c;
|
||||
}
|
||||
|
||||
=method readlen($length)
|
||||
|
||||
Reads C<$length> bytes from the internal filehandle.
|
||||
|
||||
=cut
|
||||
sub readlen {
|
||||
my ($self, $len) = @_;
|
||||
my $data;
|
||||
my $readlen = $self->{fh}->read($data, $len);
|
||||
croak "Can't do read of length $len: $!"
|
||||
unless defined $readlen;
|
||||
return $data;
|
||||
}
|
||||
|
||||
# converts a byte string into an integer
|
||||
# we do so by converting the integer into a hex string (big-endian)
|
||||
# and then reading the hex-string into an integer
|
||||
sub _bin2int($) {
|
||||
my ($bin) = @_;
|
||||
# if the length is larger than 3
|
||||
# the resulting integer might be larger than INT_MAX
|
||||
if (length($bin) > 3) {
|
||||
return Math::BigInt->from_hex(unpack("H*", $bin));
|
||||
}
|
||||
return hex(unpack("H*", $bin));
|
||||
}
|
||||
|
||||
# creates a floating-point number with the given mantissa and exponent
|
||||
sub _ldexp {
|
||||
my ($mantissa, $exponent) = @_;
|
||||
my $r = new Math::BigRat($mantissa);
|
||||
return $r * Math::BigRat->new(2)**$exponent;
|
||||
}
|
||||
|
||||
# NOTE: the read_* functions are hard to read because they're ports
|
||||
# of even harder to read python functions.
|
||||
# TODO: make them readable
|
||||
|
||||
=method read_id
|
||||
|
||||
Reads an EBML ID atom in hexadecimal string format, suitable
|
||||
for passing to L<Parse::Matroska::Definitions/elem_by_hexid($id)>.
|
||||
|
||||
=cut
|
||||
sub read_id {
|
||||
my ($self) = @_;
|
||||
my $t = $self->_getc;
|
||||
return undef unless defined $t;
|
||||
my $i = 0;
|
||||
my $mask = 1<<7;
|
||||
|
||||
if (ord($t) == 0) {
|
||||
croak "Matroska Syntax error: first byte of ID was \\0"
|
||||
}
|
||||
until (ord($t) & $mask) {
|
||||
++$i;
|
||||
$mask >>= 1;
|
||||
}
|
||||
# return hex string of the bytes we just read
|
||||
return unpack "H*", ($t . $self->readlen($i));
|
||||
}
|
||||
|
||||
=method read_size
|
||||
|
||||
Reads an EBML Data Size atom, which immediately follows
|
||||
an EBML ID atom.
|
||||
|
||||
This returns an array consisting of:
|
||||
|
||||
=for :list
|
||||
0. The length of the Data Size atom.
|
||||
1. The value encoded in the Data Size atom, which is the length of all the data following it.
|
||||
|
||||
=cut
|
||||
sub read_size {
|
||||
my ($self) = @_;
|
||||
my $t = $self->_getc;
|
||||
my $i = 0;
|
||||
my $mask = 1<<7;
|
||||
|
||||
if (ord($t) == 0) {
|
||||
croak "Matroska Syntax error: first byte of data size was \\0"
|
||||
}
|
||||
until (ord($t) & $mask) {
|
||||
++$i;
|
||||
$mask >>= 1;
|
||||
}
|
||||
$t = $t & chr($mask-1); # strip length bits (keep only significant bits)
|
||||
return ($i+1, _bin2int $t . $self->readlen($i));
|
||||
}
|
||||
|
||||
=method read_str($length)
|
||||
|
||||
Reads a string of length C<$length> bytes from the internal filehandle.
|
||||
The string is already L<Encode/decode>d from C<UTF-8>, which is the
|
||||
standard Matroska string encoding.
|
||||
|
||||
=cut
|
||||
{
|
||||
my $utf8 = find_encoding("UTF-8");
|
||||
sub read_str {
|
||||
my ($self, $length) = @_;
|
||||
return $utf8->decode($self->readlen($length));
|
||||
}
|
||||
}
|
||||
|
||||
=method read_uint($length)
|
||||
|
||||
Reads an unsigned integer of length C<$length> bytes
|
||||
from the internal filehandle.
|
||||
|
||||
Returns a L<Math::BigInt> object if C<$length> is greater
|
||||
than 4.
|
||||
|
||||
=cut
|
||||
sub read_uint {
|
||||
my ($self, $length) = @_;
|
||||
return _bin2int $self->readlen($length);
|
||||
}
|
||||
|
||||
=method read_sint($length)
|
||||
|
||||
Reads a signed integer of length C<$length> bytes
|
||||
from the internal filehandle.
|
||||
|
||||
Returns a L<Math::BigInt> object if C<$length> is greater
|
||||
than 4.
|
||||
|
||||
=cut
|
||||
sub read_sint {
|
||||
my ($self, $length) = @_;
|
||||
my $i = $self->read_uint($length);
|
||||
|
||||
# Apply 2's complement to the unsigned int
|
||||
my $mask = int(2 ** ($length * 8 - 1));
|
||||
# if the most significant bit is set...
|
||||
if ($i & $mask) {
|
||||
# subtract the MSB twice
|
||||
$i -= 2 * $mask;
|
||||
}
|
||||
return $i;
|
||||
}
|
||||
|
||||
=method read_float($length)
|
||||
|
||||
Reads an IEEE floating point number of length C<$length>
|
||||
bytes from the internal filehandle.
|
||||
|
||||
Only lengths C<4> and C<8> are supported (C C<float> and C<double>).
|
||||
|
||||
=cut
|
||||
{
|
||||
my $b1 = new Math::BigInt 1;
|
||||
|
||||
sub read_float {
|
||||
my ($self, $length) = @_;
|
||||
my $i = new Math::BigInt $self->read_uint($length)->bstr;
|
||||
my $f;
|
||||
|
||||
# These evil expressions reinterpret an unsigned int as IEEE binary floats
|
||||
if ($length == 4) {
|
||||
$f = _ldexp(($i & ((1<<23) - 1)) + (1<<23), ($i>>23 & ((1<<8) - 1)) - 150);
|
||||
$f = -$f if $i & ($b1<<31);
|
||||
} elsif ($length == 8) {
|
||||
$f = _ldexp(($i & (($b1<<52) - 1)) + ($b1<<52), ($i>>52 & ((1<<12) - 1)) - 1075);
|
||||
$f = -$f if $i & ($b1<<63);
|
||||
} else {
|
||||
croak "Matroska Syntax error: unsupported IEEE float byte size $length";
|
||||
}
|
||||
|
||||
return $f;
|
||||
}
|
||||
}
|
||||
|
||||
=method read_ebml_id($length)
|
||||
|
||||
Reads an EBML ID when it's encoded as the data inside another
|
||||
EBML element, that is, when the enclosing element's C<type> is
|
||||
C<ebml_id>.
|
||||
|
||||
This returns a hashref with the EBML element description as
|
||||
defined in L<Parse::Matroska::Definitions>.
|
||||
|
||||
=cut
|
||||
sub read_ebml_id {
|
||||
my ($self, $length) = @_;
|
||||
return elem_by_hexid(unpack("H*", $self->readlen($length)));
|
||||
}
|
||||
|
||||
=method skip($length)
|
||||
|
||||
Skips C<$length> bytes in the internal filehandle.
|
||||
|
||||
=cut
|
||||
sub skip {
|
||||
my ($self, $len) = @_;
|
||||
return if $self->{fh}->can('seek') && $self->{fh}->seek($len, 1);
|
||||
$self->readlen($len);
|
||||
return;
|
||||
}
|
||||
|
||||
=method getpos
|
||||
|
||||
Wrapper for L<IO::Seekable/$io-E<gt>getpos> in the internal filehandle.
|
||||
|
||||
Returns undef if the internal filehandle can't C<getpos>.
|
||||
|
||||
=cut
|
||||
sub getpos {
|
||||
my ($self) = @_;
|
||||
return undef unless $self->{fh}->can('getpos');
|
||||
return $self->{fh}->getpos;
|
||||
}
|
||||
|
||||
=method setpos($pos)
|
||||
|
||||
Wrapper for L<IO::Seekable/$io-E<gt>setpos> in the internal filehandle.
|
||||
|
||||
Returns C<undef> if the internal filehandle can't C<setpos>.
|
||||
|
||||
Croaks if C<setpos> does not seek to the requested position,
|
||||
that is, if calling C<getpos> does not yield the same object
|
||||
as the C<$pos> argument.
|
||||
|
||||
=cut
|
||||
sub setpos {
|
||||
my ($self, $pos) = @_;
|
||||
return undef unless $pos && $self->{fh}->can('setpos');
|
||||
|
||||
my $ret = $self->{fh}->setpos($pos);
|
||||
croak "Cannot seek to correct position"
|
||||
unless $self->getpos eq $pos;
|
||||
return $ret;
|
||||
}
|
||||
|
||||
=method read_element($read_bin)
|
||||
|
||||
Reads a full EBML element from the internal filehandle.
|
||||
|
||||
Returns a L<Parse::Matroska::Element> object initialized with
|
||||
the read data. If C<read_bin> is not present or is false, will
|
||||
delay-load the contents of C<binary> type elements, that is,
|
||||
they will only be loaded when calling C<get_value> on the
|
||||
returned L<Parse::Matroska::Element> object.
|
||||
|
||||
Does not read the children of the element if its type is
|
||||
C<sub>. Look into the L<Parse::Matroska::Element> interface
|
||||
for details in how to read children elements.
|
||||
|
||||
Pass a true C<$read_bin> if the stream being read is not
|
||||
seekable (C<getpos> is undef) and the contents of C<binary>
|
||||
elements is desired, otherwise seeking errors or internal
|
||||
filehandle corruption might occur.
|
||||
|
||||
=cut
|
||||
sub read_element {
|
||||
my ($self, $read_bin) = @_;
|
||||
return undef if $self->{fh}->eof;
|
||||
|
||||
my $elem_pos = $self->getpos;
|
||||
|
||||
my $elid = $self->read_id;
|
||||
my $elem_def = elem_by_hexid($elid);
|
||||
my ($size_len, $content_len) = $self->read_size;
|
||||
my $full_len = length($elid)/2 + $size_len + $content_len;
|
||||
|
||||
my $elem = Parse::Matroska::Element->new(
|
||||
elid => $elid,
|
||||
name => $elem_def && $elem_def->{name},
|
||||
type => $elem_def && $elem_def->{valtype},
|
||||
size_len => $size_len,
|
||||
content_len => $content_len,
|
||||
full_len => $full_len,
|
||||
reader => $self,
|
||||
elem_pos => $elem_pos,
|
||||
data_pos => $self->getpos,
|
||||
);
|
||||
weaken($elem->{reader});
|
||||
|
||||
if (defined $elem_def) {
|
||||
if ($elem->{type} eq 'sub') {
|
||||
$elem->{value} = [];
|
||||
} elsif ($elem->{type} eq 'str') {
|
||||
$elem->{value} = $self->read_str($content_len);
|
||||
} elsif ($elem->{type} eq 'ebml_id') {
|
||||
$elem->{value} = $self->read_ebml_id($content_len);
|
||||
} elsif ($elem->{type} eq 'uint') {
|
||||
$elem->{value} = $self->read_uint($content_len);
|
||||
} elsif ($elem->{type} eq 'sint') {
|
||||
$elem->{value} = $self->read_sint($content_len);
|
||||
} elsif ($elem->{type} eq 'float') {
|
||||
$elem->{value} = $self->read_float($content_len);
|
||||
} elsif ($elem->{type} eq 'skip') {
|
||||
$self->skip($content_len);
|
||||
} elsif ($elem->{type} eq 'binary') {
|
||||
if ($read_bin) {
|
||||
$elem->{value} = $self->readlen($content_len);
|
||||
} else {
|
||||
$self->skip($content_len);
|
||||
}
|
||||
} else {
|
||||
die "Matroska Definition error: type $elem->{valtype} unknown"
|
||||
}
|
||||
} else {
|
||||
$self->skip($content_len);
|
||||
}
|
||||
return $elem;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 CAVEATS
|
||||
|
||||
Children elements have to be processed as soon as an element
|
||||
with children is found, or their children ignored with
|
||||
L<Parse::Matroska::Element/skip>. Not doing so doesn't cause
|
||||
errors but results in an invalid structure, with constant C<0>
|
||||
depth.
|
||||
|
||||
To work correctly in unseekable streams, either the contents
|
||||
of C<binary>-type elements has to be ignored or the C<read_bin>
|
||||
flag to C<read_element> has to be true.
|
||||
@@ -1,37 +0,0 @@
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# ABSTRACT: internally-used helper functions
|
||||
package Parse::Matroska::Utils;
|
||||
|
||||
use Exporter;
|
||||
our @ISA = qw{Exporter};
|
||||
our @EXPORT_OK = qw{uniq uncamelize};
|
||||
|
||||
=method uniq(@array)
|
||||
|
||||
The same as L<List::MoreUtils/"uniq LIST">.
|
||||
Included to avoid depending on it since it's
|
||||
not a core module.
|
||||
|
||||
=cut
|
||||
sub uniq(@) {
|
||||
my %seen;
|
||||
return grep { !$seen{$_}++ } @_;
|
||||
}
|
||||
|
||||
=method uncamelize($string)
|
||||
|
||||
Converts a "StringLikeTHIS" into a
|
||||
"string_like_this".
|
||||
|
||||
=cut
|
||||
sub uncamelize($) {
|
||||
local $_ = shift;
|
||||
# lc followed by UC: lc_UC
|
||||
s/(?<=[a-z])([A-Z])/_\L$1/g;
|
||||
# UC followed by two lc: _UClclc
|
||||
s/([A-Z])(?=[a-z]{2})/_\L$1/g;
|
||||
# strip leading _ that the second regexp might add; lowercase all
|
||||
s/^_//; lc
|
||||
}
|
||||
@@ -7,6 +7,13 @@
|
||||
-- Add at most 5000 * 2 files when starting a file (before + after).
|
||||
MAXENTRIES = 5000
|
||||
|
||||
local options = require 'mp.options'
|
||||
|
||||
o = {
|
||||
disabled = false
|
||||
}
|
||||
options.read_options(o)
|
||||
|
||||
function Set (t)
|
||||
local set = {}
|
||||
for _, v in pairs(t) do set[v] = true end
|
||||
@@ -49,7 +56,7 @@ end
|
||||
function find_and_add_entries()
|
||||
local path = mp.get_property("path", "")
|
||||
local dir, filename = mputils.split_path(path)
|
||||
if #dir == 0 then
|
||||
if o.disabled or #dir == 0 then
|
||||
return
|
||||
end
|
||||
local pl_count = mp.get_property_number("playlist-count", 1)
|
||||
|
||||
@@ -6,9 +6,17 @@
|
||||
|
||||
local utils = require("mp.utils")
|
||||
|
||||
for i,name in ipairs(mp.get_property_native("property-list")) do
|
||||
function observe(name)
|
||||
mp.observe_property(name, "native", function(name, val)
|
||||
print("property '" .. name .. "' changed to '" ..
|
||||
utils.to_string(val) .. "'")
|
||||
end)
|
||||
end
|
||||
|
||||
for i,name in ipairs(mp.get_property_native("property-list")) do
|
||||
observe(name)
|
||||
end
|
||||
|
||||
for i,name in ipairs(mp.get_property_native("options")) do
|
||||
observe("options/" .. name)
|
||||
end
|
||||
@@ -1,169 +0,0 @@
|
||||
#! /usr/bin/env perl
|
||||
|
||||
# Generate C definitions for parsing Matroska files.
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/lib";
|
||||
use Parse::Matroska::Definitions;
|
||||
use Parse::Matroska::Reader;
|
||||
|
||||
use Getopt::Long;
|
||||
use List::Util qw{max};
|
||||
|
||||
my @global_elem_list = @Parse::Matroska::Definitions::global_elem_list;
|
||||
|
||||
Getopt::Long::Configure(qw{auto_version auto_help});
|
||||
my %opt;
|
||||
GetOptions(\%opt,
|
||||
"generate-header",
|
||||
"generate-definitions",
|
||||
"full",
|
||||
);
|
||||
|
||||
if ($opt{"generate-header"}) {
|
||||
generate_c_header();
|
||||
} elsif ($opt{"generate-definitions"}) {
|
||||
generate_c_definitions();
|
||||
} else {
|
||||
for (@ARGV) {
|
||||
my $reader = Parse::Matroska::Reader->new($_ eq '-' ? \*STDIN : $_) or die $!;
|
||||
while (my $elem = $reader->read_element($_ eq '-')) {
|
||||
process_elem($elem, $_ eq '-');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generate declarations for libmpdemux/ebml_types.h
|
||||
sub generate_c_header {
|
||||
print "/* Generated by TOOLS/matroska.pl, do not edit manually */\n\n";
|
||||
|
||||
# Write a #define for the ElementID of each known element
|
||||
for my $el (@global_elem_list) {
|
||||
printf "#define %-40s 0x%s\n", $el->{definename}, $el->{elid};
|
||||
}
|
||||
print "\n";
|
||||
|
||||
# Define a struct for each ElementID that has child elements
|
||||
for my $el (@global_elem_list) {
|
||||
next unless $el->{subelements};
|
||||
print "\nstruct $el->{structname} {\n";
|
||||
|
||||
# Figure out the length of the longest variable name
|
||||
# Used for pretty-printing in the next step
|
||||
my $l = max(map { length $_->{valname} } values %{$el->{subelements}});
|
||||
|
||||
# Output each variable, with pointers for array (multiple) elements
|
||||
for my $subel (sort { $a->{definename} cmp $b->{definename} } values %{$el->{subelements}}) {
|
||||
printf " %-${l}s %s%s;\n",
|
||||
$subel->{valname}, $subel->{multiple}?'*':' ', $subel->{fieldname};
|
||||
}
|
||||
print "\n";
|
||||
|
||||
# Output a counter variable for each element
|
||||
# (presence/absence for scalars, item count for arrays)
|
||||
for my $subel (sort values %{$el->{subelements}}) {
|
||||
print " int n_$subel->{fieldname};\n"
|
||||
}
|
||||
print "};\n";
|
||||
}
|
||||
print "\n";
|
||||
|
||||
# Output extern references for ebml_elem_desc structs for each of the elements
|
||||
# These are defined by generate_c_definitions
|
||||
for my $el (@global_elem_list) {
|
||||
next unless $el->{subelements};
|
||||
print "extern const struct ebml_elem_desc $el->{structname}_desc;\n";
|
||||
}
|
||||
print "\n";
|
||||
|
||||
# Output the max number of sub-elements a known element might have
|
||||
printf "#define MAX_EBML_SUBELEMENTS %d\n",
|
||||
max(map { scalar keys %{$_->{subelements}} }
|
||||
grep { $_->{subelements} } @global_elem_list);
|
||||
}
|
||||
|
||||
# Generate definitions for libmpdemux/ebml_defs.c
|
||||
sub generate_c_definitions {
|
||||
print "/* Generated by TOOLS/matroska.pl, do not edit manually */\n\n";
|
||||
# ebml_defs.c uses macros declared in ebml.c
|
||||
for my $el (@global_elem_list) {
|
||||
print "\n";
|
||||
if ($el->{subelements}) {
|
||||
# set N for the next macros
|
||||
print "#define N $el->{fieldname}\n";
|
||||
|
||||
# define a struct ebml_$N_desc and gets ready to define fields
|
||||
# this secretly opens two scopes; hence the }}; at the end
|
||||
print "E_S(\"$el->{name}\", ".scalar(keys %{$el->{subelements}}).")\n";
|
||||
|
||||
# define a field for each subelement
|
||||
# also does lots of macro magic, but doesn't open a scope
|
||||
for my $subel (sort { $a->{definename} cmp $b->{definename} } values %{$el->{subelements}}) {
|
||||
print "F($subel->{definename}, $subel->{fieldname}, ".
|
||||
($subel->{multiple}?'1':'0').")\n";
|
||||
}
|
||||
# close the struct
|
||||
print "}};\n";
|
||||
|
||||
# unset N since we've used it
|
||||
print "#undef N\n";
|
||||
} else {
|
||||
print "E(\"$el->{name}\", $el->{fieldname}, $el->{ebmltype})\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub repr {
|
||||
my @ret;
|
||||
foreach (@_) {
|
||||
if (/'/) {
|
||||
s/"/\\"/g;
|
||||
push @ret, "\"$_\"";
|
||||
} else {
|
||||
push @ret, "'$_'";
|
||||
}
|
||||
}
|
||||
return @ret if wantarray;
|
||||
return pop @ret if defined wantarray;
|
||||
return;
|
||||
}
|
||||
|
||||
sub process_elem {
|
||||
my ($elem, $read_bin) = @_;
|
||||
unless ($opt{full}) {
|
||||
if ($elem->{name} eq 'Cluster' || $elem->{name} eq 'Cues') {
|
||||
$elem->skip;
|
||||
return;
|
||||
}
|
||||
}
|
||||
die unless $elem;
|
||||
|
||||
if ($elem->{type} ne 'skip') {
|
||||
print "$elem->{depth} $elem->{elid} $elem->{name} size: $elem->{content_len} value: ";
|
||||
}
|
||||
|
||||
if ($elem->{type} eq 'sub') {
|
||||
print "subelements:\n";
|
||||
while (my $chld = $elem->next_child($read_bin)) {
|
||||
process_elem($chld);
|
||||
}
|
||||
} elsif ($elem->{type} eq 'binary') {
|
||||
my $t = "<skipped $elem->{content_len} bytes>";
|
||||
if ($elem->{content_len} < 20) {
|
||||
$t = unpack "H*", $elem->get_value;
|
||||
}
|
||||
print "binary $t\n";
|
||||
delete $elem->{value};
|
||||
} elsif ($elem->{type} eq 'ebml_id') {
|
||||
print "binary $elem->{value}->{elid} (".($elem->{value}->{name}||"UNKNOWN").")\n";
|
||||
} elsif ($elem->{type} eq 'skip') {
|
||||
# skip
|
||||
} elsif ($elem->{type} eq 'str') {
|
||||
print "string ". repr($elem->get_value) . "\n";
|
||||
} else {
|
||||
print "$elem->{type} ". $elem->get_value ."\n";
|
||||
}
|
||||
}
|
||||
Executable
+466
@@ -0,0 +1,466 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Generate C definitions for parsing Matroska files.
|
||||
Can also be used to directly parse Matroska files and display their contents.
|
||||
"""
|
||||
|
||||
#
|
||||
# This file is part of MPlayer.
|
||||
#
|
||||
# MPlayer is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# MPlayer is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
#
|
||||
|
||||
# for compatibility with Python 2.x
|
||||
from __future__ import print_function
|
||||
|
||||
elements_ebml = (
|
||||
'EBML, 1a45dfa3, sub', (
|
||||
'EBMLVersion, 4286, uint',
|
||||
'EBMLReadVersion, 42f7, uint',
|
||||
'EBMLMaxIDLength, 42f2, uint',
|
||||
'EBMLMaxSizeLength, 42f3, uint',
|
||||
'DocType, 4282, str',
|
||||
'DocTypeVersion, 4287, uint',
|
||||
'DocTypeReadVersion, 4285, uint',
|
||||
),
|
||||
|
||||
'CRC32, bf, binary',
|
||||
'Void, ec, binary',
|
||||
)
|
||||
|
||||
elements_matroska = (
|
||||
'Segment, 18538067, sub', (
|
||||
|
||||
'SeekHead*, 114d9b74, sub', (
|
||||
'Seek*, 4dbb, sub', (
|
||||
'SeekID, 53ab, ebml_id',
|
||||
'SeekPosition, 53ac, uint',
|
||||
),
|
||||
),
|
||||
|
||||
'Info*, 1549a966, sub', (
|
||||
'SegmentUID, 73a4, binary',
|
||||
'PrevUID, 3cb923, binary',
|
||||
'NextUID, 3eb923, binary',
|
||||
'TimecodeScale, 2ad7b1, uint',
|
||||
'DateUTC, 4461, sint',
|
||||
'Title, 7ba9, str',
|
||||
'MuxingApp, 4d80, str',
|
||||
'WritingApp, 5741, str',
|
||||
'Duration, 4489, float',
|
||||
),
|
||||
|
||||
'Cluster*, 1f43b675, sub', (
|
||||
'Timecode, e7, uint',
|
||||
'BlockGroup*, a0, sub', (
|
||||
'Block, a1, binary',
|
||||
'BlockDuration, 9b, uint',
|
||||
'ReferenceBlock*, fb, sint',
|
||||
'DiscardPadding, 75A2, sint',
|
||||
),
|
||||
'SimpleBlock*, a3, binary',
|
||||
),
|
||||
|
||||
'Tracks*, 1654ae6b, sub', (
|
||||
'TrackEntry*, ae, sub', (
|
||||
'TrackNumber, d7, uint',
|
||||
'TrackUID, 73c5, uint',
|
||||
'TrackType, 83, uint',
|
||||
'FlagEnabled, b9, uint',
|
||||
'FlagDefault, 88, uint',
|
||||
'FlagForced, 55aa, uint',
|
||||
'FlagLacing, 9c, uint',
|
||||
'MinCache, 6de7, uint',
|
||||
'MaxCache, 6df8, uint',
|
||||
'DefaultDuration, 23e383, uint',
|
||||
'TrackTimecodeScale, 23314f, float',
|
||||
'MaxBlockAdditionID, 55ee, uint',
|
||||
'Name, 536e, str',
|
||||
'Language, 22b59c, str',
|
||||
'CodecID, 86, str',
|
||||
'CodecPrivate, 63a2, binary',
|
||||
'CodecName, 258688, str',
|
||||
'CodecDecodeAll, aa, uint',
|
||||
'CodecDelay, 56aa, uint',
|
||||
'SeekPreRoll, 56bb, uint',
|
||||
'Video, e0, sub', (
|
||||
'FlagInterlaced, 9a, uint',
|
||||
'PixelWidth, b0, uint',
|
||||
'PixelHeight, ba, uint',
|
||||
'DisplayWidth, 54b0, uint',
|
||||
'DisplayHeight, 54ba, uint',
|
||||
'DisplayUnit, 54b2, uint',
|
||||
'FrameRate, 2383e3, float',
|
||||
'ColourSpace, 2eb524, binary',
|
||||
'StereoMode, 53b8, uint',
|
||||
'Colour, 55b0, sub', (
|
||||
'MatrixCoefficients, 55B1, uint',
|
||||
'BitsPerChannel, 55B2, uint',
|
||||
'ChromaSubsamplingHorz, 55B3, uint',
|
||||
'ChromaSubsamplingVert, 55B4, uint',
|
||||
'CbSubsamplingHorz, 55B5, uint',
|
||||
'CbSubsamplingVert, 55B6, uint',
|
||||
'ChromaSitingHorz, 55B7, uint',
|
||||
'ChromaSitingVert, 55B8, uint',
|
||||
'Range, 55B9, uint',
|
||||
'TransferCharacteristics, 55BA, uint',
|
||||
'Primaries, 55BB, uint',
|
||||
'MaxCLL, 55BC, uint',
|
||||
'MaxFALL, 55BD, uint',
|
||||
'MasteringMetadata, 55D0, sub', (
|
||||
'PrimaryRChromaticityX, 55D1, float',
|
||||
'PrimaryRChromaticityY, 55D2, float',
|
||||
'PrimaryGChromaticityX, 55D3, float',
|
||||
'PrimaryGChromaticityY, 55D4, float',
|
||||
'PrimaryBChromaticityX, 55D5, float',
|
||||
'PrimaryBChromaticityY, 55D6, float',
|
||||
'WhitePointChromaticityX, 55D7, float',
|
||||
'WhitePointChromaticityY, 55D8, float',
|
||||
'LuminanceMax, 55D9, float',
|
||||
'LuminanceMin, 55DA, float',
|
||||
),
|
||||
),
|
||||
),
|
||||
'Audio, e1, sub', (
|
||||
'SamplingFrequency, b5, float',
|
||||
'OutputSamplingFrequency, 78b5, float',
|
||||
'Channels, 9f, uint',
|
||||
'BitDepth, 6264, uint',
|
||||
),
|
||||
'ContentEncodings, 6d80, sub', (
|
||||
'ContentEncoding*, 6240, sub', (
|
||||
'ContentEncodingOrder, 5031, uint',
|
||||
'ContentEncodingScope, 5032, uint',
|
||||
'ContentEncodingType, 5033, uint',
|
||||
'ContentCompression, 5034, sub', (
|
||||
'ContentCompAlgo, 4254, uint',
|
||||
'ContentCompSettings, 4255, binary',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
'Cues, 1c53bb6b, sub', (
|
||||
'CuePoint*, bb, sub', (
|
||||
'CueTime, b3, uint',
|
||||
'CueTrackPositions*, b7, sub', (
|
||||
'CueTrack, f7, uint',
|
||||
'CueClusterPosition, f1, uint',
|
||||
'CueRelativePosition, f0, uint',
|
||||
'CueDuration, b2, uint',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
'Attachments, 1941a469, sub', (
|
||||
'AttachedFile*, 61a7, sub', (
|
||||
'FileDescription, 467e, str',
|
||||
'FileName, 466e, str',
|
||||
'FileMimeType, 4660, str',
|
||||
'FileData, 465c, binary',
|
||||
'FileUID, 46ae, uint',
|
||||
),
|
||||
),
|
||||
|
||||
'Chapters, 1043a770, sub', (
|
||||
'EditionEntry*, 45b9, sub', (
|
||||
'EditionUID, 45bc, uint',
|
||||
'EditionFlagHidden, 45bd, uint',
|
||||
'EditionFlagDefault, 45db, uint',
|
||||
'EditionFlagOrdered, 45dd, uint',
|
||||
'ChapterAtom*, b6, sub', (
|
||||
'ChapterUID, 73c4, uint',
|
||||
'ChapterTimeStart, 91, uint',
|
||||
'ChapterTimeEnd, 92, uint',
|
||||
'ChapterFlagHidden, 98, uint',
|
||||
'ChapterFlagEnabled, 4598, uint',
|
||||
'ChapterSegmentUID, 6e67, binary',
|
||||
'ChapterSegmentEditionUID, 6ebc, uint',
|
||||
'ChapterDisplay*, 80, sub', (
|
||||
'ChapString, 85, str',
|
||||
'ChapLanguage*, 437c, str',
|
||||
'ChapCountry*, 437e, str',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'Tags*, 1254c367, sub', (
|
||||
'Tag*, 7373, sub', (
|
||||
'Targets, 63c0, sub', (
|
||||
'TargetTypeValue, 68ca, uint',
|
||||
'TargetTrackUID, 63c5, uint',
|
||||
'TargetEditionUID, 63c9, uint',
|
||||
'TargetChapterUID, 63c4, uint',
|
||||
'TargetAttachmentUID, 63c6, uint',
|
||||
),
|
||||
'SimpleTag*, 67c8, sub', (
|
||||
'TagName, 45a3, str',
|
||||
'TagLanguage, 447a, str',
|
||||
'TagString, 4487, str'
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
import sys
|
||||
from math import ldexp
|
||||
from binascii import hexlify
|
||||
|
||||
def byte2num(s):
|
||||
return int(hexlify(s), 16)
|
||||
|
||||
class EOF(Exception): pass
|
||||
|
||||
def camelcase_to_words(name):
|
||||
parts = []
|
||||
start = 0
|
||||
for i in range(1, len(name)):
|
||||
if name[i].isupper() and (name[i-1].islower() or
|
||||
name[i+1:i+2].islower()):
|
||||
parts.append(name[start:i])
|
||||
start = i
|
||||
parts.append(name[start:])
|
||||
return '_'.join(parts).lower()
|
||||
|
||||
class MatroskaElement(object):
|
||||
|
||||
def __init__(self, name, elid, valtype, namespace):
|
||||
self.name = name
|
||||
self.definename = '{0}_ID_{1}'.format(namespace, name.upper())
|
||||
self.fieldname = camelcase_to_words(name)
|
||||
self.structname = 'ebml_' + self.fieldname
|
||||
self.elid = elid
|
||||
self.valtype = valtype
|
||||
if valtype == 'sub':
|
||||
self.ebmltype = 'EBML_TYPE_SUBELEMENTS'
|
||||
self.valname = 'struct ' + self.structname
|
||||
else:
|
||||
self.ebmltype = 'EBML_TYPE_' + valtype.upper()
|
||||
try:
|
||||
self.valname = {'uint': 'uint64_t', 'str': 'char *',
|
||||
'binary': 'bstr', 'ebml_id': 'uint32_t',
|
||||
'float': 'double', 'sint': 'int64_t',
|
||||
}[valtype]
|
||||
except KeyError:
|
||||
raise SyntaxError('Unrecognized value type ' + valtype)
|
||||
self.subelements = ()
|
||||
|
||||
def add_subelements(self, subelements):
|
||||
self.subelements = subelements
|
||||
self.subids = set(x[0].elid for x in subelements)
|
||||
|
||||
elementd = {}
|
||||
elementlist = []
|
||||
def parse_elems(l, namespace):
|
||||
subelements = []
|
||||
for el in l:
|
||||
if isinstance(el, str):
|
||||
name, hexid, eltype = [x.strip() for x in el.split(',')]
|
||||
multiple = name.endswith('*')
|
||||
name = name.strip('*')
|
||||
new = MatroskaElement(name, hexid, eltype, namespace)
|
||||
elementd[hexid] = new
|
||||
elementlist.append(new)
|
||||
subelements.append((new, multiple))
|
||||
else:
|
||||
new.add_subelements(parse_elems(el, namespace))
|
||||
return subelements
|
||||
|
||||
parse_elems(elements_ebml, 'EBML')
|
||||
parse_elems(elements_matroska, 'MATROSKA')
|
||||
|
||||
def printf(out, *args):
|
||||
out.write(' '.join([str(x) for x in args]))
|
||||
out.write('\n')
|
||||
|
||||
def generate_C_header(out):
|
||||
printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
|
||||
printf(out)
|
||||
|
||||
for el in elementlist:
|
||||
printf(out, '#define {0.definename:40} 0x{0.elid}'.format(el))
|
||||
|
||||
printf(out)
|
||||
|
||||
for el in reversed(elementlist):
|
||||
if not el.subelements:
|
||||
continue
|
||||
printf(out)
|
||||
printf(out, 'struct {0.structname} {{'.format(el))
|
||||
l = max(len(subel.valname) for subel, multiple in el.subelements)+1
|
||||
for subel, multiple in el.subelements:
|
||||
printf(out, ' {e.valname:{l}} {star}{e.fieldname};'.format(
|
||||
e=subel, l=l, star=' *'[multiple]))
|
||||
printf(out)
|
||||
for subel, multiple in el.subelements:
|
||||
printf(out, ' int n_{0.fieldname};'.format(subel))
|
||||
printf(out, '};')
|
||||
|
||||
for el in elementlist:
|
||||
if not el.subelements:
|
||||
continue
|
||||
printf(out, 'extern const struct ebml_elem_desc {0.structname}_desc;'.format(el))
|
||||
|
||||
printf(out)
|
||||
printf(out, '#define MAX_EBML_SUBELEMENTS', max(len(el.subelements)
|
||||
for el in elementlist))
|
||||
|
||||
|
||||
def generate_C_definitions(out):
|
||||
printf(out, '// Generated by TOOLS/matroska.py, do not edit manually')
|
||||
printf(out)
|
||||
for el in reversed(elementlist):
|
||||
printf(out)
|
||||
if el.subelements:
|
||||
printf(out, '#define N', el.fieldname)
|
||||
printf(out, 'E_S("{0}", {1})'.format(el.name, len(el.subelements)))
|
||||
for subel, multiple in el.subelements:
|
||||
printf(out, 'F({0.definename}, {0.fieldname}, {1})'.format(
|
||||
subel, int(multiple)))
|
||||
printf(out, '}};')
|
||||
printf(out, '#undef N')
|
||||
else:
|
||||
printf(out, 'E("{0.name}", {0.fieldname}, {0.ebmltype})'.format(el))
|
||||
|
||||
def read(s, length):
|
||||
t = s.read(length)
|
||||
if len(t) != length:
|
||||
raise EOF
|
||||
return t
|
||||
|
||||
def read_id(s):
|
||||
t = read(s, 1)
|
||||
i = 0
|
||||
mask = 128
|
||||
if ord(t) == 0:
|
||||
raise SyntaxError
|
||||
while not ord(t) & mask:
|
||||
i += 1
|
||||
mask >>= 1
|
||||
t += read(s, i)
|
||||
return t
|
||||
|
||||
def read_vint(s):
|
||||
t = read(s, 1)
|
||||
i = 0
|
||||
mask = 128
|
||||
if ord(t) == 0:
|
||||
raise SyntaxError
|
||||
while not ord(t) & mask:
|
||||
i += 1
|
||||
mask >>= 1
|
||||
t = bytes((ord(t) & (mask - 1),))
|
||||
t += read(s, i)
|
||||
return i+1, byte2num(t)
|
||||
|
||||
def read_str(s, length):
|
||||
return read(s, length)
|
||||
|
||||
def read_uint(s, length):
|
||||
t = read(s, length)
|
||||
return byte2num(t)
|
||||
|
||||
def read_sint(s, length):
|
||||
i = read_uint(s, length)
|
||||
mask = 1 << (length * 8 - 1)
|
||||
if i & mask:
|
||||
i -= 2 * mask
|
||||
return i
|
||||
|
||||
def read_float(s, length):
|
||||
t = read(s, length)
|
||||
i = byte2num(t)
|
||||
if length == 4:
|
||||
f = ldexp((i & 0x7fffff) + (1 << 23), (i >> 23 & 0xff) - 150)
|
||||
if i & (1 << 31):
|
||||
f = -f
|
||||
elif length == 8:
|
||||
f = ldexp((i & ((1 << 52) - 1)) + (1 << 52), (i >> 52 & 0x7ff) - 1075)
|
||||
if i & (1 << 63):
|
||||
f = -f
|
||||
else:
|
||||
raise SyntaxError
|
||||
return f
|
||||
|
||||
def parse_one(s, depth, parent, maxlen):
|
||||
elid = hexlify(read_id(s)).decode('ascii')
|
||||
elem = elementd.get(elid)
|
||||
if parent is not None and elid not in parent.subids and elid not in ('ec', 'bf'):
|
||||
print('Unexpected:', elid)
|
||||
if 1:
|
||||
raise NotImplementedError
|
||||
size, length = read_vint(s)
|
||||
this_length = len(elid) / 2 + size + length
|
||||
if elem is not None:
|
||||
if elem.valtype != 'skip':
|
||||
print(" " * depth, '[' + elid + ']', elem.name, 'size:', length, 'value:', end=' ')
|
||||
if elem.valtype == 'sub':
|
||||
print('subelements:')
|
||||
while length > 0:
|
||||
length -= parse_one(s, depth + 1, elem, length)
|
||||
if length < 0:
|
||||
raise SyntaxError
|
||||
elif elem.valtype == 'str':
|
||||
print('string', repr(read_str(s, length).decode('utf8', 'replace')))
|
||||
elif elem.valtype in ('binary', 'ebml_id'):
|
||||
t = read_str(s, length)
|
||||
dec = ''
|
||||
if elem.valtype == 'ebml_id':
|
||||
idelem = elementd.get(hexlify(t).decode('ascii'))
|
||||
if idelem is None:
|
||||
dec = '(UNKNOWN)'
|
||||
else:
|
||||
dec = '({0.name})'.format(idelem)
|
||||
if len(t) < 20:
|
||||
t = hexlify(t).decode('ascii')
|
||||
else:
|
||||
t = '<{0} bytes>'.format(len(t))
|
||||
print('binary', t, dec)
|
||||
elif elem.valtype == 'uint':
|
||||
print('uint', read_uint(s, length))
|
||||
elif elem.valtype == 'sint':
|
||||
print('sint', read_sint(s, length))
|
||||
elif elem.valtype == 'float':
|
||||
print('float', read_float(s, length))
|
||||
elif elem.valtype == 'skip':
|
||||
read(s, length)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
else:
|
||||
print(depth, 'Unknown element:', elid, 'size:', length)
|
||||
read(s, length)
|
||||
return this_length
|
||||
|
||||
if __name__ == "__main__":
|
||||
def parse_toplevel(s):
|
||||
parse_one(s, 0, None, 1 << 63)
|
||||
|
||||
if sys.argv[1] == '--generate-header':
|
||||
generate_C_header(sys.stdout)
|
||||
elif sys.argv[1] == '--generate-definitions':
|
||||
generate_C_definitions(sys.stdout)
|
||||
else:
|
||||
s = open(sys.argv[1], "rb")
|
||||
while 1:
|
||||
start = s.tell()
|
||||
try:
|
||||
parse_toplevel(s)
|
||||
except EOF:
|
||||
if s.tell() != start:
|
||||
raise Exception("Unexpected end of file")
|
||||
break
|
||||
@@ -67,13 +67,12 @@ EOF
|
||||
chapters
|
||||
editions
|
||||
titles
|
||||
duration
|
||||
|
||||
audio
|
||||
audio-bitrate
|
||||
audio-codec
|
||||
audio-format
|
||||
channels
|
||||
samplerate
|
||||
audio-codec-name
|
||||
|
||||
video
|
||||
angle
|
||||
|
||||
@@ -1 +1 @@
|
||||
profile=pseudo-gui
|
||||
player-operation-mode=pseudo-gui
|
||||
+6
-5
@@ -151,15 +151,16 @@ for cur in ax:
|
||||
cur.addLegend(offset = (-1, 1))
|
||||
for e in G.sevents:
|
||||
cur = ax[1 if e.type == "value" else 0]
|
||||
if not cur in G.curveno:
|
||||
G.curveno[cur] = 0
|
||||
args = {'name': e.name,'antialias':True}
|
||||
color = mkColor(colors[G.curveno[cur] % len(colors)])
|
||||
if e.type == "event-signal":
|
||||
args['symbol'] = e.marker
|
||||
args['pen'] = None
|
||||
args['symbolBrush'] = pg.mkBrush(color, width=0)
|
||||
else:
|
||||
if not cur in G.curveno:
|
||||
G.curveno[cur] = 0
|
||||
args['pen'] = pg.mkPen(mkColor(colors[G.curveno[cur] % len(colors)]), width=0)
|
||||
G.curveno[cur] += 1
|
||||
args['pen'] = pg.mkPen(color, width=0)
|
||||
G.curveno[cur] += 1
|
||||
n = cur.plot([x for x,y in e.vals], [y for x,y in e.vals], **args)
|
||||
|
||||
QtGui.QApplication.instance().exec_()
|
||||
+3
-3
@@ -89,7 +89,7 @@ class Libav < TravisDepsBuilder
|
||||
{
|
||||
"libav-stable" => {
|
||||
:action => :stable,
|
||||
:url => 'http://libav.org/releases/libav-11.tar.gz'
|
||||
:url => 'http://libav.org/releases/libav-12.tar.gz'
|
||||
},
|
||||
"libav-git" => {
|
||||
:action => :git,
|
||||
@@ -97,7 +97,7 @@ class Libav < TravisDepsBuilder
|
||||
},
|
||||
"ffmpeg-stable" => {
|
||||
:action => :stable,
|
||||
:url => 'http://www.ffmpeg.org/releases/ffmpeg-2.4.tar.gz'
|
||||
:url => 'http://www.ffmpeg.org/releases/ffmpeg-3.2.2.tar.gz'
|
||||
},
|
||||
"ffmpeg-git" => {
|
||||
:action => :git,
|
||||
@@ -107,7 +107,7 @@ class Libav < TravisDepsBuilder
|
||||
end
|
||||
|
||||
def configure
|
||||
[super, "--cc=#{ENV['CC']}"].join(" ")
|
||||
[super, "--cc=#{ENV['CC']} --disable-asm"].join(" ")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ "x$LIBAV" != "xffmpeg-git" ] || [ "x$TRAVIS_OS_NAME" != "xlinux" ]; then
|
||||
# trigger build only on one of the matrix nodes
|
||||
exit;
|
||||
fi
|
||||
|
||||
if [ "x$TRAVIS_BRANCH" != "xmaster" ]; then
|
||||
# only rebuild website with pushes to master
|
||||
exit;
|
||||
fi
|
||||
|
||||
TRAVIS_COMMIT_RANGE=${TRAVIS_COMMIT_RANGE:-HEAD^..}
|
||||
if git diff --quiet --name-only --exit-code "$TRAVIS_COMMIT_RANGE" DOCS/; then
|
||||
exit;
|
||||
fi
|
||||
|
||||
body='{
|
||||
"request": {
|
||||
"branch":"master"
|
||||
}}'
|
||||
|
||||
curl -s -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Travis-API-Version: 3" \
|
||||
-H "Authorization: token $WEBSITE_TRAVIS_TOKEN" \
|
||||
-d "$body" \
|
||||
https://api.travis-ci.org/repo/mpv-player%2Fmpv.io/requests
|
||||
+4
-2
@@ -4,11 +4,14 @@
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use warnings FATAL => 'uninitialized';
|
||||
|
||||
my $mpv = $ARGV[0] || 'mpv';
|
||||
|
||||
my @opts = parse_main_opts('--list-options', '^ (\-\-[^\s\*]*)\*?\s*(.*)');
|
||||
|
||||
die "Couldn't find any options" unless (@opts);
|
||||
|
||||
my @ao = parse_opts('--ao=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
my @vo = parse_opts('--vo=help', '^ ([^\s\:]*)\s*: (.*)');
|
||||
|
||||
@@ -141,8 +144,7 @@ $runtime_completions
|
||||
local expl
|
||||
_tags files urls
|
||||
while _tags; do
|
||||
_requested files expl 'media file' _files -g \\
|
||||
"*.(#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
|
||||
_requested files expl 'media file' _files && rc=0
|
||||
if _requested urls; then
|
||||
while _next_label urls expl URL; do
|
||||
_urls "\$expl[@]" && rc=0
|
||||
|
||||
+6
-22
@@ -86,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;
|
||||
|
||||
@@ -100,10 +100,13 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
lavc_context = avcodec_alloc_context3(lavc_codec);
|
||||
ctx->avctx = lavc_context;
|
||||
ctx->avframe = av_frame_alloc();
|
||||
lavc_context->refcounted_frames = 1;
|
||||
lavc_context->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
lavc_context->codec_id = lavc_codec->id;
|
||||
|
||||
#if LIBAVCODEC_VERSION_MICRO >= 100
|
||||
lavc_context->pkt_timebase = ctx->codec_timebase;
|
||||
#endif
|
||||
|
||||
if (opts->downmix && mpopts->audio_output_channels.num_chmaps == 1) {
|
||||
lavc_context->request_channel_layout =
|
||||
mp_chmap_to_lavc(&mpopts->audio_output_channels.chmaps[0]);
|
||||
@@ -203,7 +206,6 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
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) {
|
||||
if (ret >= 0 && mpkt)
|
||||
@@ -216,24 +218,6 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
ret = 0;
|
||||
}
|
||||
#else
|
||||
int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt);
|
||||
if (mpkt) {
|
||||
// At least "shorten" decodes sub-frames, instead of the whole packet.
|
||||
// At least "mpc8" can return 0 and wants the packet again next time.
|
||||
if (ret >= 0) {
|
||||
ret = FFMIN(ret, mpkt->len); // sanity check against decoder overreads
|
||||
mpkt->buffer += ret;
|
||||
mpkt->len -= ret;
|
||||
mpkt->pts = MP_NOPTS_VALUE; // don't reset PTS next time
|
||||
}
|
||||
// LATM may need many packets to find mux info
|
||||
if (ret == AVERROR(EAGAIN)) {
|
||||
mpkt->len = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (ret < 0) {
|
||||
MP_ERR(da, "Error decoding audio.\n");
|
||||
return -1;
|
||||
@@ -241,7 +225,7 @@ static int decode_packet(struct dec_audio *da, struct demux_packet *mpkt,
|
||||
if (!got_frame)
|
||||
return 0;
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, &priv->codec_timebase);
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pts, &priv->codec_timebase);
|
||||
|
||||
struct mp_audio *mpframe = mp_audio_from_avframe(priv->avframe);
|
||||
if (!mpframe)
|
||||
|
||||
+40
-20
@@ -71,6 +71,7 @@ static void uninit(struct dec_audio *da)
|
||||
av_freep(&lavf_ctx->pb->buffer);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
avformat_free_context(lavf_ctx);
|
||||
spdif_ctx->lavf_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,12 +83,10 @@ static int init(struct dec_audio *da, const char *decoder)
|
||||
spdif_ctx->use_dts_hd = da->opts->dtshd;
|
||||
spdif_ctx->pool = mp_audio_pool_create(spdif_ctx);
|
||||
|
||||
if (strcmp(decoder, "dts-hd") == 0) {
|
||||
decoder = "dts";
|
||||
if (strcmp(decoder, "spdif_dts_hd") == 0)
|
||||
spdif_ctx->use_dts_hd = true;
|
||||
}
|
||||
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(decoder);
|
||||
spdif_ctx->codec_id = mp_codec_to_av_codec_id(da->codec->codec);
|
||||
return spdif_ctx->codec_id != AV_CODEC_ID_NONE;
|
||||
}
|
||||
|
||||
@@ -116,16 +115,10 @@ 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;
|
||||
|
||||
@@ -178,11 +171,7 @@ static int init_filter(struct dec_audio *da, AVPacket *pkt)
|
||||
if (!stream)
|
||||
goto fail;
|
||||
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
stream->codecpar->codec_id = spdif_ctx->codec_id;
|
||||
#else
|
||||
stream->codec->codec_id = spdif_ctx->codec_id;
|
||||
#endif
|
||||
|
||||
AVDictionary *format_opts = NULL;
|
||||
|
||||
@@ -305,22 +294,53 @@ static const int codecs[] = {
|
||||
AV_CODEC_ID_NONE
|
||||
};
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
static bool find_codec(const char *name)
|
||||
{
|
||||
for (int n = 0; codecs[n] != AV_CODEC_ID_NONE; n++) {
|
||||
const char *format = mp_codec_from_av_codec_id(codecs[n]);
|
||||
if (format) {
|
||||
mp_add_decoder(list, "spdif", format, format,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
if (format && name && strcmp(format, name) == 0)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// codec is the libavcodec name of the source audio codec.
|
||||
// pref is a ","-separated list of names, some of them which do not match with
|
||||
// libavcodec names (like dts-hd).
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
|
||||
if (!find_codec(codec))
|
||||
return list;
|
||||
|
||||
bool spdif_allowed = false, dts_hd_allowed = false;
|
||||
bstr sel = bstr0(pref);
|
||||
while (sel.len) {
|
||||
bstr decoder;
|
||||
bstr_split_tok(sel, ",", &decoder, &sel);
|
||||
if (decoder.len) {
|
||||
if (bstr_equals0(decoder, codec))
|
||||
spdif_allowed = true;
|
||||
if (bstr_equals0(decoder, "dts-hd") && strcmp(codec, "dts") == 0)
|
||||
spdif_allowed = dts_hd_allowed = true;
|
||||
}
|
||||
}
|
||||
mp_add_decoder(list, "spdif", "dts", "dts-hd",
|
||||
|
||||
if (!spdif_allowed)
|
||||
return list;
|
||||
|
||||
const char *suffix_name = dts_hd_allowed ? "dts_hd" : codec;
|
||||
char name[80];
|
||||
snprintf(name, sizeof(name), "spdif_%s", suffix_name);
|
||||
mp_add_decoder(list, "spdif", codec, name,
|
||||
"libavformat/spdifenc audio pass-through decoder");
|
||||
return list;
|
||||
}
|
||||
|
||||
const struct ad_functions ad_spdif = {
|
||||
.name = "spdif",
|
||||
.add_decoders = add_decoders,
|
||||
.add_decoders = NULL,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
+24
-17
@@ -43,11 +43,12 @@
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
extern const struct ad_functions ad_lavc;
|
||||
|
||||
// Not a real codec - specially treated.
|
||||
extern const struct ad_functions ad_spdif;
|
||||
|
||||
static const struct ad_functions * const ad_drivers[] = {
|
||||
&ad_lavc,
|
||||
&ad_spdif,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -90,10 +91,10 @@ static struct mp_decoder_list *audio_select_decoders(struct dec_audio *d_audio)
|
||||
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new =
|
||||
mp_select_decoders(list, codec, opts->audio_decoders);
|
||||
if (d_audio->try_spdif) {
|
||||
mp_select_decoders(d_audio->log, list, codec, opts->audio_decoders);
|
||||
if (d_audio->try_spdif && codec) {
|
||||
struct mp_decoder_list *spdif =
|
||||
mp_select_decoder_list(list, codec, "spdif", opts->audio_spdif);
|
||||
select_spdif_codec(codec, opts->audio_spdif);
|
||||
mp_append_decoders(spdif, new);
|
||||
talloc_free(new);
|
||||
new = spdif;
|
||||
@@ -108,6 +109,8 @@ static const struct ad_functions *find_driver(const char *name)
|
||||
if (strcmp(ad_drivers[i]->name, name) == 0)
|
||||
return ad_drivers[i];
|
||||
}
|
||||
if (strcmp(name, "spdif") == 0)
|
||||
return &ad_spdif;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -126,21 +129,18 @@ int audio_init_best_codec(struct dec_audio *d_audio)
|
||||
const struct ad_functions *driver = find_driver(sel->family);
|
||||
if (!driver)
|
||||
continue;
|
||||
MP_VERBOSE(d_audio, "Opening audio decoder %s:%s\n",
|
||||
sel->family, sel->decoder);
|
||||
MP_VERBOSE(d_audio, "Opening audio decoder %s\n", sel->decoder);
|
||||
d_audio->ad_driver = driver;
|
||||
if (init_audio_codec(d_audio, sel->decoder)) {
|
||||
decoder = sel;
|
||||
break;
|
||||
}
|
||||
MP_WARN(d_audio, "Audio decoder init failed for "
|
||||
"%s:%s\n", sel->family, sel->decoder);
|
||||
MP_WARN(d_audio, "Audio decoder init failed for %s\n", sel->decoder);
|
||||
}
|
||||
|
||||
if (d_audio->ad_driver) {
|
||||
d_audio->decoder_desc =
|
||||
talloc_asprintf(d_audio, "%s [%s:%s]", decoder->desc, decoder->family,
|
||||
decoder->decoder);
|
||||
talloc_asprintf(d_audio, "%s (%s)", decoder->decoder, decoder->desc);
|
||||
MP_VERBOSE(d_audio, "Selected audio codec: %s\n", d_audio->decoder_desc);
|
||||
} else {
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
@@ -180,10 +180,14 @@ static void fix_audio_pts(struct dec_audio *da)
|
||||
|
||||
if (da->current_frame->pts != MP_NOPTS_VALUE) {
|
||||
double newpts = da->current_frame->pts;
|
||||
|
||||
if (da->pts != MP_NOPTS_VALUE)
|
||||
MP_STATS(da, "value %f audio-pts-err", da->pts - newpts);
|
||||
|
||||
// Keep the interpolated timestamp if it doesn't deviate more
|
||||
// than 1 ms from the real one. (MKV rounded timestamps.)
|
||||
if (da->pts == MP_NOPTS_VALUE || fabs(da->pts - newpts) > 0.001)
|
||||
da->pts = da->current_frame->pts;
|
||||
da->pts = newpts;
|
||||
}
|
||||
|
||||
if (da->pts == MP_NOPTS_VALUE && da->header->missing_timestamps)
|
||||
@@ -253,12 +257,15 @@ void audio_work(struct dec_audio *da)
|
||||
struct demux_packet *new_segment = da->new_segment;
|
||||
da->new_segment = NULL;
|
||||
|
||||
// Could avoid decoder reinit; would still need flush.
|
||||
da->codec = new_segment->codec;
|
||||
if (da->ad_driver)
|
||||
da->ad_driver->uninit(da);
|
||||
da->ad_driver = NULL;
|
||||
audio_init_best_codec(da);
|
||||
if (da->codec == new_segment->codec) {
|
||||
audio_reset_decoding(da);
|
||||
} else {
|
||||
da->codec = new_segment->codec;
|
||||
if (da->ad_driver)
|
||||
da->ad_driver->uninit(da);
|
||||
da->ad_driver = NULL;
|
||||
audio_init_best_codec(da);
|
||||
}
|
||||
|
||||
da->start = new_segment->start;
|
||||
da->end = new_segment->end;
|
||||
|
||||
@@ -59,4 +59,7 @@ int audio_get_frame(struct dec_audio *d_audio, struct mp_audio **out_frame);
|
||||
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
|
||||
// ad_spdif.c
|
||||
struct mp_decoder_list *select_spdif_codec(const char *codec, const char *pref);
|
||||
|
||||
#endif /* MPLAYER_DEC_AUDIO_H */
|
||||
+10
-4
@@ -166,10 +166,10 @@ static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
.replaygain_data = s->replaygain_data,
|
||||
.out_pool = mp_audio_pool_create(af),
|
||||
};
|
||||
struct m_config *config = m_config_from_obj_desc(af, s->log, &desc);
|
||||
if (m_config_apply_defaults(config, name, s->opts->af_defs) < 0)
|
||||
goto error;
|
||||
if (m_config_set_obj_params(config, args) < 0)
|
||||
struct m_config *config =
|
||||
m_config_from_obj_desc_and_args(af, s->log, NULL, &desc,
|
||||
name, s->opts->af_defs, args);
|
||||
if (!config)
|
||||
goto error;
|
||||
af->priv = config->optstruct;
|
||||
|
||||
@@ -779,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)
|
||||
|
||||
@@ -148,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);
|
||||
|
||||
@@ -280,7 +280,6 @@ static int filter_out(struct af_instance *af)
|
||||
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);
|
||||
@@ -310,21 +309,6 @@ static int filter_out(struct af_instance *af)
|
||||
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);
|
||||
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 + s->input->samples);
|
||||
|
||||
@@ -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,18 +109,9 @@ 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
|
||||
return swr_get_out_samples(s->avrctx, in_samples);
|
||||
#else
|
||||
return av_rescale_rnd(in_samples, s->out_rate, s->in_rate, AV_ROUND_UP)
|
||||
+ swr_get_delay(s->avrctx, s->out_rate);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -406,8 +393,17 @@ static int control(struct af_instance *af, int cmd, void *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;
|
||||
@@ -479,19 +475,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)
|
||||
|
||||
+138
-124
@@ -28,12 +28,11 @@
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_pan_s
|
||||
{
|
||||
int nch; // Number of output channels; zero means same as input
|
||||
float level[AF_NCH][AF_NCH]; // Gain level for each channel
|
||||
char *matrixstr;
|
||||
}af_pan_t;
|
||||
typedef struct af_pan_s {
|
||||
int nch; // Number of output channels; zero means same as input
|
||||
float level[AF_NCH][AF_NCH]; // Gain level for each channel
|
||||
char *matrixstr;
|
||||
} af_pan_t;
|
||||
|
||||
static void set_channels(struct mp_audio *mpa, int num)
|
||||
{
|
||||
@@ -44,129 +43,17 @@ static void set_channels(struct mp_audio *mpa, int num)
|
||||
mp_audio_set_channels(mpa, &map);
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
static void parse_matrix(struct af_instance *af, const char *cp)
|
||||
{
|
||||
af_pan_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
set_channels(af->data, s->nch ? s->nch: ((struct mp_audio*)arg)->nch);
|
||||
|
||||
if((af->data->format != ((struct mp_audio*)arg)->format) ||
|
||||
(af->data->bps != ((struct mp_audio*)arg)->bps)){
|
||||
mp_audio_set_format((struct mp_audio*)arg, af->data->format);
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PAN_LEVEL:{
|
||||
int i;
|
||||
int ch = ((af_control_ext_t*)arg)->ch;
|
||||
float* level = ((af_control_ext_t*)arg)->arg;
|
||||
if (ch >= AF_NCH)
|
||||
return AF_FALSE;
|
||||
for(i=0;i<AF_NCH;i++)
|
||||
s->level[ch][i] = level[i];
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SET_PAN_NOUT:
|
||||
// Reinit must be called after this function has been called
|
||||
|
||||
// Sanity check
|
||||
if(((int*)arg)[0] <= 0 || ((int*)arg)[0] > AF_NCH){
|
||||
MP_ERR(af, "The number of output channels must be"
|
||||
" between 1 and %i. Current value is %i\n",AF_NCH,((int*)arg)[0]);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->nch=((int*)arg)[0];
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PAN_BALANCE:{
|
||||
float val = *(float*)arg;
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
if (af->data->nch >= 2) {
|
||||
s->level[0][0] = MPMIN(1.f, 1.f - val);
|
||||
s->level[0][1] = MPMAX(0.f, val);
|
||||
s->level[1][0] = MPMAX(0.f, -val);
|
||||
s->level[1][1] = MPMIN(1.f, 1.f + val);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_GET_PAN_BALANCE:
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
*(float*)arg = s->level[0][1] - s->level[1][0];
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
if (!c)
|
||||
return 0;
|
||||
struct mp_audio *l = mp_audio_pool_get(af->out_pool, &af->fmt_out, c->samples);
|
||||
if (!l) {
|
||||
talloc_free(c);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(l, c);
|
||||
|
||||
af_pan_t* s = af->priv; // Setup for this instance
|
||||
float* in = c->planes[0]; // Input audio data
|
||||
float* out = NULL; // Output audio data
|
||||
float* end = in+c->samples*c->nch; // End of loop
|
||||
int nchi = c->nch; // Number of input channels
|
||||
int ncho = l->nch; // Number of output channels
|
||||
register int j,k;
|
||||
|
||||
out = l->planes[0];
|
||||
// Execute panning
|
||||
// FIXME: Too slow
|
||||
while(in < end){
|
||||
for(j=0;j<ncho;j++){
|
||||
register float x = 0.0;
|
||||
register float* tin = in;
|
||||
for(k=0;k<nchi;k++)
|
||||
x += tin[k] * s->level[j][k];
|
||||
out[j] = x;
|
||||
}
|
||||
out+= ncho;
|
||||
in+= nchi;
|
||||
}
|
||||
|
||||
talloc_free(c);
|
||||
af_add_output_frame(af, l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->filter_frame = filter_frame;
|
||||
af_pan_t *s = af->priv;
|
||||
int n = 0;
|
||||
int j,k;
|
||||
|
||||
int nch = s->nch;
|
||||
if(nch && AF_OK != control(af,AF_CONTROL_SET_PAN_NOUT, &nch))
|
||||
return AF_ERROR;
|
||||
|
||||
// Read pan values
|
||||
char *cp = s->matrixstr;
|
||||
j = 0; k = 0;
|
||||
while(cp && k < AF_NCH){
|
||||
int j = 0, k = 0, n;
|
||||
while (*cp && k < AF_NCH) {
|
||||
sscanf(cp, "%f%n" , &s->level[j][k], &n);
|
||||
MP_VERBOSE(af, "Pan level from channel %i to"
|
||||
" channel %i = %f\n",k,j,s->level[j][k]);
|
||||
cp =&cp[n];
|
||||
" channel %i = %f\n", k, j, s->level[j][k]);
|
||||
cp = &cp[n];
|
||||
j++;
|
||||
if(j>=nch){
|
||||
if (j >= s->nch) {
|
||||
j = 0;
|
||||
k++;
|
||||
}
|
||||
@@ -174,6 +61,133 @@ static int af_open(struct af_instance* af){
|
||||
break;
|
||||
cp++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_pan_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
// Sanity check
|
||||
if (!arg)
|
||||
return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
set_channels(af->data, s->nch ? s->nch : ((struct mp_audio*)arg)->nch);
|
||||
|
||||
if ((af->data->format != ((struct mp_audio*)arg)->format) ||
|
||||
(af->data->bps != ((struct mp_audio*)arg)->bps)) {
|
||||
mp_audio_set_format((struct mp_audio*)arg, af->data->format);
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PAN_LEVEL: {
|
||||
int i;
|
||||
int ch = ((af_control_ext_t*)arg)->ch;
|
||||
float *level = ((af_control_ext_t*)arg)->arg;
|
||||
if (ch >= AF_NCH)
|
||||
return AF_FALSE;
|
||||
for (i = 0; i < AF_NCH; i++)
|
||||
s->level[ch][i] = level[i];
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SET_PAN_NOUT:
|
||||
// Reinit must be called after this function has been called
|
||||
// Sanity check
|
||||
if (((int*)arg)[0] <= 0 || ((int*)arg)[0] > AF_NCH) {
|
||||
MP_ERR(af, "The number of output channels must be"
|
||||
" between 1 and %i. Current value is %i\n",
|
||||
AF_NCH, ((int*)arg)[0]);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->nch = ((int*)arg)[0];
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SET_PAN_BALANCE: {
|
||||
float val = *(float*)arg;
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
if (af->data->nch >= 2) {
|
||||
s->level[0][0] = MPMIN(1.f, 1.f - val);
|
||||
s->level[0][1] = MPMAX(0.f, val);
|
||||
s->level[1][0] = MPMAX(0.f, -val);
|
||||
s->level[1][1] = MPMIN(1.f, 1.f + val);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_GET_PAN_BALANCE:
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
*(float*)arg = s->level[0][1] - s->level[1][0];
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-matrix")) {
|
||||
parse_matrix(af, args[1]);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static int filter_frame(struct af_instance *af, struct mp_audio *c)
|
||||
{
|
||||
if (!c)
|
||||
return 0;
|
||||
struct mp_audio *l = mp_audio_pool_get(af->out_pool, &af->fmt_out, c->samples);
|
||||
if (!l) {
|
||||
talloc_free(c);
|
||||
return -1;
|
||||
}
|
||||
mp_audio_copy_attributes(l, c);
|
||||
|
||||
af_pan_t* s = af->priv; // Setup for this instance
|
||||
float *in = c->planes[0]; // Input audio data
|
||||
float *out = NULL; // Output audio data
|
||||
float *end = in+c->samples * c->nch; // End of loop
|
||||
int nchi = c->nch; // Number of input channels
|
||||
int ncho = l->nch; // Number of output channels
|
||||
register int j, k;
|
||||
|
||||
out = l->planes[0];
|
||||
// Execute panning
|
||||
// FIXME: Too slow
|
||||
while (in < end) {
|
||||
for (j = 0; j < ncho; j++) {
|
||||
register float x = 0.0;
|
||||
register float *tin = in;
|
||||
for (k = 0; k < nchi; k++)
|
||||
x += tin[k] * s->level[j][k];
|
||||
out[j] = x;
|
||||
}
|
||||
out += ncho;
|
||||
in += nchi;
|
||||
}
|
||||
|
||||
talloc_free(c);
|
||||
af_add_output_frame(af, l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter_frame = filter_frame;
|
||||
af_pan_t *s = af->priv;
|
||||
int nch = s->nch;
|
||||
if (nch && AF_OK != control(af, AF_CONTROL_SET_PAN_NOUT, &nch))
|
||||
return AF_ERROR;
|
||||
|
||||
// Read pan values
|
||||
if (s->matrixstr)
|
||||
parse_matrix(af, s->matrixstr);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
struct priv {
|
||||
RubberBandState rubber;
|
||||
double speed;
|
||||
double pitch;
|
||||
struct mp_audio *pending;
|
||||
bool needs_reset;
|
||||
// Estimate how much librubberband has buffered internally.
|
||||
@@ -44,6 +45,14 @@ static void update_speed(struct af_instance *af, double new_speed)
|
||||
rubberband_set_time_ratio(p->rubber, 1.0 / p->speed);
|
||||
}
|
||||
|
||||
static void update_pitch(struct af_instance *af, double new_pitch)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
p->pitch = new_pitch;
|
||||
rubberband_set_pitch_scale(p->rubber, p->pitch);
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
@@ -72,6 +81,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
}
|
||||
|
||||
update_speed(af, p->speed);
|
||||
update_pitch(af, p->pitch);
|
||||
control(af, AF_CONTROL_RESET, NULL);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
@@ -87,6 +97,19 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
p->pending = NULL;
|
||||
p->rubber_delay = 0;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND: {
|
||||
char **args = arg;
|
||||
if (!strcmp(args[0], "set-pitch")) {
|
||||
char *endptr;
|
||||
double pitch = strtod(args[1], &endptr);
|
||||
if (*endptr || pitch < 0.01 || pitch > 100.0)
|
||||
return CONTROL_ERROR;
|
||||
update_pitch(af, pitch);
|
||||
return CONTROL_OK;
|
||||
} else {
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
@@ -187,9 +210,11 @@ const struct af_info af_info_rubberband = {
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.speed = 1.0,
|
||||
.pitch = 1.0,
|
||||
.opt_pitch = RubberBandOptionPitchHighConsistency,
|
||||
.opt_transients = RubberBandOptionTransientsMixed,
|
||||
.opt_formant = RubberBandOptionFormantPreserved,
|
||||
.opt_channels = RubberBandOptionChannelsTogether,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("transients", opt_transients, 0,
|
||||
@@ -220,6 +245,7 @@ const struct af_info af_info_rubberband = {
|
||||
OPT_CHOICE("channels", opt_channels, 0,
|
||||
({"apart", RubberBandOptionChannelsApart},
|
||||
{"together", RubberBandOptionChannelsTogether})),
|
||||
OPT_DOUBLE("pitch-scale", pitch, M_OPT_RANGE, .min = 0.01, .max = 100),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+54
-30
@@ -28,7 +28,6 @@
|
||||
#include "audio/format.h"
|
||||
#include "audio/audio.h"
|
||||
|
||||
#include "input/input.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "common/msg.h"
|
||||
@@ -36,6 +35,7 @@
|
||||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_audiounit;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
extern const struct ao_driver audio_out_coreaudio_exclusive;
|
||||
extern const struct ao_driver audio_out_rsound;
|
||||
@@ -53,6 +53,9 @@ extern const struct ao_driver audio_out_sdl;
|
||||
|
||||
static const struct ao_driver * const audio_out_drivers[] = {
|
||||
// native:
|
||||
#if HAVE_AUDIOUNIT
|
||||
&audio_out_audiounit,
|
||||
#endif
|
||||
#if HAVE_COREAUDIO
|
||||
&audio_out_coreaudio,
|
||||
#endif
|
||||
@@ -109,6 +112,8 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
.priv_size = ao->priv_size,
|
||||
.priv_defaults = ao->priv_defaults,
|
||||
.options = ao->options,
|
||||
.options_prefix = ao->options_prefix,
|
||||
.global_opts = ao->global_opts,
|
||||
.hidden = ao->encode,
|
||||
.p = ao,
|
||||
};
|
||||
@@ -121,11 +126,16 @@ const struct m_obj_list ao_obj_list = {
|
||||
.description = "audio outputs",
|
||||
.allow_unknown_entries = true,
|
||||
.allow_trailer = true,
|
||||
.disallow_positional_parameters = true,
|
||||
.use_global_options = true,
|
||||
};
|
||||
|
||||
static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
struct input_ctx *input_ctx, char *name, char **args)
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
char *name)
|
||||
{
|
||||
assert(wakeup_cb);
|
||||
|
||||
struct MPOpts *opts = global->opts;
|
||||
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
|
||||
struct m_obj_desc desc;
|
||||
@@ -139,17 +149,16 @@ static struct ao *ao_alloc(bool probing, struct mpv_global *global,
|
||||
*ao = (struct ao) {
|
||||
.driver = desc.p,
|
||||
.probing = probing,
|
||||
.input_ctx = input_ctx,
|
||||
.global = global,
|
||||
.wakeup_cb = wakeup_cb,
|
||||
.wakeup_ctx = wakeup_ctx,
|
||||
.log = mp_log_new(ao, log, name),
|
||||
.def_buffer = opts->audio_buffer,
|
||||
.client_name = talloc_strdup(ao, opts->audio_client_name),
|
||||
};
|
||||
struct m_config *config = m_config_from_obj_desc(ao, ao->log, &desc);
|
||||
if (m_config_apply_defaults(config, name, opts->ao_defs) < 0)
|
||||
ao->priv = m_config_group_from_desc(ao, ao->log, global, &desc, name);
|
||||
if (!ao->priv)
|
||||
goto error;
|
||||
if (m_config_set_obj_params(config, args) < 0)
|
||||
goto error;
|
||||
ao->priv = config->optstruct;
|
||||
return ao;
|
||||
error:
|
||||
talloc_free(ao);
|
||||
@@ -157,12 +166,12 @@ error:
|
||||
}
|
||||
|
||||
static struct ao *ao_init(bool probing, struct mpv_global *global,
|
||||
struct input_ctx *input_ctx,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx, int flags,
|
||||
int samplerate, int format, struct mp_chmap channels,
|
||||
char *dev, char *name, char **args)
|
||||
char *dev, char *name)
|
||||
{
|
||||
struct ao *ao = ao_alloc(probing, global, input_ctx, name, args);
|
||||
struct ao *ao = ao_alloc(probing, global, wakeup_cb, wakeup_ctx, name);
|
||||
if (!ao)
|
||||
return NULL;
|
||||
ao->samplerate = samplerate;
|
||||
@@ -193,8 +202,9 @@ 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, flags,
|
||||
samplerate, format, channels, rdevice, redirect, NULL);
|
||||
return ao_init(probing, global, wakeup_cb, wakeup_ctx,
|
||||
encode_lavc_ctx, flags, samplerate, format, channels,
|
||||
rdevice, redirect);
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
@@ -244,7 +254,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,
|
||||
int init_flags,
|
||||
struct input_ctx *input_ctx,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
{
|
||||
@@ -300,9 +310,8 @@ 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, init_flags,
|
||||
samplerate, format, channels, dev,
|
||||
entry->name, entry->attribs);
|
||||
ao = ao_init(probing, global, wakeup_cb, wakeup_ctx, encode_lavc_ctx,
|
||||
init_flags, samplerate, format, channels, dev, entry->name);
|
||||
if (ao)
|
||||
break;
|
||||
if (!probing)
|
||||
@@ -405,8 +414,7 @@ int ao_query_and_reset_events(struct ao *ao, int events)
|
||||
static void ao_add_events(struct ao *ao, int events)
|
||||
{
|
||||
atomic_fetch_or(&ao->events_, events);
|
||||
if (ao->input_ctx)
|
||||
mp_input_wakeup(ao->input_ctx);
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
}
|
||||
|
||||
// Request that the player core destroys and recreates the AO. Fully thread-safe.
|
||||
@@ -490,7 +498,8 @@ bool ao_untimed(struct ao *ao)
|
||||
|
||||
struct ao_hotplug {
|
||||
struct mpv_global *global;
|
||||
struct input_ctx *input_ctx;
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
void *wakeup_ctx;
|
||||
// A single AO instance is used to listen to hotplug events. It wouldn't
|
||||
// make much sense to allow multiple AO drivers; all sane platforms have
|
||||
// a single such audio API.
|
||||
@@ -502,12 +511,14 @@ struct ao_hotplug {
|
||||
};
|
||||
|
||||
struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
|
||||
struct input_ctx *input_ctx)
|
||||
void (*wakeup_cb)(void *ctx),
|
||||
void *wakeup_ctx)
|
||||
{
|
||||
struct ao_hotplug *hp = talloc_ptrtype(NULL, hp);
|
||||
*hp = (struct ao_hotplug){
|
||||
.global = global,
|
||||
.input_ctx = input_ctx,
|
||||
.wakeup_cb = wakeup_cb,
|
||||
.wakeup_ctx = wakeup_ctx,
|
||||
.needs_update = true,
|
||||
};
|
||||
return hp;
|
||||
@@ -515,14 +526,10 @@ 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) {
|
||||
ao->driver->list_devs(ao, list);
|
||||
} else {
|
||||
char name[80] = "Default";
|
||||
if (num > 1)
|
||||
mp_snprintf_cat(name, sizeof(name), " (%s)", ao->driver->name);
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", name});
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", ""});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,8 +567,8 @@ struct ao_device_list *ao_hotplug_get_device_list(struct ao_hotplug *hp)
|
||||
if (d == &audio_out_null)
|
||||
break; // don't add unsafe/special entries
|
||||
|
||||
struct ao *ao = ao_alloc(true, hp->global, hp->input_ctx,
|
||||
(char *)d->name, NULL);
|
||||
struct ao *ao = ao_alloc(true, hp->global, hp->wakeup_cb, hp->wakeup_ctx,
|
||||
(char *)d->name);
|
||||
if (!ao)
|
||||
continue;
|
||||
|
||||
@@ -585,6 +592,19 @@ void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
|
||||
{
|
||||
struct ao_device_desc c = *e;
|
||||
const char *dname = ao->driver->name;
|
||||
char buf[80];
|
||||
if (!c.desc || !c.desc[0]) {
|
||||
if (c.name && c.name[0]) {
|
||||
c.desc = c.name;
|
||||
} else if (list->num_devices) {
|
||||
// Assume this is the default device.
|
||||
snprintf(buf, sizeof(buf), "Default (%s)", dname);
|
||||
c.desc = buf;
|
||||
} else {
|
||||
// First default device (and maybe the only one).
|
||||
c.desc = "Default";
|
||||
}
|
||||
}
|
||||
c.name = c.name[0] ? talloc_asprintf(list, "%s/%s", dname, c.name)
|
||||
: talloc_strdup(list, dname);
|
||||
c.desc = talloc_strdup(list, c.desc);
|
||||
@@ -601,9 +621,13 @@ void ao_hotplug_destroy(struct ao_hotplug *hp)
|
||||
talloc_free(hp);
|
||||
}
|
||||
|
||||
static void dummy_wakeup(void *ctx)
|
||||
{
|
||||
}
|
||||
|
||||
void ao_print_devices(struct mpv_global *global, struct mp_log *log)
|
||||
{
|
||||
struct ao_hotplug *hp = ao_hotplug_create(global, NULL);
|
||||
struct ao_hotplug *hp = ao_hotplug_create(global, dummy_wakeup, NULL);
|
||||
struct ao_device_list *list = ao_hotplug_get_device_list(hp);
|
||||
mp_info(log, "List of detected audio devices:\n");
|
||||
for (int n = 0; n < list->num_devices; n++) {
|
||||
|
||||
+5
-2
@@ -58,6 +58,8 @@ enum {
|
||||
AO_INIT_SAFE_MULTICHANNEL_ONLY = 1 << 1,
|
||||
// Stream silence as long as no audio is playing.
|
||||
AO_INIT_STREAM_SILENCE = 1 << 2,
|
||||
// Force exclusive mode, i.e. lock out the system mixer.
|
||||
AO_INIT_EXCLUSIVE = 1 << 3,
|
||||
};
|
||||
|
||||
typedef struct ao_control_vol {
|
||||
@@ -83,7 +85,7 @@ struct mp_audio;
|
||||
|
||||
struct ao *ao_init_best(struct mpv_global *global,
|
||||
int init_flags,
|
||||
struct input_ctx *input_ctx,
|
||||
void (*wakeup_cb)(void *ctx), void *wakeup_ctx,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels);
|
||||
void ao_uninit(struct ao *ao);
|
||||
@@ -106,7 +108,8 @@ void ao_hotplug_event(struct ao *ao);
|
||||
|
||||
struct ao_hotplug;
|
||||
struct ao_hotplug *ao_hotplug_create(struct mpv_global *global,
|
||||
struct input_ctx *input_ctx);
|
||||
void (*wakeup_cb)(void *ctx),
|
||||
void *wakeup_ctx);
|
||||
void ao_hotplug_destroy(struct ao_hotplug *hp);
|
||||
bool ao_hotplug_check_update(struct ao_hotplug *hp);
|
||||
const char *ao_hotplug_get_detected_device(struct ao_hotplug *hp);
|
||||
|
||||
+106
-67
@@ -35,6 +35,7 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/endian.h"
|
||||
@@ -48,6 +49,37 @@
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
struct ao_alsa_opts {
|
||||
char *device;
|
||||
char *mixer_device;
|
||||
char *mixer_name;
|
||||
int mixer_index;
|
||||
int resample;
|
||||
int ni;
|
||||
int ignore_chmap;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct ao_alsa_opts
|
||||
static const struct m_sub_options ao_alsa_conf = {
|
||||
.opts = (const struct m_option[]) {
|
||||
OPT_STRING("alsa-device", device, 0, DEVICE_OPT_DEPRECATION),
|
||||
OPT_FLAG("alsa-resample", resample, 0),
|
||||
OPT_STRING("alsa-mixer-device", mixer_device, 0),
|
||||
OPT_STRING("alsa-mixer-name", mixer_name, 0),
|
||||
OPT_INTRANGE("alsa-mixer-index", mixer_index, 0, 0, 99),
|
||||
OPT_FLAG("alsa-non-interleaved", ni, 0),
|
||||
OPT_FLAG("alsa-ignore-chmap", ignore_chmap, 0),
|
||||
{0}
|
||||
},
|
||||
.defaults = &(const struct ao_alsa_opts) {
|
||||
.mixer_device = "default",
|
||||
.mixer_name = "Master",
|
||||
.mixer_index = 0,
|
||||
.ni = 0,
|
||||
},
|
||||
.size = sizeof(struct ao_alsa_opts),
|
||||
};
|
||||
|
||||
struct priv {
|
||||
snd_pcm_t *alsa;
|
||||
bool device_lost;
|
||||
@@ -61,13 +93,7 @@ struct priv {
|
||||
|
||||
snd_output_t *output;
|
||||
|
||||
char *cfg_device;
|
||||
char *cfg_mixer_device;
|
||||
char *cfg_mixer_name;
|
||||
int cfg_mixer_index;
|
||||
int cfg_resample;
|
||||
int cfg_ni;
|
||||
int cfg_ignore_chmap;
|
||||
struct ao_alsa_opts *opts;
|
||||
};
|
||||
|
||||
#define BUFFER_TIME 250000 // 250ms
|
||||
@@ -125,13 +151,13 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
|
||||
snd_mixer_selem_id_alloca(&sid);
|
||||
|
||||
snd_mixer_selem_id_set_index(sid, p->cfg_mixer_index);
|
||||
snd_mixer_selem_id_set_name(sid, p->cfg_mixer_name);
|
||||
snd_mixer_selem_id_set_index(sid, p->opts->mixer_index);
|
||||
snd_mixer_selem_id_set_name(sid, p->opts->mixer_name);
|
||||
|
||||
err = snd_mixer_open(&handle, 0);
|
||||
CHECK_ALSA_ERROR("Mixer open error");
|
||||
|
||||
err = snd_mixer_attach(handle, p->cfg_mixer_device);
|
||||
err = snd_mixer_attach(handle, p->opts->mixer_device);
|
||||
CHECK_ALSA_ERROR("Mixer attach error");
|
||||
|
||||
err = snd_mixer_selem_register(handle, NULL, NULL);
|
||||
@@ -337,8 +363,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];
|
||||
@@ -348,12 +376,18 @@ static bool query_chmaps(struct ao *ao, struct mp_chmap *chmap)
|
||||
struct mp_chmap entry;
|
||||
if (mp_chmap_from_alsa(&entry, &maps[i]->map)) {
|
||||
struct mp_chmap reorder = entry;
|
||||
if (maps[i]->type == SND_CHMAP_TYPE_VAR)
|
||||
mp_chmap_reorder_norm(&reorder);
|
||||
MP_DBG(ao, "Got supported channel map: %s (type %s) -> %s -> %s\n",
|
||||
aname, snd_pcm_chmap_type_name(maps[i]->type),
|
||||
mp_chmap_to_str(&entry), mp_chmap_to_str(&reorder));
|
||||
mp_chmap_sel_add_map(&chmap_sel, &reorder);
|
||||
mp_chmap_reorder_norm(&reorder);
|
||||
|
||||
MP_DBG(ao, "got ALSA chmap: %s (%s) -> %s", aname,
|
||||
snd_pcm_chmap_type_name(maps[i]->type),
|
||||
mp_chmap_to_str(&entry));
|
||||
if (!mp_chmap_equals(&entry, &reorder))
|
||||
MP_DBG(ao, " -> %s", mp_chmap_to_str(&reorder));
|
||||
MP_DBG(ao, "\n");
|
||||
|
||||
struct mp_chmap final =
|
||||
maps[i]->type == SND_CHMAP_TYPE_VAR ? reorder : entry;
|
||||
mp_chmap_sel_add_map(&chmap_sel, &final);
|
||||
} else {
|
||||
MP_VERBOSE(ao, "skipping unknown ALSA channel map: %s\n", aname);
|
||||
}
|
||||
@@ -445,7 +479,7 @@ static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels
|
||||
|
||||
MP_VERBOSE(ao, "which we understand as: %s\n", mp_chmap_to_str(&chmap));
|
||||
|
||||
if (p->cfg_ignore_chmap) {
|
||||
if (p->opts->ignore_chmap) {
|
||||
MP_VERBOSE(ao, "user set ignore-chmap; ignoring the channel map.\n");
|
||||
} else if (af_fmt_is_spdif(ao->format)) {
|
||||
MP_VERBOSE(ao, "using spdif passthrough; ignoring the channel map.\n");
|
||||
@@ -454,10 +488,14 @@ static int set_chmap(struct ao *ao, struct mp_chmap *dev_chmap, int num_channels
|
||||
} else if (chmap.num != num_channels) {
|
||||
MP_WARN(ao, "ALSA channel map conflicts with channel count!\n");
|
||||
} else {
|
||||
MP_VERBOSE(ao, "using the ALSA channel map.\n");
|
||||
if (mp_chmap_equals(&chmap, &ao->channels))
|
||||
if (mp_chmap_equals(&chmap, &ao->channels)) {
|
||||
MP_VERBOSE(ao, "which is what we requested.\n");
|
||||
ao->channels = chmap;
|
||||
} else if (!mp_chmap_is_valid(dev_chmap)) {
|
||||
MP_VERBOSE(ao, "ignoring the ALSA channel map.\n");
|
||||
} else {
|
||||
MP_VERBOSE(ao, "using the ALSA channel map.\n");
|
||||
ao->channels = chmap;
|
||||
}
|
||||
}
|
||||
|
||||
free(alsa_chmap);
|
||||
@@ -562,11 +600,16 @@ static int try_open_device(struct ao *ao, const char *device, int mode)
|
||||
bstr dev;
|
||||
bstr_split_tok(bstr0(device), ":", &dev, &(bstr){0});
|
||||
if (bstr_equals0(dev, "default")) {
|
||||
ac3_device = append_params(tmp, "iec958", params);
|
||||
MP_VERBOSE(ao, "got error %d; opening iec fallback device '%s'\n",
|
||||
err, ac3_device);
|
||||
err = snd_pcm_open
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
const char *const fallbacks[] = {"hdmi", "iec958", NULL};
|
||||
for (int n = 0; fallbacks[n]; n++) {
|
||||
char *ndev = append_params(tmp, fallbacks[n], params);
|
||||
MP_VERBOSE(ao, "got error '%s'; opening iec fallback "
|
||||
"device '%s'\n", snd_strerror(err), ndev);
|
||||
err = snd_pcm_open
|
||||
(&p->alsa, ndev, SND_PCM_STREAM_PLAYBACK, mode);
|
||||
if (err >= 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
talloc_free(tmp);
|
||||
@@ -613,8 +656,8 @@ static int init_device(struct ao *ao, int mode)
|
||||
const char *device = "default";
|
||||
if (ao->device)
|
||||
device = ao->device;
|
||||
if (p->cfg_device && p->cfg_device[0])
|
||||
device = p->cfg_device;
|
||||
if (p->opts->device && p->opts->device[0])
|
||||
device = p->opts->device;
|
||||
|
||||
err = try_open_device(ao, device, mode);
|
||||
CHECK_ALSA_ERROR("Playback open error");
|
||||
@@ -639,7 +682,7 @@ static int init_device(struct ao *ao, int mode)
|
||||
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
if (!p->cfg_resample) {
|
||||
if (!p->opts->resample) {
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
}
|
||||
@@ -681,18 +724,27 @@ static int init_device(struct ao *ao, int mode)
|
||||
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) {
|
||||
dev_chmap.num = 0; // disable chmap API
|
||||
} else if (dev_chmap.num == 1 && dev_chmap.speaker[0] == MP_SPEAKER_ID_FC) {
|
||||
// As yet another ALSA API inconsistency, mono is not reported correctly.
|
||||
dev_chmap.num = 0;
|
||||
} else if (query_chmaps(ao, &dev_chmap)) {
|
||||
ao->channels = dev_chmap;
|
||||
} else {
|
||||
// Assume only stereo and mono are supported.
|
||||
mp_chmap_from_channels(&ao->channels, MPMIN(2, dev_chmap.num));
|
||||
dev_chmap.num = 0;
|
||||
// Stereo, or mono if input is 1 channel.
|
||||
struct mp_chmap reduced;
|
||||
mp_chmap_from_channels(&reduced, MPMIN(2, ao->channels.num));
|
||||
|
||||
struct mp_chmap dev_chmap = {0};
|
||||
if (!af_fmt_is_spdif(ao->format) && !p->opts->ignore_chmap &&
|
||||
!mp_chmap_equals(&ao->channels, &reduced))
|
||||
{
|
||||
struct mp_chmap res = ao->channels;
|
||||
if (query_chmaps(ao, &res))
|
||||
dev_chmap = res;
|
||||
|
||||
// Whatever it is, we dumb it down to mono or stereo. Some drivers may
|
||||
// return things like bl-br, but the user (probably) still wants stereo.
|
||||
// This also handles the failure case (dev_chmap.num==0).
|
||||
if (dev_chmap.num <= 2) {
|
||||
dev_chmap.num = 0;
|
||||
ao->channels = reduced;
|
||||
} else if (dev_chmap.num) {
|
||||
ao->channels = dev_chmap;
|
||||
}
|
||||
}
|
||||
|
||||
int num_channels = ao->channels.num;
|
||||
@@ -742,8 +794,13 @@ static int init_device(struct ao *ao, int mode)
|
||||
if (num_channels != ao->channels.num) {
|
||||
int req = ao->channels.num;
|
||||
mp_chmap_from_channels(&ao->channels, MPMIN(2, num_channels));
|
||||
mp_chmap_fill_na(&ao->channels, num_channels);
|
||||
MP_ERR(ao, "Asked for %d channels, got %d - fallback to %s.\n", req,
|
||||
num_channels, mp_chmap_to_str(&ao->channels));
|
||||
if (num_channels != ao->channels.num) {
|
||||
MP_FATAL(ao, "mismatching channel counts.\n");
|
||||
goto alsa_error;
|
||||
}
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &p->buffersize);
|
||||
@@ -788,12 +845,6 @@ static int init_device(struct ao *ao, int mode)
|
||||
|
||||
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:
|
||||
@@ -804,7 +855,9 @@ alsa_error:
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!p->cfg_ni)
|
||||
p->opts = mp_get_config_group(ao, ao->global, &ao_alsa_conf);
|
||||
|
||||
if (!p->opts->ni)
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
|
||||
@@ -1086,7 +1139,8 @@ static bool is_useless_device(char *name)
|
||||
{
|
||||
char *crap[] = {"front", "rear", "center_lfe", "side", "surround21",
|
||||
"surround40", "surround41", "surround50", "surround51", "surround71",
|
||||
"sysdefault", "pulse", "null", "dsnoop", "dmix", "hw", "iec958"};
|
||||
"sysdefault", "pulse", "null", "dsnoop", "dmix", "hw", "iec958",
|
||||
"default"};
|
||||
for (int i = 0; i < MP_ARRAY_SIZE(crap); i++) {
|
||||
int l = strlen(crap[i]);
|
||||
if (name && strncmp(name, crap[i], l) == 0 &&
|
||||
@@ -1102,6 +1156,8 @@ static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
if (snd_device_name_hint(-1, "pcm", &hints) < 0)
|
||||
return;
|
||||
|
||||
ao_device_list_add(list, ao, &(struct ao_device_desc){"", ""});
|
||||
|
||||
for (int n = 0; hints[n]; n++) {
|
||||
char *name = snd_device_name_get_hint(hints[n], "NAME");
|
||||
char *desc = snd_device_name_get_hint(hints[n], "DESC");
|
||||
@@ -1123,8 +1179,6 @@ static void list_devs(struct ao *ao, struct ao_device_list *list)
|
||||
snd_device_name_free_hint(hints);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_alsa = {
|
||||
.description = "ALSA audio output",
|
||||
.name = "alsa",
|
||||
@@ -1142,20 +1196,5 @@ const struct ao_driver audio_out_alsa = {
|
||||
.wakeup = ao_wakeup_poll,
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.cfg_mixer_device = "default",
|
||||
.cfg_mixer_name = "Master",
|
||||
.cfg_mixer_index = 0,
|
||||
.cfg_ni = 0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("device", cfg_device, 0),
|
||||
OPT_FLAG("resample", cfg_resample, 0),
|
||||
OPT_STRING("mixer-device", cfg_mixer_device, 0),
|
||||
OPT_STRING("mixer-name", cfg_mixer_name, 0),
|
||||
OPT_INTRANGE("mixer-index", cfg_mixer_index, 0, 0, 99),
|
||||
OPT_FLAG("non-interleaved", cfg_ni, 0),
|
||||
OPT_FLAG("ignore-chmap", cfg_ignore_chmap, 0),
|
||||
{0}
|
||||
},
|
||||
.global_opts = &ao_alsa_conf,
|
||||
};
|
||||
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "ao.h"
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_option.h"
|
||||
#include "misc/ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "ao_coreaudio_utils.h"
|
||||
#include "ao_coreaudio_chmap.h"
|
||||
|
||||
#import <AudioUnit/AudioUnit.h>
|
||||
#import <CoreAudio/CoreAudioTypes.h>
|
||||
#import <AudioToolbox/AudioToolbox.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
#import <mach/mach_time.h>
|
||||
|
||||
struct priv {
|
||||
AudioUnit audio_unit;
|
||||
double device_latency;
|
||||
};
|
||||
|
||||
static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
||||
const AudioTimeStamp *ts, UInt32 bus,
|
||||
UInt32 frames, AudioBufferList *buffer_list)
|
||||
{
|
||||
struct ao *ao = ctx;
|
||||
struct priv *p = ao->priv;
|
||||
void *planes[MP_NUM_CHANNELS] = {0};
|
||||
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
planes[n] = buffer_list->mBuffers[n].mData;
|
||||
|
||||
int64_t end = mp_time_us();
|
||||
end += ca_frames_to_us(ao, frames);
|
||||
end += p->device_latency * 1e6;
|
||||
ao_read_data(ao, planes, frames, end);
|
||||
return noErr;
|
||||
}
|
||||
|
||||
static bool init_audiounit(struct ao *ao)
|
||||
{
|
||||
AudioStreamBasicDescription asbd;
|
||||
OSStatus err;
|
||||
uint32_t size;
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
AVAudioSessionPortDescription *port = nil;
|
||||
NSInteger maxChannels = instance.maximumOutputNumberOfChannels;
|
||||
NSInteger prefChannels = MIN(maxChannels, ao->channels.num);
|
||||
|
||||
[instance setCategory:AVAudioSessionCategoryPlayback error:nil];
|
||||
[instance setMode:AVAudioSessionModeMoviePlayback error:nil];
|
||||
[instance setActive:YES error:nil];
|
||||
[instance setPreferredOutputNumberOfChannels:prefChannels error:nil];
|
||||
|
||||
AudioComponentDescription desc = (AudioComponentDescription) {
|
||||
.componentType = kAudioUnitType_Output,
|
||||
.componentSubType = kAudioUnitSubType_RemoteIO,
|
||||
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
||||
.componentFlags = 0,
|
||||
.componentFlagsMask = 0,
|
||||
};
|
||||
|
||||
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
|
||||
if (comp == NULL) {
|
||||
MP_ERR(ao, "unable to find audio component\n");
|
||||
goto coreaudio_error;
|
||||
}
|
||||
|
||||
err = AudioComponentInstanceNew(comp, &(p->audio_unit));
|
||||
CHECK_CA_ERROR("unable to open audio component");
|
||||
|
||||
err = AudioUnitInitialize(p->audio_unit);
|
||||
CHECK_CA_ERROR_L(coreaudio_error_component,
|
||||
"unable to initialize audio unit");
|
||||
|
||||
if (af_fmt_is_spdif(ao->format) || instance.outputNumberOfChannels <= 2) {
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO;
|
||||
} else {
|
||||
port = instance.currentRoute.outputs.firstObject;
|
||||
if (port.channels.count == 2 &&
|
||||
port.channels[0].channelLabel == kAudioChannelLabel_Unknown) {
|
||||
// Special case when using an HDMI adapter. The iOS device will
|
||||
// perform SPDIF conversion for us, so send all available channels
|
||||
// using the AC3 mapping.
|
||||
ao->channels = (struct mp_chmap)MP_CHMAP6(FL, FC, FR, SL, SR, LFE);
|
||||
} else {
|
||||
ao->channels.num = (uint8_t)port.channels.count;
|
||||
for (AVAudioSessionChannelDescription *ch in port.channels) {
|
||||
ao->channels.speaker[ch.channelNumber - 1] =
|
||||
ca_label_to_mp_speaker_id(ch.channelLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ca_fill_asbd(ao, &asbd);
|
||||
size = sizeof(AudioStreamBasicDescription);
|
||||
err = AudioUnitSetProperty(p->audio_unit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input, 0, &asbd, size);
|
||||
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set the input format on the audio unit");
|
||||
|
||||
AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
|
||||
.inputProc = render_cb_lpcm,
|
||||
.inputProcRefCon = ao,
|
||||
};
|
||||
|
||||
err = AudioUnitSetProperty(p->audio_unit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input, 0, &render_cb,
|
||||
sizeof(AURenderCallbackStruct));
|
||||
|
||||
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
||||
"unable to set render callback on audio unit");
|
||||
|
||||
return true;
|
||||
|
||||
coreaudio_error_audiounit:
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
coreaudio_error_component:
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
coreaudio_error:
|
||||
return false;
|
||||
}
|
||||
|
||||
static void stop(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
OSStatus err = AudioOutputUnitStop(p->audio_unit);
|
||||
CHECK_CA_WARN("can't stop audio unit");
|
||||
}
|
||||
|
||||
static void start(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
AVAudioSession *instance = AVAudioSession.sharedInstance;
|
||||
|
||||
p->device_latency = [instance outputLatency];
|
||||
|
||||
OSStatus err = AudioOutputUnitStart(p->audio_unit);
|
||||
CHECK_CA_WARN("can't start audio unit");
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
AudioOutputUnitStop(p->audio_unit);
|
||||
AudioUnitUninitialize(p->audio_unit);
|
||||
AudioComponentInstanceDispose(p->audio_unit);
|
||||
|
||||
[AVAudioSession.sharedInstance
|
||||
setActive:NO
|
||||
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
|
||||
error:nil];
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
if (!init_audiounit(ao))
|
||||
goto coreaudio_error;
|
||||
|
||||
return CONTROL_OK;
|
||||
|
||||
coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_audiounit = {
|
||||
.description = "AudioUnit (iOS)",
|
||||
.name = "audiounit",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.pause = stop,
|
||||
.resume = start,
|
||||
.priv_size = sizeof(struct priv),
|
||||
};
|
||||
@@ -39,7 +39,6 @@ struct priv {
|
||||
AudioStreamID original_asbd_stream;
|
||||
|
||||
int change_physical_format;
|
||||
int exclusive;
|
||||
};
|
||||
|
||||
static int64_t ca_get_hardware_latency(struct ao *ao) {
|
||||
@@ -143,7 +142,7 @@ static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (!af_fmt_is_pcm(ao->format) || p->exclusive) {
|
||||
if (!af_fmt_is_pcm(ao->format) || (ao->init_flags & AO_INIT_EXCLUSIVE)) {
|
||||
MP_VERBOSE(ao, "redirecting to coreaudio_exclusive\n");
|
||||
ao->redirect = "coreaudio_exclusive";
|
||||
return CONTROL_ERROR;
|
||||
@@ -427,7 +426,7 @@ const struct ao_driver audio_out_coreaudio = {
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]){
|
||||
OPT_FLAG("change-physical-format", change_physical_format, 0),
|
||||
OPT_FLAG("exclusive", exclusive, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "coreaudio",
|
||||
};
|
||||
@@ -56,7 +56,7 @@ static const int speaker_map[][2] = {
|
||||
{ 0, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][1] >= 0; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
@@ -64,6 +64,7 @@ static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
|
||||
{
|
||||
if (!mp_msg_test(ao->log, l))
|
||||
@@ -327,3 +328,4 @@ void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
||||
MP_WARN(ao, "mismatching channels - falling back to %s\n",
|
||||
mp_chmap_to_str(out_map));
|
||||
}
|
||||
#endif
|
||||
@@ -22,8 +22,12 @@
|
||||
|
||||
struct mp_chmap;
|
||||
|
||||
int ca_label_to_mp_speaker_id(AudioChannelLabel label);
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
bool ca_init_chmap(struct ao *ao, AudioDeviceID device);
|
||||
void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
|
||||
struct mp_chmap *out_map);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -41,7 +41,7 @@
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao_coreaudio_chmap.h"
|
||||
|
||||
@@ -22,15 +22,17 @@
|
||||
* on CoreAudio but not the AUHAL (such as using AudioQueue services).
|
||||
*/
|
||||
|
||||
#include <CoreAudio/HostTime.h>
|
||||
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/endian.h"
|
||||
#include "osdep/semaphore.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include <CoreAudio/HostTime.h>
|
||||
#endif
|
||||
|
||||
CFStringRef cfstr_from_cstr(char *str)
|
||||
{
|
||||
return CFStringCreateWithCString(NULL, str, CA_CFSTR_ENCODING);
|
||||
@@ -46,6 +48,7 @@ char *cfstr_get_cstr(CFStringRef cfstr)
|
||||
return buffer;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
static bool ca_is_output_device(struct ao *ao, AudioDeviceID dev)
|
||||
{
|
||||
size_t n_buffers;
|
||||
@@ -142,6 +145,7 @@ OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device)
|
||||
coreaudio_error:
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
{
|
||||
@@ -306,6 +310,7 @@ int64_t ca_frames_to_us(struct ao *ao, uint32_t frames)
|
||||
return frames / (float) ao->samplerate * 1e6;
|
||||
}
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts)
|
||||
{
|
||||
uint64_t out = AudioConvertHostTimeToNanos(ts->mHostTime);
|
||||
@@ -522,4 +527,4 @@ coreaudio_error:
|
||||
sem_destroy(&wakeup);
|
||||
return format_set;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -49,7 +49,9 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
|
||||
} while (0)
|
||||
|
||||
void ca_get_device_list(struct ao *ao, struct ao_device_list *list);
|
||||
#if HAVE_COREAUDIO
|
||||
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device);
|
||||
#endif
|
||||
|
||||
bool ca_formatid_is_compressed(uint32_t formatid);
|
||||
void ca_fill_asbd(struct ao *ao, AudioStreamBasicDescription *asbd);
|
||||
@@ -63,8 +65,11 @@ bool ca_asbd_is_better(AudioStreamBasicDescription *req,
|
||||
AudioStreamBasicDescription *new);
|
||||
|
||||
int64_t ca_frames_to_us(struct ao *ao, uint32_t frames);
|
||||
#if HAVE_COREAUDIO
|
||||
int64_t ca_get_latency(const AudioTimeStamp *ts);
|
||||
#endif
|
||||
|
||||
#if HAVE_COREAUDIO
|
||||
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);
|
||||
@@ -73,5 +78,6 @@ OSStatus ca_enable_mixing(struct ao *ao, AudioDeviceID device, bool changed);
|
||||
int64_t ca_get_device_latency_us(struct ao *ao, AudioDeviceID device);
|
||||
bool ca_change_physical_format_sync(struct ao *ao, AudioStreamID stream,
|
||||
AudioStreamBasicDescription change_format);
|
||||
#endif
|
||||
|
||||
#endif /* MPV_COREAUDIO_UTILS_H */
|
||||
+37
-25
@@ -34,24 +34,48 @@
|
||||
#include "internal.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
struct priv {
|
||||
jack_client_t *client;
|
||||
float jack_latency;
|
||||
char *cfg_port;
|
||||
char *cfg_client_name;
|
||||
struct jack_opts {
|
||||
char *port;
|
||||
char *client_name;
|
||||
int connect;
|
||||
int autostart;
|
||||
int stdlayout;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct jack_opts
|
||||
static const struct m_sub_options ao_jack_conf = {
|
||||
.opts = (const struct m_option[]){
|
||||
OPT_STRING("jack-port", port, 0),
|
||||
OPT_STRING("jack-name", client_name, 0),
|
||||
OPT_FLAG("jack-autostart", autostart, 0),
|
||||
OPT_FLAG("jack-connect", connect, 0),
|
||||
OPT_CHOICE("jack-std-channel-layout", stdlayout, 0,
|
||||
({"waveext", 0}, {"any", 1})),
|
||||
{0}
|
||||
},
|
||||
.defaults = &(const struct jack_opts) {
|
||||
.client_name = "mpv",
|
||||
.connect = 1,
|
||||
},
|
||||
.size = sizeof(struct jack_opts),
|
||||
};
|
||||
|
||||
struct priv {
|
||||
jack_client_t *client;
|
||||
float jack_latency;
|
||||
int last_chunk;
|
||||
|
||||
int num_ports;
|
||||
jack_port_t *ports[MP_NUM_CHANNELS];
|
||||
|
||||
int activated;
|
||||
|
||||
struct jack_opts *opts;
|
||||
};
|
||||
|
||||
static int process(jack_nframes_t nframes, void *arg)
|
||||
@@ -77,7 +101,7 @@ connect_to_outports(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
char *port_name = (p->cfg_port && p->cfg_port[0]) ? p->cfg_port : NULL;
|
||||
char *port_name = (p->opts->port && p->opts->port[0]) ? p->opts->port : NULL;
|
||||
const char **matching_ports = NULL;
|
||||
int port_flags = JackPortIsInput;
|
||||
int i;
|
||||
@@ -144,7 +168,7 @@ static void resume(struct ao *ao)
|
||||
if (jack_activate(p->client))
|
||||
MP_FATAL(ao, "activate failed\n");
|
||||
|
||||
if (p->connect)
|
||||
if (p->opts->connect)
|
||||
connect_to_outports(ao);
|
||||
}
|
||||
}
|
||||
@@ -155,9 +179,11 @@ static int init(struct ao *ao)
|
||||
struct mp_chmap_sel sel = {0};
|
||||
jack_options_t open_options;
|
||||
|
||||
p->opts = mp_get_config_group(ao, ao->global, &ao_jack_conf);
|
||||
|
||||
ao->format = AF_FORMAT_FLOATP;
|
||||
|
||||
switch (p->stdlayout) {
|
||||
switch (p->opts->stdlayout) {
|
||||
case 0:
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
break;
|
||||
@@ -170,10 +196,10 @@ static int init(struct ao *ao)
|
||||
goto err_chmap;
|
||||
|
||||
open_options = JackNullOption;
|
||||
if (!p->autostart)
|
||||
if (!p->opts->autostart)
|
||||
open_options |= JackNoStartServer;
|
||||
|
||||
p->client = jack_client_open(p->cfg_client_name, open_options, NULL);
|
||||
p->client = jack_client_open(p->opts->client_name, open_options, NULL);
|
||||
if (!p->client) {
|
||||
MP_FATAL(ao, "cannot open server\n");
|
||||
goto err_client_open;
|
||||
@@ -213,8 +239,6 @@ static void uninit(struct ao *ao)
|
||||
jack_client_close(p->client);
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_jack = {
|
||||
.description = "JACK audio output",
|
||||
.name = "jack",
|
||||
@@ -222,17 +246,5 @@ const struct ao_driver audio_out_jack = {
|
||||
.uninit = uninit,
|
||||
.resume = resume,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.cfg_client_name = "mpv",
|
||||
.connect = 1,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("port", cfg_port, 0),
|
||||
OPT_STRING("name", cfg_client_name, 0),
|
||||
OPT_FLAG("autostart", autostart, 0),
|
||||
OPT_FLAG("connect", connect, 0),
|
||||
OPT_CHOICE("std-channel-layout", stdlayout, 0,
|
||||
({"waveext", 0}, {"any", 1})),
|
||||
{0}
|
||||
},
|
||||
.global_opts = &ao_jack_conf,
|
||||
};
|
||||
@@ -258,7 +258,6 @@ static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
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",
|
||||
@@ -297,28 +296,6 @@ static void encode_audio_and_write(struct ao *ao, AVFrame *frame)
|
||||
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
|
||||
|
||||
@@ -241,4 +241,5 @@ const struct ao_driver audio_out_null = {
|
||||
OPT_CHANNELS("channel-layouts", channel_layouts, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "ao-null",
|
||||
};
|
||||
+18
-2
@@ -24,6 +24,19 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#ifndef AL_FORMAT_MONO_FLOAT32
|
||||
#define AL_FORMAT_MONO_FLOAT32 0x10010
|
||||
#endif
|
||||
#ifndef AL_FORMAT_STEREO_FLOAT32
|
||||
#define AL_FORMAT_STEREO_FLOAT32 0x10011
|
||||
#endif
|
||||
#ifndef AL_FORMAT_MONO_DOUBLE_EXT
|
||||
#define AL_FORMAT_MONO_DOUBLE_EXT 0x10012
|
||||
#endif
|
||||
#include <OpenAL/MacOSX_OALExtensions.h>
|
||||
#else
|
||||
#ifdef OPENAL_AL_H
|
||||
#include <OpenAL/alc.h>
|
||||
#include <OpenAL/al.h>
|
||||
@@ -33,6 +46,7 @@
|
||||
#include <AL/al.h>
|
||||
#include <AL/alext.h>
|
||||
#endif
|
||||
#endif // __APPLE__
|
||||
|
||||
#include "common/msg.h"
|
||||
|
||||
@@ -96,7 +110,7 @@ static int validate_device_opt(struct mp_log *log, const m_option_t *opt,
|
||||
mp_info(log, " '%s'\n", list);
|
||||
list = list + strlen(list) + 1;
|
||||
}
|
||||
return M_OPT_EXIT - 1;
|
||||
return M_OPT_EXIT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -356,7 +370,9 @@ const struct ao_driver audio_out_openal = {
|
||||
.list_devs = list_devs,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING_VALIDATE("device", cfg_device, 0, validate_device_opt),
|
||||
OPT_STRING_VALIDATE("device", cfg_device, 0, validate_device_opt,
|
||||
DEVICE_OPT_DEPRECATION),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "ao-openal",
|
||||
};
|
||||
@@ -246,4 +246,5 @@ const struct ao_driver audio_out_opensles = {
|
||||
OPT_INTRANGE("sample-rate", cfg_sample_rate, 0, 1000, 100000),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "opensles",
|
||||
};
|
||||
+2
-1
@@ -648,9 +648,10 @@ const struct ao_driver audio_out_oss = {
|
||||
.oss_mixer_device = PATH_DEV_MIXER,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("device", dsp, 0),
|
||||
OPT_STRING("device", dsp, 0, DEVICE_OPT_DEPRECATION),
|
||||
OPT_STRING("mixer-device", oss_mixer_device, 0),
|
||||
OPT_STRING("mixer-channel", cfg_oss_mixer_channel, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "oss",
|
||||
};
|
||||
+1
-2
@@ -152,8 +152,6 @@ static int init(struct ao *ao)
|
||||
priv->outputfilename,
|
||||
priv->waveheader ? "WAVE" : "RAW PCM", ao->samplerate,
|
||||
ao->channels.num, af_fmt_to_str(ao->format));
|
||||
MP_INFO(ao, "Info: Faster dumping is achieved with --no-video\n");
|
||||
MP_INFO(ao, "Info: To write WAVE files use --ao=pcm:waveheader (default).\n");
|
||||
|
||||
priv->fp = fopen(priv->outputfilename, priv->append ? "ab" : "wb");
|
||||
if (!priv->fp) {
|
||||
@@ -226,4 +224,5 @@ const struct ao_driver audio_out_pcm = {
|
||||
OPT_FLAG("append", append, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "ao-pcm",
|
||||
};
|
||||
@@ -836,9 +836,10 @@ const struct ao_driver audio_out_pulse = {
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("host", cfg_host, 0),
|
||||
OPT_STRING("sink", cfg_sink, 0),
|
||||
OPT_STRING("sink", cfg_sink, 0, DEVICE_OPT_DEPRECATION),
|
||||
OPT_CHOICE_OR_INT("buffer", cfg_buffer, 0, 1, 2000, ({"native", 0})),
|
||||
OPT_FLAG("latency-hacks", cfg_latency_hacks, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "pulse",
|
||||
};
|
||||
@@ -156,9 +156,12 @@ const struct ao_driver audio_out_rsound = {
|
||||
.resume = audio_resume,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("host", host, 0),
|
||||
OPT_STRING("port", port, 0),
|
||||
OPT_STRING("host", host, 0,
|
||||
.deprecation_message = "request --audio-device support on issue tracker"),
|
||||
OPT_STRING("port", port, 0,
|
||||
.deprecation_message = "request --audio-device support on issue tracker"),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "rsound",
|
||||
};
|
||||
|
||||
@@ -212,4 +212,5 @@ const struct ao_driver audio_out_sdl = {
|
||||
OPT_FLOAT("buflen", buflen, 0),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "sdl",
|
||||
};
|
||||
@@ -320,7 +320,9 @@ const struct ao_driver audio_out_sndio = {
|
||||
.reset = reset,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("device", dev, 0, OPTDEF_STR(SIO_DEVANY)),
|
||||
OPT_STRING("device", dev, 0, OPTDEF_STR(SIO_DEVANY),
|
||||
DEVICE_OPT_DEPRECATION),
|
||||
{0}
|
||||
},
|
||||
.options_prefix = "ao-sndio",
|
||||
};
|
||||
@@ -275,6 +275,8 @@ static int init(struct ao *ao)
|
||||
struct wasapi_state *state = ao->priv;
|
||||
state->log = ao->log;
|
||||
|
||||
state->opt_exclusive |= ao->init_flags & AO_INIT_EXCLUSIVE;
|
||||
|
||||
state->deviceID = wasapi_find_deviceID(ao);
|
||||
if (!state->deviceID) {
|
||||
uninit(ao);
|
||||
@@ -494,9 +496,4 @@ const struct ao_driver audio_out_wasapi = {
|
||||
.hotplug_init = hotplug_init,
|
||||
.hotplug_uninit = hotplug_uninit,
|
||||
.priv_size = sizeof(wasapi_state),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("exclusive", opt_exclusive, 0),
|
||||
OPT_STRING("device", opt_device, 0),
|
||||
{NULL},
|
||||
},
|
||||
};
|
||||
@@ -29,7 +29,7 @@
|
||||
#include <endpointvolume.h>
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "osdep/windows_utils.h"
|
||||
#include "internal.h"
|
||||
#include "ao.h"
|
||||
@@ -50,6 +50,10 @@ void wasapi_change_uninit(struct ao* ao);
|
||||
#define SAFE_RELEASE(unk, release) \
|
||||
do { if ((unk) != NULL) { release; (unk) = NULL; } } while(0)
|
||||
|
||||
#define mp_format_res_str(hres) \
|
||||
(SUCCEEDED(hres) ? "ok" : ((hres) == AUDCLNT_E_UNSUPPORTED_FORMAT) \
|
||||
? "unsupported" : mp_HRESULT_to_str(hres))
|
||||
|
||||
enum wasapi_thread_state {
|
||||
WASAPI_THREAD_FEED = 0,
|
||||
WASAPI_THREAD_RESUME,
|
||||
@@ -92,8 +96,6 @@ typedef struct wasapi_state {
|
||||
|
||||
// ao options
|
||||
int opt_exclusive;
|
||||
int opt_list;
|
||||
char *opt_device;
|
||||
|
||||
// format info
|
||||
WAVEFORMATEXTENSIBLE format;
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <initguid.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "ao_wasapi.h"
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include <math.h>
|
||||
#include <wchar.h>
|
||||
#include <windows.h>
|
||||
#include <initguid.h>
|
||||
#include <errors.h>
|
||||
#include <ksguid.h>
|
||||
#include <ksmedia.h>
|
||||
@@ -290,18 +289,12 @@ static bool set_ao_format(struct ao *ao, WAVEFORMATEX *wf,
|
||||
static bool try_format_exclusive(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
MP_VERBOSE(ao, "Trying %s (exclusive)\n",
|
||||
waveformat_to_str(&wformat->Format));
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
||||
&wformat->Format, NULL);
|
||||
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
MP_VERBOSE(ao, "Trying %s (exclusive) -> %s\n",
|
||||
waveformat_to_str(&wformat->Format), mp_format_res_str(hr));
|
||||
return SUCCEEDED(hr);
|
||||
exit_label:
|
||||
MP_ERR(state, "Error testing exclusive format: %s\n", mp_HRESULT_to_str(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
// This works like try_format_exclusive(), but will try to fallback to the AC3
|
||||
@@ -394,11 +387,8 @@ static bool search_channels(struct ao *ao, WAVEFORMATEXTENSIBLE *wformat)
|
||||
for (int j = 0; channel_layouts[j]; j++) {
|
||||
mp_chmap_from_str(&entry, bstr0(channel_layouts[j]));
|
||||
if (!wformat->Format.nSamplesPerSec) {
|
||||
if (search_samplerates(ao, wformat, &entry)) {
|
||||
if (search_samplerates(ao, wformat, &entry))
|
||||
mp_chmap_sel_add_map(&chmap_sel, &entry);
|
||||
MP_VERBOSE(ao, "%s is supported\n",
|
||||
waveformat_to_str(&wformat->Format));
|
||||
}
|
||||
} else {
|
||||
change_waveformat_channels(wformat, &entry);
|
||||
if (try_format_exclusive(ao, wformat))
|
||||
@@ -443,11 +433,12 @@ static bool find_formats_shared(struct ao *ao)
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
set_waveformat_with_ao(&wformat, ao);
|
||||
|
||||
MP_VERBOSE(ao, "Trying %s (shared)\n", waveformat_to_str(&wformat.Format));
|
||||
WAVEFORMATEX *closestMatch;
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
AUDCLNT_SHAREMODE_SHARED,
|
||||
&wformat.Format, &closestMatch);
|
||||
MP_VERBOSE(ao, "Trying %s (shared) -> %s\n",
|
||||
waveformat_to_str(&wformat.Format), mp_format_res_str(hr));
|
||||
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
@@ -858,11 +849,7 @@ static LPWSTR select_device(struct mp_log *l, struct device_desc *d)
|
||||
|
||||
bstr wasapi_get_specified_device_string(struct ao *ao)
|
||||
{
|
||||
struct wasapi_state *state = ao->priv;
|
||||
bstr device = bstr_strip(bstr0(state->opt_device));
|
||||
if (!device.len)
|
||||
device = bstr_strip(bstr0(ao->device));
|
||||
return device;
|
||||
return bstr_strip(bstr0(ao->device));
|
||||
}
|
||||
|
||||
LPWSTR wasapi_find_deviceID(struct ao *ao)
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "audio/out/ao.h"
|
||||
|
||||
/* global data used by ao.c and ao drivers */
|
||||
@@ -40,8 +40,10 @@ struct ao {
|
||||
const struct ao_driver *api; // entrypoints to the wrapper (push.c/pull.c)
|
||||
const struct ao_driver *driver;
|
||||
void *priv;
|
||||
struct mpv_global *global;
|
||||
struct encode_lavc_context *encode_lavc_ctx;
|
||||
struct input_ctx *input_ctx;
|
||||
void (*wakeup_cb)(void *ctx);
|
||||
void *wakeup_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
|
||||
@@ -180,6 +182,8 @@ struct ao_driver {
|
||||
int priv_size;
|
||||
const void *priv_defaults;
|
||||
const struct m_option *options;
|
||||
const char *options_prefix;
|
||||
const struct m_sub_options *global_opts;
|
||||
};
|
||||
|
||||
// These functions can be called by AOs.
|
||||
@@ -203,4 +207,7 @@ bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
void ao_device_list_add(struct ao_device_list *list, struct ao *ao,
|
||||
struct ao_device_desc *e);
|
||||
|
||||
#define DEVICE_OPT_DEPRECATION \
|
||||
.deprecation_message = "use --audio-device instead"
|
||||
|
||||
#endif
|
||||
+6
-4
@@ -30,7 +30,7 @@
|
||||
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/threads.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "misc/ring.h"
|
||||
|
||||
/*
|
||||
@@ -101,7 +101,8 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
int state = atomic_load(&p->state);
|
||||
if (!IS_PLAYING(state)) {
|
||||
set_state(ao, AO_STATE_PLAY);
|
||||
ao->driver->resume(ao);
|
||||
if (!ao->stream_silence)
|
||||
ao->driver->resume(ao);
|
||||
}
|
||||
|
||||
return write_samples;
|
||||
@@ -151,7 +152,7 @@ int ao_read_data(struct ao *ao, void **data, int samples, int64_t out_time_us)
|
||||
end:
|
||||
|
||||
if (need_wakeup)
|
||||
mp_input_wakeup_nolock(ao->input_ctx);
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
|
||||
// pad with silence (underflow/paused/eof)
|
||||
for (int n = 0; n < ao->num_planes; n++)
|
||||
@@ -203,7 +204,8 @@ static void pause(struct ao *ao)
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
set_state(ao, AO_STATE_PLAY);
|
||||
ao->driver->resume(ao);
|
||||
if (!ao->stream_silence)
|
||||
ao->driver->resume(ao);
|
||||
}
|
||||
|
||||
static bool get_eof(struct ao *ao)
|
||||
|
||||
+42
-12
@@ -35,7 +35,7 @@
|
||||
|
||||
#include "osdep/threads.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/atomic.h"
|
||||
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
@@ -49,6 +49,8 @@ struct ao_push_state {
|
||||
|
||||
struct mp_audio_buffer *buffer;
|
||||
|
||||
struct mp_audio *silence;
|
||||
|
||||
bool terminate;
|
||||
bool wait_on_ao;
|
||||
bool still_playing;
|
||||
@@ -182,6 +184,7 @@ static int unlocked_get_space(struct ao *ao)
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
int space = mp_audio_buffer_get_write_available(p->buffer);
|
||||
if (ao->driver->get_space) {
|
||||
int align = af_format_sample_alignment(ao->format);
|
||||
// The following code attempts to keep the total buffered audio to
|
||||
// ao->buffer in order to improve latency.
|
||||
int device_space = ao->driver->get_space(ao);
|
||||
@@ -191,6 +194,7 @@ static int unlocked_get_space(struct ao *ao)
|
||||
// byte based and doesn't do proper chunked processing.
|
||||
int min_buffer = ao->buffer + 64;
|
||||
int missing = min_buffer - device_buffered - soft_buffered;
|
||||
missing = (missing + align - 1) / align * align;
|
||||
// But always keep the device's buffer filled as much as we can.
|
||||
int device_missing = device_space - soft_buffered;
|
||||
missing = MPMAX(missing, device_missing);
|
||||
@@ -257,15 +261,38 @@ static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
return write_samples;
|
||||
}
|
||||
|
||||
static void ao_get_silence(struct ao *ao, struct mp_audio *data, int size)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
if (!p->silence) {
|
||||
p->silence = talloc_zero(p, struct mp_audio);
|
||||
mp_audio_set_format(p->silence, ao->format);
|
||||
mp_audio_set_channels(p->silence, &ao->channels);
|
||||
p->silence->rate = ao->samplerate;
|
||||
}
|
||||
if (p->silence->samples < size) {
|
||||
mp_audio_realloc_min(p->silence, size);
|
||||
p->silence->samples = size;
|
||||
mp_audio_fill_silence(p->silence, 0, size);
|
||||
}
|
||||
*data = *p->silence;
|
||||
data->samples = size;
|
||||
}
|
||||
|
||||
// called locked
|
||||
static void ao_play_data(struct ao *ao)
|
||||
{
|
||||
struct ao_push_state *p = ao->api_priv;
|
||||
struct mp_audio data;
|
||||
mp_audio_buffer_peek(p->buffer, &data);
|
||||
int max = data.samples;
|
||||
int space = ao->driver->get_space(ao);
|
||||
bool play_silence = p->paused || (ao->stream_silence && !p->still_playing);
|
||||
space = MPMAX(space, 0);
|
||||
struct mp_audio data;
|
||||
if (play_silence) {
|
||||
ao_get_silence(ao, &data, space);
|
||||
} else {
|
||||
mp_audio_buffer_peek(p->buffer, &data);
|
||||
}
|
||||
int max = data.samples;
|
||||
if (data.samples > space)
|
||||
data.samples = space;
|
||||
int flags = 0;
|
||||
@@ -287,7 +314,8 @@ static void ao_play_data(struct ao *ao)
|
||||
MP_ERR(ao, "Audio output driver seems to ignore AOPLAY_FINAL_CHUNK.\n");
|
||||
r = max;
|
||||
}
|
||||
mp_audio_buffer_skip(p->buffer, r);
|
||||
if (!play_silence)
|
||||
mp_audio_buffer_skip(p->buffer, r);
|
||||
if (r > 0)
|
||||
p->expected_end_time = 0;
|
||||
// Nothing written, but more input data than space - this must mean the
|
||||
@@ -298,14 +326,14 @@ static void ao_play_data(struct ao *ao)
|
||||
// Wait until space becomes available. Also wait if we actually wrote data,
|
||||
// so the AO wakes us up properly if it needs more data.
|
||||
p->wait_on_ao = space == 0 || r > 0 || stuck;
|
||||
p->still_playing |= r > 0;
|
||||
p->still_playing |= r > 0 && !play_silence;
|
||||
// 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 &&
|
||||
!(flags & AOPLAY_FINAL_CHUNK);
|
||||
if (more)
|
||||
mp_input_wakeup(ao->input_ctx); // request more data
|
||||
ao->wakeup_cb(ao->wakeup_ctx); // request more data
|
||||
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);
|
||||
}
|
||||
@@ -317,12 +345,13 @@ static void *playthread(void *arg)
|
||||
mpthread_set_name("ao");
|
||||
pthread_mutex_lock(&p->lock);
|
||||
while (!p->terminate) {
|
||||
if (!p->paused)
|
||||
bool playing = !p->paused || ao->stream_silence;
|
||||
if (playing)
|
||||
ao_play_data(ao);
|
||||
|
||||
if (!p->need_wakeup) {
|
||||
MP_STATS(ao, "start audio wait");
|
||||
if (!p->wait_on_ao || p->paused) {
|
||||
if (!p->wait_on_ao || !playing) {
|
||||
// Avoid busy waiting, because the audio API will still report
|
||||
// that it needs new data, even if we're not ready yet, or if
|
||||
// get_space() decides that the amount of audio buffered in the
|
||||
@@ -347,7 +376,7 @@ static void *playthread(void *arg)
|
||||
}
|
||||
|
||||
if (was_playing && !p->still_playing)
|
||||
mp_input_wakeup(ao->input_ctx);
|
||||
ao->wakeup_cb(ao->wakeup_ctx);
|
||||
pthread_cond_signal(&p->wakeup); // for draining
|
||||
|
||||
if (p->still_playing && timeout > 0) {
|
||||
@@ -451,8 +480,9 @@ int ao_play_silence(struct ao *ao, int samples)
|
||||
assert(ao->api == &ao_api_push);
|
||||
if (samples <= 0 || !af_fmt_is_pcm(ao->format) || !ao->driver->play)
|
||||
return 0;
|
||||
char *p = talloc_size(NULL, samples * ao->sstride);
|
||||
af_fill_silence(p, samples * ao->sstride, ao->format);
|
||||
int bytes = af_fmt_to_bytes(ao->format) * samples * ao->channels.num;
|
||||
char *p = talloc_size(NULL, bytes);
|
||||
af_fill_silence(p, bytes, ao->format);
|
||||
void *tmp[MP_NUM_CHANNELS];
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
tmp[n] = p;
|
||||
|
||||
+34
-7
@@ -16,6 +16,8 @@
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/log.h>
|
||||
@@ -75,13 +77,35 @@ void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st)
|
||||
// other demuxers must be handled manually.
|
||||
void mp_set_lav_codec_headers(AVCodecContext *avctx, struct mp_codec_params *c)
|
||||
{
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (c->lav_codecpar)
|
||||
avcodec_parameters_to_context(avctx, c->lav_codecpar);
|
||||
#else
|
||||
if (c->lav_headers)
|
||||
mp_copy_lav_codec_headers(avctx, c->lav_headers);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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, INT_MAX);
|
||||
|
||||
if (tb.num < 1 || tb.den < 1)
|
||||
tb = AV_TIME_BASE_Q;
|
||||
|
||||
return tb;
|
||||
}
|
||||
|
||||
// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it.
|
||||
@@ -97,8 +121,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;
|
||||
@@ -133,6 +159,7 @@ void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb)
|
||||
dst->side_data_elems = mpkt->avpacket->side_data_elems;
|
||||
if (dst->data == mpkt->avpacket->data)
|
||||
dst->buf = mpkt->avpacket->buf;
|
||||
dst->flags |= mpkt->avpacket->flags;
|
||||
}
|
||||
if (mpkt && tb && tb->num > 0 && tb->den > 0)
|
||||
dst->duration = mpkt->duration / av_q2d(*tb);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -218,9 +218,7 @@ bool print_libav_versions(struct mp_log *log, int v)
|
||||
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;
|
||||
}
|
||||
|
||||
+19
-27
@@ -45,9 +45,10 @@ static struct mp_decoder_entry *find_decoder(struct mp_decoder_list *list,
|
||||
{
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *cur = &list->entries[n];
|
||||
if (bstr_equals0(decoder, cur->decoder) &&
|
||||
bstr_equals0(family, cur->family))
|
||||
return cur;
|
||||
if (bstr_equals0(decoder, cur->decoder)) {
|
||||
if (bstr_equals0(family, "*") || bstr_equals0(family, cur->family))
|
||||
return cur;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
@@ -70,13 +71,14 @@ static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry,
|
||||
// The selection string corresponds to --vd/--ad directly, and has the
|
||||
// following syntax:
|
||||
// selection = [<entry> ("," <entry>)*]
|
||||
// entry = <family> ":" <decoder> // prefer decoder
|
||||
// entry = [<family> ":"] <decoder> // prefer decoder
|
||||
// entry = <family> ":*" // prefer all decoders
|
||||
// entry = "+" <family> ":" <decoder> // force a decoder
|
||||
// entry = "-" <family> ":" <decoder> // exclude a decoder
|
||||
// entry = "+" [<family> ":"] <decoder> // force a decoder
|
||||
// entry = "-" [<family> ":"] <decoder> // exclude a decoder
|
||||
// entry = "-" // don't add fallback decoders
|
||||
// Forcing a decoder means it's added even if the codec mismatches.
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
struct mp_decoder_list *all,
|
||||
const char *codec,
|
||||
const char *selection)
|
||||
{
|
||||
@@ -90,16 +92,22 @@ struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
|
||||
bstr entry;
|
||||
bstr_split_tok(sel, ",", &entry, &sel);
|
||||
if (bstr_equals0(entry, "-")) {
|
||||
mp_warn(log, "Excluding codecs is deprecated.\n");
|
||||
stop = true;
|
||||
break;
|
||||
}
|
||||
bool force = bstr_eatstart0(&entry, "+");
|
||||
bool exclude = !force && bstr_eatstart0(&entry, "-");
|
||||
if (exclude || force)
|
||||
mp_warn(log, "Forcing or excluding codecs is deprecated.\n");
|
||||
struct mp_decoder_list *dest = exclude ? remove : list;
|
||||
bstr family, decoder;
|
||||
if (!bstr_split_tok(entry, ":", &family, &decoder)) {
|
||||
family = entry;
|
||||
decoder = bstr0("*");
|
||||
if (bstr_split_tok(entry, ":", &family, &decoder)) {
|
||||
mp_warn(log, "Codec family selection is deprecated. "
|
||||
"Pass the codec name directly.\n");
|
||||
} else {
|
||||
family = bstr0("*");
|
||||
decoder = entry;
|
||||
}
|
||||
if (bstr_equals0(decoder, "*")) {
|
||||
for (int n = 0; n < all->num_entries; n++) {
|
||||
@@ -130,22 +138,6 @@ struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
|
||||
return list;
|
||||
}
|
||||
|
||||
// selection is a ","-separated list of decoders, all in the given family.
|
||||
struct mp_decoder_list *mp_select_decoder_list(struct mp_decoder_list *all,
|
||||
const char *codec,
|
||||
const char *family,
|
||||
const char *selection)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
bstr sel = bstr0(selection);
|
||||
while (sel.len) {
|
||||
bstr decoder;
|
||||
bstr_split_tok(sel, ",", &decoder, &sel);
|
||||
add_new(list, find_decoder(all, bstr0(family), decoder), codec);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
void mp_append_decoders(struct mp_decoder_list *list, struct mp_decoder_list *a)
|
||||
{
|
||||
for (int n = 0; n < a->num_entries; n++)
|
||||
@@ -158,7 +150,7 @@ void mp_print_decoders(struct mp_log *log, int msgl, const char *header,
|
||||
mp_msg(log, msgl, "%s\n", header);
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *entry = &list->entries[n];
|
||||
mp_msg(log, msgl, " %s:%s", entry->family, entry->decoder);
|
||||
mp_msg(log, msgl, " %s", entry->decoder);
|
||||
if (strcmp(entry->decoder, entry->codec) != 0)
|
||||
mp_msg(log, msgl, " (%s)", entry->codec);
|
||||
mp_msg(log, msgl, " - %s\n", entry->desc);
|
||||
|
||||
+4
-6
@@ -18,6 +18,8 @@
|
||||
#ifndef MP_CODECS_H
|
||||
#define MP_CODECS_H
|
||||
|
||||
struct mp_log;
|
||||
|
||||
struct mp_decoder_entry {
|
||||
const char *family; // decoder module (e.g. ad_lavc => "lavc")
|
||||
const char *codec; // name of the codec (e.g. "mp3")
|
||||
@@ -33,15 +35,11 @@ struct mp_decoder_list {
|
||||
void mp_add_decoder(struct mp_decoder_list *list, const char *family,
|
||||
const char *codec, const char *decoder, const char *desc);
|
||||
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all,
|
||||
struct mp_decoder_list *mp_select_decoders(struct mp_log *log,
|
||||
struct mp_decoder_list *all,
|
||||
const char *codec,
|
||||
const char *selection);
|
||||
|
||||
struct mp_decoder_list *mp_select_decoder_list(struct mp_decoder_list *all,
|
||||
const char *codec,
|
||||
const char *family,
|
||||
const char *selection);
|
||||
|
||||
void mp_append_decoders(struct mp_decoder_list *list, struct mp_decoder_list *a);
|
||||
|
||||
struct mp_log;
|
||||
|
||||
+3
-2
@@ -18,6 +18,7 @@
|
||||
#ifndef MPLAYER_MPCOMMON_H
|
||||
#define MPLAYER_MPCOMMON_H
|
||||
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
@@ -64,8 +65,8 @@ enum {
|
||||
DATA_EOF = -2,
|
||||
};
|
||||
|
||||
extern const char *const mpv_version;
|
||||
extern const char *const mpv_builddate;
|
||||
extern const char mpv_version[];
|
||||
extern const char mpv_builddate[];
|
||||
|
||||
char *mp_format_time(double time, bool fractions);
|
||||
char *mp_format_time_fmt(const char *fmt, double time);
|
||||
|
||||
+19
-37
@@ -36,25 +36,25 @@
|
||||
#define OPT_BASE_STRUCT struct encode_opts
|
||||
const struct m_sub_options encode_config = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_STRING("o", file, CONF_GLOBAL | CONF_NOCFG | CONF_PRE_PARSE),
|
||||
OPT_STRING("of", format, CONF_GLOBAL),
|
||||
OPT_STRINGLIST("ofopts*", fopts, CONF_GLOBAL),
|
||||
OPT_FLOATRANGE("ofps", fps, CONF_GLOBAL, 0.0, 1000000.0),
|
||||
OPT_FLOATRANGE("omaxfps", maxfps, CONF_GLOBAL, 0.0, 1000000.0),
|
||||
OPT_STRING("ovc", vcodec, CONF_GLOBAL),
|
||||
OPT_STRINGLIST("ovcopts*", vopts, CONF_GLOBAL),
|
||||
OPT_STRING("oac", acodec, CONF_GLOBAL),
|
||||
OPT_STRINGLIST("oacopts*", aopts, CONF_GLOBAL),
|
||||
OPT_FLAG("oharddup", harddup, CONF_GLOBAL),
|
||||
OPT_FLOATRANGE("ovoffset", voffset, CONF_GLOBAL, -1000000.0, 1000000.0),
|
||||
OPT_FLOATRANGE("oaoffset", aoffset, CONF_GLOBAL, -1000000.0, 1000000.0),
|
||||
OPT_FLAG("ocopyts", copyts, CONF_GLOBAL),
|
||||
OPT_FLAG("orawts", rawts, CONF_GLOBAL),
|
||||
OPT_FLAG("oautofps", autofps, CONF_GLOBAL),
|
||||
OPT_FLAG("oneverdrop", neverdrop, CONF_GLOBAL),
|
||||
OPT_FLAG("ovfirst", video_first, CONF_GLOBAL),
|
||||
OPT_FLAG("oafirst", audio_first, CONF_GLOBAL),
|
||||
OPT_FLAG("ometadata", metadata, CONF_GLOBAL),
|
||||
OPT_STRING("o", file, M_OPT_FIXED | CONF_NOCFG | CONF_PRE_PARSE),
|
||||
OPT_STRING("of", format, M_OPT_FIXED),
|
||||
OPT_STRINGLIST("ofopts*", fopts, M_OPT_FIXED),
|
||||
OPT_FLOATRANGE("ofps", fps, M_OPT_FIXED, 0.0, 1000000.0),
|
||||
OPT_FLOATRANGE("omaxfps", maxfps, M_OPT_FIXED, 0.0, 1000000.0),
|
||||
OPT_STRING("ovc", vcodec, M_OPT_FIXED),
|
||||
OPT_STRINGLIST("ovcopts*", vopts, M_OPT_FIXED),
|
||||
OPT_STRING("oac", acodec, M_OPT_FIXED),
|
||||
OPT_STRINGLIST("oacopts*", aopts, M_OPT_FIXED),
|
||||
OPT_FLAG("oharddup", harddup, M_OPT_FIXED),
|
||||
OPT_FLOATRANGE("ovoffset", voffset, M_OPT_FIXED, -1000000.0, 1000000.0),
|
||||
OPT_FLOATRANGE("oaoffset", aoffset, M_OPT_FIXED, -1000000.0, 1000000.0),
|
||||
OPT_FLAG("ocopyts", copyts, M_OPT_FIXED),
|
||||
OPT_FLAG("orawts", rawts, M_OPT_FIXED),
|
||||
OPT_FLAG("oautofps", autofps, M_OPT_FIXED),
|
||||
OPT_FLAG("oneverdrop", neverdrop, M_OPT_FIXED),
|
||||
OPT_FLAG("ovfirst", video_first, M_OPT_FIXED),
|
||||
OPT_FLAG("oafirst", audio_first, M_OPT_FIXED),
|
||||
OPT_FLAG("ometadata", metadata, M_OPT_FIXED),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct encode_opts),
|
||||
@@ -595,12 +595,7 @@ int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#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.
|
||||
ctx->vst->time_base = ctx->timebase;
|
||||
@@ -635,12 +630,7 @@ int encode_lavc_alloc_stream(struct encode_lavc_context *ctx,
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
#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.
|
||||
ctx->ast->time_base = ctx->timebase;
|
||||
@@ -708,10 +698,8 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -747,10 +735,8 @@ int encode_lavc_open_codec(struct encode_lavc_context *ctx,
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -826,11 +812,7 @@ int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVStream *stream,
|
||||
(int)packet->size);
|
||||
|
||||
|
||||
#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;
|
||||
|
||||
+5
-1
@@ -5,9 +5,13 @@
|
||||
// The only purpose of this is to make mpv library-safe.
|
||||
// Think hard before adding new members.
|
||||
struct mpv_global {
|
||||
struct MPOpts *opts;
|
||||
struct mp_log *log;
|
||||
struct m_config_shadow *config;
|
||||
struct mp_client_api *client_api;
|
||||
|
||||
// Using this is deprecated and should be avoided (missing synchronization).
|
||||
// Use m_config_cache to access mpv_global.config instead.
|
||||
struct MPOpts *opts;
|
||||
};
|
||||
|
||||
#endif
|
||||
+50
-22
@@ -27,12 +27,13 @@
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "misc/bstr.h"
|
||||
#include "osdep/atomics.h"
|
||||
#include "osdep/atomic.h"
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
#include "misc/ring.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
#include "osdep/terminal.h"
|
||||
#include "osdep/io.h"
|
||||
#include "osdep/timer.h"
|
||||
@@ -59,6 +60,8 @@ struct mp_log_root {
|
||||
int num_buffers;
|
||||
FILE *log_file;
|
||||
FILE *stats_file;
|
||||
char *log_path;
|
||||
char *stats_path;
|
||||
// --- must be accessed atomically
|
||||
/* This is incremented every time the msglevels must be reloaded.
|
||||
* (This is perhaps better than maintaining a globally accessible and
|
||||
@@ -286,6 +289,7 @@ static void write_log_file(struct mp_log *log, int lev, char *text)
|
||||
(mp_time_us() - MP_START_TIME) / 1e6,
|
||||
mp_log_levels[lev][0],
|
||||
log->verbose_prefix, text);
|
||||
fflush(root->log_file);
|
||||
}
|
||||
|
||||
static void write_msg_to_buffers(struct mp_log *log, int lev, char *text)
|
||||
@@ -457,6 +461,43 @@ void mp_msg_init(struct mpv_global *global)
|
||||
mp_msg_update_msglevels(global);
|
||||
}
|
||||
|
||||
// If opt is different from *current_path, reopen *file and update *current_path.
|
||||
// If there's an error, _append_ it to err_buf.
|
||||
// *current_path and *file are, rather trickily, only accessible under the
|
||||
// mp_msg_lock.
|
||||
static void reopen_file(char *opt, char **current_path, FILE **file,
|
||||
const char *type, struct mpv_global *global)
|
||||
{
|
||||
void *tmp = talloc_new(NULL);
|
||||
bool fail = false;
|
||||
|
||||
char *new_path = mp_get_user_path(tmp, global, opt);
|
||||
if (!new_path)
|
||||
new_path = "";
|
||||
|
||||
pthread_mutex_lock(&mp_msg_lock); // for *current_path/*file
|
||||
|
||||
char *old_path = *current_path ? *current_path : "";
|
||||
if (strcmp(old_path, new_path) != 0) {
|
||||
if (*file)
|
||||
fclose(*file);
|
||||
*file = NULL;
|
||||
talloc_free(*current_path);
|
||||
*current_path = talloc_strdup(NULL, new_path);
|
||||
if (new_path[0]) {
|
||||
*file = fopen(new_path, "wb");
|
||||
fail = !*file;
|
||||
}
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
|
||||
if (fail)
|
||||
mp_err(global->log, "Failed to open %s file '%s'\n", type, new_path);
|
||||
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
void mp_msg_update_msglevels(struct mpv_global *global)
|
||||
{
|
||||
struct mp_log_root *root = global->log->root;
|
||||
@@ -480,11 +521,14 @@ void mp_msg_update_msglevels(struct mpv_global *global)
|
||||
m_option_type_msglevels.copy(NULL, &root->msg_levels,
|
||||
&global->opts->msg_levels);
|
||||
|
||||
if (!root->log_file && opts->log_file && opts->log_file[0])
|
||||
root->log_file = fopen(opts->log_file, "wb");
|
||||
|
||||
atomic_fetch_add(&root->reload_counter, 1);
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
|
||||
reopen_file(opts->log_file, &root->log_path, &root->log_file,
|
||||
"log", global);
|
||||
|
||||
reopen_file(opts->dump_stats, &root->stats_path, &root->stats_file,
|
||||
"stats", global);
|
||||
}
|
||||
|
||||
void mp_msg_force_stderr(struct mpv_global *global, bool force_stderr)
|
||||
@@ -499,8 +543,10 @@ void mp_msg_uninit(struct mpv_global *global)
|
||||
struct mp_log_root *root = global->log->root;
|
||||
if (root->stats_file)
|
||||
fclose(root->stats_file);
|
||||
talloc_free(root->stats_path);
|
||||
if (root->log_file)
|
||||
fclose(root->log_file);
|
||||
talloc_free(root->log_path);
|
||||
m_option_type_msglevels.free(&root->msg_levels);
|
||||
talloc_free(root);
|
||||
global->log = NULL;
|
||||
@@ -582,24 +628,6 @@ struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer)
|
||||
return ptr;
|
||||
}
|
||||
|
||||
int mp_msg_open_stats_file(struct mpv_global *global, const char *path)
|
||||
{
|
||||
struct mp_log_root *root = global->log->root;
|
||||
int r;
|
||||
|
||||
pthread_mutex_lock(&mp_msg_lock);
|
||||
|
||||
if (root->stats_file)
|
||||
fclose(root->stats_file);
|
||||
root->stats_file = fopen(path, "wb");
|
||||
r = root->stats_file ? 0 : -1;
|
||||
|
||||
pthread_mutex_unlock(&mp_msg_lock);
|
||||
|
||||
mp_msg_update_msglevels(global);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Thread-safety: fully thread-safe, but keep in mind that the lifetime of
|
||||
// log must be guaranteed during the call.
|
||||
// Never call this from signal handlers.
|
||||
|
||||
@@ -29,7 +29,6 @@ struct mp_log_buffer *mp_msg_log_buffer_new(struct mpv_global *global,
|
||||
void mp_msg_log_buffer_destroy(struct mp_log_buffer *buffer);
|
||||
struct mp_log_buffer_entry *mp_msg_log_buffer_read(struct mp_log_buffer *buffer);
|
||||
|
||||
int mp_msg_open_stats_file(struct mpv_global *global, const char *path);
|
||||
int mp_msg_find_level(const char *s);
|
||||
|
||||
extern const char *const mp_log_levels[MSGL_MAX + 1];
|
||||
|
||||
+2
-2
@@ -22,5 +22,5 @@
|
||||
#define BUILDDATE "UNKNOWN"
|
||||
#endif
|
||||
|
||||
const char *const mpv_version = "mpv " VERSION;
|
||||
const char *const mpv_builddate = BUILDDATE;
|
||||
const char mpv_version[] = "mpv " VERSION;
|
||||
const char mpv_builddate[] = BUILDDATE;
|
||||
+98
-34
@@ -20,6 +20,7 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include <math.h>
|
||||
@@ -28,7 +29,8 @@
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
@@ -81,6 +83,37 @@ const demuxer_desc_t *const demuxer_list[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
struct demux_opts {
|
||||
int max_packs;
|
||||
int max_bytes;
|
||||
double min_secs;
|
||||
int force_seekable;
|
||||
double min_secs_cache;
|
||||
int access_references;
|
||||
};
|
||||
|
||||
#define OPT_BASE_STRUCT struct demux_opts
|
||||
|
||||
const struct m_sub_options demux_conf = {
|
||||
.opts = (const struct m_option[]){
|
||||
OPT_DOUBLE("demuxer-readahead-secs", min_secs, M_OPT_MIN, .min = 0),
|
||||
OPT_INTRANGE("demuxer-max-packets", max_packs, 0, 0, INT_MAX),
|
||||
OPT_INTRANGE("demuxer-max-bytes", max_bytes, 0, 0, INT_MAX),
|
||||
OPT_FLAG("force-seekable", force_seekable, 0),
|
||||
OPT_DOUBLE("cache-secs", min_secs_cache, M_OPT_MIN, .min = 0),
|
||||
OPT_FLAG("access-references", access_references, 0),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_opts),
|
||||
.defaults = &(const struct demux_opts){
|
||||
.max_packs = 16000,
|
||||
.max_bytes = 400 * 1024 * 1024,
|
||||
.min_secs = 1.0,
|
||||
.min_secs_cache = 10.0,
|
||||
.access_references = 1,
|
||||
},
|
||||
};
|
||||
|
||||
struct demux_internal {
|
||||
struct mp_log *log;
|
||||
|
||||
@@ -473,7 +506,7 @@ static double get_refresh_seek_pts(struct demux_internal *in)
|
||||
// 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;
|
||||
ds->refreshing |= ds->selected;
|
||||
}
|
||||
|
||||
// Seek back to player's current position, with a small offset added.
|
||||
@@ -568,7 +601,7 @@ static bool read_packet(struct demux_internal *in)
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
active |= ds->active;
|
||||
read_more |= ds->active && !ds->head;
|
||||
read_more |= (ds->active && !ds->head) || ds->refreshing;
|
||||
packs += ds->packs;
|
||||
bytes += ds->bytes;
|
||||
if (ds->active && ds->last_ts != MP_NOPTS_VALUE && in->min_secs > 0 &&
|
||||
@@ -580,12 +613,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -602,11 +635,13 @@ static bool read_packet(struct demux_internal *in)
|
||||
return false;
|
||||
}
|
||||
|
||||
double seek_pts = get_refresh_seek_pts(in);
|
||||
bool refresh_seek = seek_pts != MP_NOPTS_VALUE;
|
||||
read_more |= refresh_seek;
|
||||
|
||||
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;
|
||||
@@ -615,7 +650,7 @@ static bool read_packet(struct demux_internal *in)
|
||||
|
||||
struct demuxer *demux = in->d_thread;
|
||||
|
||||
if (seek_pts != MP_NOPTS_VALUE) {
|
||||
if (refresh_seek) {
|
||||
MP_VERBOSE(in, "refresh seek to %f\n", seek_pts);
|
||||
demux->desc->seek(demux, seek_pts, SEEK_BACKWARD | SEEK_HR);
|
||||
}
|
||||
@@ -649,8 +684,7 @@ static void ds_get_packets(struct demux_stream *ds)
|
||||
struct demux_internal *in = ds->in;
|
||||
MP_DBG(in, "reading packet for %s\n", t);
|
||||
in->eof = false; // force retry
|
||||
ds->eof = false;
|
||||
while (ds->selected && !ds->head && !ds->eof) {
|
||||
while (ds->selected && !ds->head) {
|
||||
ds->active = true;
|
||||
// Note: the following code marks EOF if it can't continue
|
||||
if (in->threading) {
|
||||
@@ -660,6 +694,8 @@ static void ds_get_packets(struct demux_stream *ds)
|
||||
} else {
|
||||
read_packet(in);
|
||||
}
|
||||
if (ds->eof)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1031,6 +1067,7 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
dst->ts_resets_possible = src->ts_resets_possible;
|
||||
dst->fully_read = src->fully_read;
|
||||
dst->start_time = src->start_time;
|
||||
dst->is_network = src->is_network;
|
||||
dst->priv = src->priv;
|
||||
}
|
||||
|
||||
@@ -1039,10 +1076,10 @@ static void demux_copy(struct demuxer *dst, struct demuxer *src)
|
||||
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);
|
||||
dst->num_update_stream_tags = src->num_update_stream_tags;
|
||||
}
|
||||
for (int n = 0; n < dst->num_update_stream_tags; n++) {
|
||||
talloc_free(dst->update_stream_tags[n]);
|
||||
@@ -1151,6 +1188,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,
|
||||
@@ -1162,16 +1216,18 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
return NULL;
|
||||
|
||||
struct demuxer *demuxer = talloc_ptrtype(NULL, demuxer);
|
||||
struct demux_opts *opts = mp_get_config_group(demuxer, global, &demux_conf);
|
||||
*demuxer = (struct demuxer) {
|
||||
.desc = desc,
|
||||
.stream = stream,
|
||||
.seekable = stream->seekable,
|
||||
.filepos = -1,
|
||||
.opts = global->opts,
|
||||
.global = global,
|
||||
.log = mp_log_new(demuxer, log, desc->name),
|
||||
.glog = log,
|
||||
.filename = talloc_strdup(demuxer, stream->url),
|
||||
.is_network = stream->is_network,
|
||||
.access_references = opts->access_references,
|
||||
.events = DEMUX_EVENT_ALL,
|
||||
};
|
||||
demuxer->seekable = stream->seekable;
|
||||
@@ -1185,16 +1241,16 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
.d_thread = talloc(demuxer, struct demuxer),
|
||||
.d_buffer = talloc(demuxer, struct demuxer),
|
||||
.d_user = demuxer,
|
||||
.min_secs = demuxer->opts->demuxer_min_secs,
|
||||
.max_packs = demuxer->opts->demuxer_max_packs,
|
||||
.max_bytes = demuxer->opts->demuxer_max_bytes,
|
||||
.min_secs = opts->min_secs,
|
||||
.max_packs = opts->max_packs,
|
||||
.max_bytes = opts->max_bytes,
|
||||
.initial_state = true,
|
||||
};
|
||||
pthread_mutex_init(&in->lock, NULL);
|
||||
pthread_cond_init(&in->wakeup, NULL);
|
||||
|
||||
if (stream->uncached_stream)
|
||||
in->min_secs = MPMAX(in->min_secs, demuxer->opts->demuxer_min_secs_cache);
|
||||
in->min_secs = MPMAX(in->min_secs, opts->min_secs_cache);
|
||||
|
||||
*in->d_thread = *demuxer;
|
||||
*in->d_buffer = *demuxer;
|
||||
@@ -1225,7 +1281,7 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
mp_verbose(log, "Detected file format: %s\n", desc->desc);
|
||||
if (!in->d_thread->seekable)
|
||||
mp_verbose(log, "Stream is not seekable.\n");
|
||||
if (!in->d_thread->seekable && demuxer->opts->force_seekable) {
|
||||
if (!in->d_thread->seekable && opts->force_seekable) {
|
||||
mp_warn(log, "Not seekable, but enabling seeking on user request.\n");
|
||||
in->d_thread->seekable = true;
|
||||
in->d_thread->partially_seekable = true;
|
||||
@@ -1235,16 +1291,18 @@ static struct demuxer *open_given_type(struct mpv_global *global,
|
||||
demux_changed(in->d_thread, DEMUX_EVENT_ALL);
|
||||
demux_update(demuxer);
|
||||
stream_control(demuxer->stream, STREAM_CTRL_SET_READAHEAD, &(int){false});
|
||||
struct timeline *tl = timeline_load(global, log, demuxer);
|
||||
if (tl) {
|
||||
struct demuxer_params params2 = {0};
|
||||
params2.timeline = tl;
|
||||
struct demuxer *sub = open_given_type(global, log,
|
||||
&demuxer_desc_timeline, stream,
|
||||
¶ms2, DEMUX_CHECK_FORCE);
|
||||
if (sub)
|
||||
return sub;
|
||||
timeline_destroy(tl);
|
||||
if (!(params && params->disable_timeline)) {
|
||||
struct timeline *tl = timeline_load(global, log, demuxer);
|
||||
if (tl) {
|
||||
struct demuxer_params params2 = {0};
|
||||
params2.timeline = tl;
|
||||
struct demuxer *sub =
|
||||
open_given_type(global, log, &demuxer_desc_timeline, stream,
|
||||
¶ms2, DEMUX_CHECK_FORCE);
|
||||
if (sub)
|
||||
return sub;
|
||||
timeline_destroy(tl);
|
||||
}
|
||||
}
|
||||
return demuxer;
|
||||
}
|
||||
@@ -1311,12 +1369,12 @@ 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,
|
||||
struct mpv_global *global)
|
||||
{
|
||||
struct MPOpts *opts = global->opts;
|
||||
struct demuxer_params dummy = {0};
|
||||
if (!params)
|
||||
params = &dummy;
|
||||
@@ -1324,12 +1382,18 @@ struct demuxer *demux_open_url(const char *url,
|
||||
cancel, global);
|
||||
if (!s)
|
||||
return NULL;
|
||||
if (params->allow_capture)
|
||||
stream_set_capture_file(s, opts->stream_capture);
|
||||
if (params->allow_capture) {
|
||||
char *f;
|
||||
mp_read_option_raw(global, "stream-capture", &m_option_type_string, &f);
|
||||
stream_set_capture_file(s, f);
|
||||
talloc_free(f);
|
||||
}
|
||||
if (!params->disable_cache)
|
||||
stream_enable_cache(&s, &opts->stream_cache);
|
||||
stream_enable_cache_defaults(&s);
|
||||
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);
|
||||
}
|
||||
@@ -1602,7 +1666,7 @@ static int cached_demux_control(struct demux_internal *in, int cmd, void *arg)
|
||||
int num_packets = 0;
|
||||
for (int n = 0; n < in->num_streams; n++) {
|
||||
struct demux_stream *ds = in->streams[n]->ds;
|
||||
if (ds->active) {
|
||||
if (ds->active && !(!ds->head && ds->eof)) {
|
||||
r->underrun |= !ds->head && !ds->eof;
|
||||
r->ts_range[0] = MP_PTS_MAX(r->ts_range[0], ds->base_ts);
|
||||
r->ts_range[1] = MP_PTS_MIN(r->ts_range[1], ds->last_ts);
|
||||
|
||||
+5
-1
@@ -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 {
|
||||
@@ -160,6 +161,7 @@ struct demuxer_params {
|
||||
int matroska_wanted_segment;
|
||||
bool *matroska_was_valid;
|
||||
struct timeline *timeline;
|
||||
bool disable_timeline;
|
||||
// -- demux_open_url() only
|
||||
int stream_flags;
|
||||
bool allow_capture;
|
||||
@@ -183,6 +185,8 @@ typedef struct demuxer {
|
||||
// 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
|
||||
bool access_references; // allow opening other files/URLs
|
||||
|
||||
// Bitmask of DEMUX_EVENT_*
|
||||
int events;
|
||||
@@ -205,7 +209,6 @@ typedef struct demuxer {
|
||||
struct mp_tags *metadata;
|
||||
|
||||
void *priv; // demuxer-specific internal data
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
struct mp_log *log, *glog;
|
||||
struct demuxer_params *params;
|
||||
@@ -219,6 +222,7 @@ typedef struct demuxer {
|
||||
// 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;
|
||||
|
||||
|
||||
@@ -253,6 +253,9 @@ out:
|
||||
|
||||
static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
struct stream *s = demuxer->stream;
|
||||
if (check >= DEMUX_CHECK_UNSAFE) {
|
||||
bstr d = stream_peek(s, PROBE_SIZE);
|
||||
|
||||
+3
-1
@@ -28,7 +28,6 @@
|
||||
#include "demux.h"
|
||||
#include "timeline.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
#include "options/path.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "common/common.h"
|
||||
@@ -296,6 +295,9 @@ static void build_mpv_edl_timeline(struct timeline *tl)
|
||||
|
||||
static int try_open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
struct priv *p = talloc_zero(demuxer, struct priv);
|
||||
demuxer->priv = p;
|
||||
demuxer->fully_read = true;
|
||||
|
||||
+85
-47
@@ -22,6 +22,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include <errno.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
@@ -35,7 +36,6 @@
|
||||
#include <libavutil/display.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/tags.h"
|
||||
#include "common/av_common.h"
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "stream/stream.h"
|
||||
#include "demux.h"
|
||||
#include "stheader.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "options/path.h"
|
||||
|
||||
@@ -69,22 +70,32 @@ struct demux_lavf_opts {
|
||||
char **avopts;
|
||||
int hacks;
|
||||
int genptsmode;
|
||||
char *sub_cp;
|
||||
int rtsp_transport;
|
||||
};
|
||||
|
||||
const struct m_sub_options demux_lavf_conf = {
|
||||
.opts = (const m_option_t[]) {
|
||||
OPT_INTRANGE("probesize", probesize, 0, 32, INT_MAX),
|
||||
OPT_STRING("format", format, 0),
|
||||
OPT_FLOATRANGE("analyzeduration", analyzeduration, 0, 0, 3600),
|
||||
OPT_INTRANGE("buffersize", buffersize, 0, 1, 10 * 1024 * 1024,
|
||||
OPTDEF_INT(BIO_BUFFER_SIZE)),
|
||||
OPT_FLAG("allow-mimetype", allow_mimetype, 0),
|
||||
OPT_INTRANGE("probescore", probescore, 0, 1, AVPROBE_SCORE_MAX),
|
||||
OPT_STRING("cryptokey", cryptokey, 0),
|
||||
OPT_FLAG("hacks", hacks, 0),
|
||||
OPT_CHOICE("genpts-mode", genptsmode, 0,
|
||||
OPT_INTRANGE("demuxer-lavf-probesize", probesize, 0, 32, INT_MAX),
|
||||
OPT_STRING("demuxer-lavf-format", format, 0),
|
||||
OPT_FLOATRANGE("demuxer-lavf-analyzeduration", analyzeduration, 0,
|
||||
0, 3600),
|
||||
OPT_INTRANGE("demuxer-lavf-buffersize", buffersize, 0, 1,
|
||||
10 * 1024 * 1024, OPTDEF_INT(BIO_BUFFER_SIZE)),
|
||||
OPT_FLAG("demuxer-lavf-allow-mimetype", allow_mimetype, 0),
|
||||
OPT_INTRANGE("demuxer-lavf-probescore", probescore, 0,
|
||||
1, AVPROBE_SCORE_MAX),
|
||||
OPT_STRING("demuxer-lavf-cryptokey", cryptokey, 0),
|
||||
OPT_FLAG("demuxer-lavf-hacks", hacks, 0),
|
||||
OPT_CHOICE("demuxer-lavf-genpts-mode", genptsmode, 0,
|
||||
({"lavf", 1}, {"no", 0})),
|
||||
OPT_KEYVALUELIST("o", avopts, 0),
|
||||
OPT_KEYVALUELIST("demuxer-lavf-o", avopts, 0),
|
||||
OPT_STRING("sub-codepage", sub_cp, 0),
|
||||
OPT_CHOICE("rtsp-transport", rtsp_transport, 0,
|
||||
({"lavf", 0},
|
||||
{"udp", 1},
|
||||
{"tcp", 2},
|
||||
{"http", 3})),
|
||||
{0}
|
||||
},
|
||||
.size = sizeof(struct demux_lavf_opts),
|
||||
@@ -95,6 +106,8 @@ const struct m_sub_options demux_lavf_conf = {
|
||||
// user is supposed to retry with larger probe sizes until a higher
|
||||
// value is reached.
|
||||
.probescore = AVPROBE_SCORE_MAX/4 + 1,
|
||||
.sub_cp = "auto",
|
||||
.rtsp_transport = 2,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -114,6 +127,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 +151,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"),
|
||||
@@ -151,6 +168,8 @@ static const struct format_hack format_hacks[] = {
|
||||
BLACKLIST("bin"),
|
||||
// Useless, does not work with custom streams.
|
||||
BLACKLIST("image2"),
|
||||
// Probably a security risk.
|
||||
BLACKLIST("ffm"),
|
||||
// Image demuxers ("<name>_pipe" is detected explicitly)
|
||||
{"image2pipe", .image_format = true},
|
||||
{0}
|
||||
@@ -158,6 +177,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;
|
||||
@@ -169,6 +189,9 @@ typedef struct lavf_priv {
|
||||
int cur_program;
|
||||
char *mime_type;
|
||||
double seek_delay;
|
||||
|
||||
struct demux_lavf_opts *opts;
|
||||
double mf_fps;
|
||||
} lavf_priv_t;
|
||||
|
||||
// At least mp4 has name="mov,mp4,m4a,3gp,3g2,mj2", so we split the name
|
||||
@@ -266,7 +289,7 @@ static void list_formats(struct demuxer *demuxer)
|
||||
static void convert_charset(struct demuxer *demuxer)
|
||||
{
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
char *cp = demuxer->opts->sub_cp;
|
||||
char *cp = priv->opts->sub_cp;
|
||||
if (!cp || mp_charset_is_utf8(cp))
|
||||
return;
|
||||
bstr data = stream_read_complete(priv->stream, NULL, 128 * 1024 * 1024);
|
||||
@@ -286,8 +309,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);
|
||||
}
|
||||
|
||||
@@ -306,9 +331,8 @@ static const char *const prefixes[] =
|
||||
|
||||
static int lavf_check_file(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
struct demux_lavf_opts *lavfdopts = opts->demux_lavf;
|
||||
lavf_priv_t *priv = demuxer->priv;
|
||||
struct demux_lavf_opts *lavfdopts = priv->opts;
|
||||
struct stream *s = priv->stream;
|
||||
|
||||
priv->filename = remove_prefix(s->url, prefixes);
|
||||
@@ -553,13 +577,8 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
AVFormatContext *avfc = priv->avfc;
|
||||
AVStream *st = avfc->streams[i];
|
||||
struct sh_stream *sh = NULL;
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
AVCodecParameters *codec = st->codecpar;
|
||||
int lavc_delay = codec->initial_padding;
|
||||
#else
|
||||
AVCodecContext *codec = st->codec;
|
||||
int lavc_delay = codec->delay;
|
||||
#endif
|
||||
|
||||
switch (codec->codec_type) {
|
||||
case AVMEDIA_TYPE_AUDIO: {
|
||||
@@ -599,7 +618,7 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
if (st->avg_frame_rate.num)
|
||||
sh->codec->fps = av_q2d(st->avg_frame_rate);
|
||||
if (priv->format_hack.image_format)
|
||||
sh->codec->fps = demuxer->opts->mf_fps;
|
||||
sh->codec->fps = priv->mf_fps;
|
||||
sh->codec->par_w = st->sample_aspect_ratio.num;
|
||||
sh->codec->par_h = st->sample_aspect_ratio.den;
|
||||
|
||||
@@ -655,17 +674,11 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
sh->ff_index = st->index;
|
||||
sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec->codec_tag = codec->codec_tag;
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
sh->codec->lav_codecpar = avcodec_parameters_alloc();
|
||||
if (sh->codec->lav_codecpar)
|
||||
avcodec_parameters_copy(sh->codec->lav_codecpar, codec);
|
||||
#else
|
||||
sh->codec->codec = mp_codec_from_av_codec_id(codec->codec_id);
|
||||
sh->codec->codec_tag = codec->codec_tag;
|
||||
sh->codec->lav_headers = avcodec_alloc_context3(NULL);
|
||||
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;
|
||||
@@ -676,6 +689,10 @@ static void handle_new_stream(demuxer_t *demuxer, int i)
|
||||
AVDictionaryEntry *title = av_dict_get(st->metadata, "title", NULL, 0);
|
||||
if (title && title->value)
|
||||
sh->title = talloc_strdup(sh, title->value);
|
||||
if (!sh->title && st->disposition & AV_DISPOSITION_VISUAL_IMPAIRED)
|
||||
sh->title = talloc_asprintf(sh, "visual impaired");
|
||||
if (!sh->title && st->disposition & AV_DISPOSITION_HEARING_IMPAIRED)
|
||||
sh->title = talloc_asprintf(sh, "hearing impaired");
|
||||
AVDictionaryEntry *lang = av_dict_get(st->metadata, "language", NULL, 0);
|
||||
if (lang && lang->value)
|
||||
sh->lang = talloc_strdup(sh, lang->value);
|
||||
@@ -725,10 +742,16 @@ static int interrupt_cb(void *ctx)
|
||||
return mp_cancel_test(priv->stream->cancel);
|
||||
}
|
||||
|
||||
static int block_io_open(struct AVFormatContext *s, AVIOContext **pb,
|
||||
const char *url, int flags, AVDictionary **options)
|
||||
{
|
||||
struct demuxer *demuxer = s->opaque;
|
||||
MP_ERR(demuxer, "Not opening '%s' due to --access-references=no.\n", url);
|
||||
return AVERROR(EACCES);
|
||||
}
|
||||
|
||||
static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
struct demux_lavf_opts *lavfdopts = opts->demux_lavf;
|
||||
AVFormatContext *avfc;
|
||||
AVDictionaryEntry *t = NULL;
|
||||
float analyze_duration = 0;
|
||||
@@ -736,6 +759,15 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
demuxer->priv = priv;
|
||||
priv->stream = demuxer->stream;
|
||||
|
||||
priv->opts = mp_get_config_group(priv, demuxer->global, &demux_lavf_conf);
|
||||
struct demux_lavf_opts *lavfdopts = priv->opts;
|
||||
|
||||
int index_mode;
|
||||
mp_read_option_raw(demuxer->global, "index", &m_option_type_choice,
|
||||
&index_mode);
|
||||
mp_read_option_raw(demuxer->global, "mf-fps", &m_option_type_double,
|
||||
&priv->mf_fps);
|
||||
|
||||
if (lavf_check_file(demuxer, check) < 0)
|
||||
return -1;
|
||||
|
||||
@@ -747,7 +779,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
parse_cryptokey(avfc, lavfdopts->cryptokey);
|
||||
if (lavfdopts->genptsmode)
|
||||
avfc->flags |= AVFMT_FLAG_GENPTS;
|
||||
if (opts->index_mode != 1)
|
||||
if (index_mode != 1)
|
||||
avfc->flags |= AVFMT_FLAG_IGNIDX;
|
||||
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100
|
||||
@@ -780,7 +812,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
priv->stream->type == STREAMTYPE_AVDEVICE ||
|
||||
priv->format_hack.no_stream)
|
||||
{
|
||||
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log, opts);
|
||||
mp_setup_av_network_options(&dopts, demuxer->global, demuxer->log);
|
||||
// This might be incorrect.
|
||||
demuxer->seekable = true;
|
||||
} else {
|
||||
@@ -803,7 +835,7 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
|
||||
if (matches_avinputformat_name(priv, "rtsp")) {
|
||||
const char *transport = NULL;
|
||||
switch (opts->network_rtsp_transport) {
|
||||
switch (lavfdopts->rtsp_transport) {
|
||||
case 1: transport = "udp"; break;
|
||||
case 2: transport = "tcp"; break;
|
||||
case 3: transport = "http"; break;
|
||||
@@ -819,6 +851,10 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
.opaque = demuxer,
|
||||
};
|
||||
|
||||
avfc->opaque = demuxer;
|
||||
if (!demuxer->access_references)
|
||||
avfc->io_open = block_io_open;
|
||||
|
||||
mp_set_avdict(&dopts, lavfdopts->avopts);
|
||||
|
||||
if (avformat_open_input(&avfc, priv->filename, priv->avif, &dopts) < 0) {
|
||||
@@ -855,8 +891,8 @@ static int demux_open_lavf(demuxer_t *demuxer, enum demux_check check)
|
||||
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->fully_read = priv->format_hack.fully_read;
|
||||
|
||||
@@ -902,12 +938,12 @@ static int demux_lavf_fill_buffer(demuxer_t *demux)
|
||||
if (pkt->dts != AV_NOPTS_VALUE)
|
||||
dp->dts = pkt->dts * av_q2d(st->time_base);
|
||||
dp->duration = pkt->duration * av_q2d(st->time_base);
|
||||
#if !HAVE_AV_AVPACKET_INT64_DURATION
|
||||
if (pkt->convergence_duration > 0)
|
||||
dp->duration = pkt->convergence_duration * av_q2d(st->time_base);
|
||||
#endif
|
||||
dp->pos = pkt->pos;
|
||||
dp->keyframe = pkt->flags & AV_PKT_FLAG_KEY;
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100
|
||||
if (pkt->flags & AV_PKT_FLAG_DISCARD)
|
||||
MP_ERR(demux, "Edit lists are not correctly supported (FFmpeg issue).\n");
|
||||
#endif
|
||||
av_packet_unref(pkt);
|
||||
|
||||
if (priv->format_hack.clear_filepos)
|
||||
@@ -1062,6 +1098,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;
|
||||
}
|
||||
@@ -1079,14 +1121,10 @@ 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]) {
|
||||
avcodec_free_context(&priv->streams[n]->codec->lav_headers);
|
||||
#if HAVE_AVCODEC_HAS_CODECPAR
|
||||
if (priv->streams[n])
|
||||
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;
|
||||
|
||||
@@ -32,6 +32,9 @@ static int cmp_filename(const void *a, const void *b)
|
||||
|
||||
static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
int flags = 0;
|
||||
int probe_size = STREAM_BUFFER_SIZE;
|
||||
if (check <= DEMUX_CHECK_REQUEST) {
|
||||
|
||||
+11
-4
@@ -27,6 +27,7 @@
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/path.h"
|
||||
#include "misc/ctype.h"
|
||||
|
||||
@@ -304,10 +305,15 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
if (!mf || mf->nr_of_files < 1)
|
||||
goto error;
|
||||
|
||||
char *force_type = demuxer->opts->mf_type;
|
||||
double mf_fps;
|
||||
char *mf_type;
|
||||
mp_read_option_raw(demuxer->global, "mf-fps", &m_option_type_double, &mf_fps);
|
||||
mp_read_option_raw(demuxer->global, "mf-type", &m_option_type_string, &mf_type);
|
||||
|
||||
const char *codec = mp_map_mimetype_to_video_codec(demuxer->stream->mime_type);
|
||||
if (!codec || (force_type && force_type[0]))
|
||||
codec = probe_format(mf, force_type, check);
|
||||
if (!codec || (mf_type && mf_type[0]))
|
||||
codec = probe_format(mf, mf_type, check);
|
||||
talloc_free(mf_type);
|
||||
if (!codec)
|
||||
goto error;
|
||||
|
||||
@@ -320,7 +326,8 @@ static int demux_open_mf(demuxer_t *demuxer, enum demux_check check)
|
||||
c->codec = codec;
|
||||
c->disp_w = 0;
|
||||
c->disp_h = 0;
|
||||
c->fps = demuxer->opts->mf_fps;
|
||||
c->fps = mf_fps;
|
||||
c->reliable_fps = true;
|
||||
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
|
||||
|
||||
+84
-18
@@ -43,7 +43,7 @@
|
||||
|
||||
#include "mpv_talloc.h"
|
||||
#include "common/av_common.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "misc/bstr.h"
|
||||
#include "stream/stream.h"
|
||||
@@ -107,6 +107,7 @@ typedef struct mkv_track {
|
||||
double v_frate;
|
||||
uint32_t colorspace;
|
||||
int stereo_mode;
|
||||
struct mp_colorspace color;
|
||||
|
||||
uint32_t a_channels, a_bps;
|
||||
float a_sfreq;
|
||||
@@ -127,6 +128,8 @@ typedef struct mkv_track {
|
||||
AVCodecParserContext *av_parser;
|
||||
AVCodecContext *av_parser_codec;
|
||||
|
||||
bool require_keyframes;
|
||||
|
||||
/* stuff for realaudio braincancer */
|
||||
double ra_pts; /* previous audio timestamp */
|
||||
uint32_t sub_packet_size; ///< sub packet size, per stream
|
||||
@@ -162,6 +165,8 @@ struct block_info {
|
||||
};
|
||||
|
||||
typedef struct mkv_demuxer {
|
||||
struct demux_mkv_opts *opts;
|
||||
|
||||
int64_t segment_start, segment_end;
|
||||
|
||||
double duration;
|
||||
@@ -179,6 +184,9 @@ typedef struct mkv_demuxer {
|
||||
mkv_index_t *indexes;
|
||||
size_t num_indexes;
|
||||
bool index_complete;
|
||||
int index_mode;
|
||||
|
||||
int edition_id;
|
||||
|
||||
struct header_elem {
|
||||
int32_t id;
|
||||
@@ -194,7 +202,7 @@ typedef struct mkv_demuxer {
|
||||
|
||||
bool index_has_durations;
|
||||
|
||||
bool eof_warning;
|
||||
bool eof_warning, keyframe_warning;
|
||||
|
||||
struct block_info tmp_block;
|
||||
} mkv_demuxer_t;
|
||||
@@ -531,6 +539,42 @@ static void parse_trackaudio(struct demuxer *demuxer, struct mkv_track *track,
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_colour *colour)
|
||||
{
|
||||
// Note: As per matroska spec, the order is consistent with ISO/IEC
|
||||
// 23001-8:2013/DCOR1, which is the same order used by libavutil/pixfmt.h,
|
||||
// so we can just re-use our avcol_ conversion functions.
|
||||
if (colour->n_matrix_coefficients) {
|
||||
track->color.space = avcol_spc_to_mp_csp(colour->matrix_coefficients);
|
||||
MP_VERBOSE(demuxer, "| + Matrix: %s\n",
|
||||
m_opt_choice_str(mp_csp_names, track->color.space));
|
||||
}
|
||||
if (colour->n_primaries) {
|
||||
track->color.primaries = avcol_pri_to_mp_csp_prim(colour->primaries);
|
||||
MP_VERBOSE(demuxer, "| + Primaries: %s\n",
|
||||
m_opt_choice_str(mp_csp_prim_names, track->color.primaries));
|
||||
}
|
||||
if (colour->n_transfer_characteristics) {
|
||||
track->color.gamma = avcol_trc_to_mp_csp_trc(colour->transfer_characteristics);
|
||||
MP_VERBOSE(demuxer, "| + Gamma: %s\n",
|
||||
m_opt_choice_str(mp_csp_trc_names, track->color.gamma));
|
||||
}
|
||||
if (colour->n_range) {
|
||||
track->color.levels = avcol_range_to_mp_csp_levels(colour->range);
|
||||
MP_VERBOSE(demuxer, "| + Levels: %s\n",
|
||||
m_opt_choice_str(mp_csp_levels_names, track->color.levels));
|
||||
}
|
||||
if (colour->n_mastering_metadata) {
|
||||
struct ebml_mastering_metadata *mastering = &colour->mastering_metadata;
|
||||
|
||||
if (mastering->n_luminance_max) {
|
||||
track->color.sig_peak = mastering->luminance_max;
|
||||
MP_VERBOSE(demuxer, "| + HDR peak: %f\n", track->color.sig_peak);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
struct ebml_video *video)
|
||||
{
|
||||
@@ -570,6 +614,8 @@ static void parse_trackvideo(struct demuxer *demuxer, struct mkv_track *track,
|
||||
MP_WARN(demuxer, "Unknown StereoMode: %d\n", (int)video->stereo_mode);
|
||||
}
|
||||
}
|
||||
if (video->n_colour)
|
||||
parse_trackcolour(demuxer, track, &video->colour);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -747,11 +793,10 @@ static void add_block_position(demuxer_t *demuxer, struct mkv_track *track,
|
||||
|
||||
static int demux_mkv_read_cues(demuxer_t *demuxer)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
mkv_demuxer_t *mkv_d = (mkv_demuxer_t *) demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
|
||||
if (opts->index_mode != 1 || mkv_d->index_complete) {
|
||||
if (mkv_d->index_mode != 1 || mkv_d->index_complete) {
|
||||
ebml_read_skip(demuxer->log, -1, s);
|
||||
return 0;
|
||||
}
|
||||
@@ -810,9 +855,9 @@ done:
|
||||
|
||||
static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
stream_t *s = demuxer->stream;
|
||||
int wanted_edition = opts->edition_id;
|
||||
int wanted_edition = mkv_d->edition_id;
|
||||
uint64_t wanted_edition_uid = demuxer->matroska_data.uid.edition;
|
||||
|
||||
/* A specific edition UID was requested; ignore the user option which is
|
||||
@@ -858,6 +903,7 @@ static int demux_mkv_read_chapters(struct demuxer *demuxer)
|
||||
if (wanted_edition_uid) {
|
||||
MP_ERR(demuxer, "Unable to find expected edition uid: %"PRIu64"\n",
|
||||
wanted_edition_uid);
|
||||
talloc_free(parse_ctx.talloc_ctx);
|
||||
return -1;
|
||||
} else {
|
||||
selected_edition = 0;
|
||||
@@ -1191,10 +1237,9 @@ static int read_deferred_element(struct demuxer *demuxer,
|
||||
|
||||
static void read_deferred_cues(demuxer_t *demuxer)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
|
||||
if (mkv_d->index_complete || opts->index_mode != 1)
|
||||
if (mkv_d->index_complete || mkv_d->index_mode != 1)
|
||||
return;
|
||||
|
||||
for (int n = 0; n < mkv_d->num_headers; n++) {
|
||||
@@ -1393,6 +1438,7 @@ static int demux_mkv_open_video(demuxer_t *demuxer, mkv_track_t *track)
|
||||
sh_v->par_h = p.p_h;
|
||||
|
||||
sh_v->stereo_mode = track->stereo_mode;
|
||||
sh_v->color = track->color;
|
||||
|
||||
done:
|
||||
demux_add_sh_stream(demuxer, sh);
|
||||
@@ -1467,7 +1513,7 @@ static void parse_flac_chmap(struct mp_chmap *channels, unsigned char *data,
|
||||
}
|
||||
|
||||
static const char *const mkv_audio_tags[][2] = {
|
||||
{ "A_MPEG/L2", "mp3" },
|
||||
{ "A_MPEG/L2", "mp2" },
|
||||
{ "A_MPEG/L3", "mp3" },
|
||||
{ "A_AC3", "ac3" },
|
||||
{ "A_EAC3", "eac3" },
|
||||
@@ -1657,7 +1703,9 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
mp_chmap_set_unknown(&sh_a->channels, track->a_channels);
|
||||
|
||||
const char *codec = sh_a->codec;
|
||||
if (!strcmp(codec, "mp3") || !strcmp(codec, "truehd")) {
|
||||
if (!strcmp(codec, "mp2") || !strcmp(codec, "mp3") ||
|
||||
!strcmp(codec, "truehd"))
|
||||
{
|
||||
track->parse = true;
|
||||
} else if (!strcmp(codec, "flac")) {
|
||||
unsigned char *ptr = extradata;
|
||||
@@ -1706,6 +1754,11 @@ static int demux_mkv_open_audio(demuxer_t *demuxer, mkv_track_t *track)
|
||||
if (sh_a->samplerate == 8000 && strcmp(codec, "ac3") == 0)
|
||||
track->default_duration = 0;
|
||||
|
||||
// Deal with some FFmpeg-produced garbage, and assume all audio codecs can
|
||||
// start decoding from anywhere.
|
||||
if (strcmp(codec, "truehd") != 0)
|
||||
track->require_keyframes = true;
|
||||
|
||||
sh_a->extradata = extradata;
|
||||
sh_a->extradata_size = extradata_len;
|
||||
|
||||
@@ -1855,7 +1908,6 @@ static int read_mkv_segment_header(demuxer_t *demuxer, int64_t *segment_end)
|
||||
static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
{
|
||||
stream_t *s = demuxer->stream;
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
mkv_demuxer_t *mkv_d;
|
||||
int64_t start_pos;
|
||||
int64_t end_pos;
|
||||
@@ -1882,6 +1934,12 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
mkv_d->a_skip_preroll = 1;
|
||||
mkv_d->skip_to_timecode = INT64_MIN;
|
||||
|
||||
mp_read_option_raw(demuxer->global, "index", &m_option_type_choice,
|
||||
&mkv_d->index_mode);
|
||||
mp_read_option_raw(demuxer->global, "edition", &m_option_type_choice,
|
||||
&mkv_d->edition_id);
|
||||
mkv_d->opts = mp_get_config_group(mkv_d, demuxer->global, &demux_mkv_conf);
|
||||
|
||||
if (demuxer->params && demuxer->params->matroska_was_valid)
|
||||
*demuxer->params->matroska_was_valid = true;
|
||||
|
||||
@@ -1941,7 +1999,7 @@ static int demux_mkv_open(demuxer_t *demuxer, enum demux_check check)
|
||||
process_tags(demuxer);
|
||||
|
||||
probe_first_timestamp(demuxer);
|
||||
if (opts->demux_mkv->probe_duration)
|
||||
if (mkv_d->opts->probe_duration)
|
||||
probe_last_timestamp(demuxer, start_pos);
|
||||
|
||||
return 0;
|
||||
@@ -2423,6 +2481,15 @@ static int handle_block(demuxer_t *demuxer, struct block_info *block_info)
|
||||
|
||||
current_pts = tc / 1e9 - track->codec_delay;
|
||||
|
||||
if (track->require_keyframes && !keyframe) {
|
||||
keyframe = true;
|
||||
if (!mkv_d->keyframe_warning) {
|
||||
MP_WARN(demuxer, "This is a broken file! Packets with incorrect "
|
||||
"keyframe flag found. Enabling workaround.\n");
|
||||
mkv_d->keyframe_warning = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (track->type == MATROSKA_TRACK_AUDIO) {
|
||||
if (mkv_d->a_skip_to_keyframe)
|
||||
use_this_block &= keyframe;
|
||||
@@ -2730,7 +2797,6 @@ static int create_index_until(struct demuxer *demuxer, int64_t timecode)
|
||||
static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
|
||||
int64_t target_timecode, int flags)
|
||||
{
|
||||
struct MPOpts *opts = demuxer->opts;
|
||||
struct mkv_demuxer *mkv_d = demuxer->priv;
|
||||
struct mkv_index *index = NULL;
|
||||
|
||||
@@ -2758,9 +2824,9 @@ static struct mkv_index *seek_with_cues(struct demuxer *demuxer, int seek_id,
|
||||
if (flags & FLAG_SUBPREROLL) {
|
||||
// Find the cluster with the highest filepos, that has a timestamp
|
||||
// still lower than min_tc.
|
||||
double secs = opts->demux_mkv->subtitle_preroll_secs;
|
||||
double secs = mkv_d->opts->subtitle_preroll_secs;
|
||||
if (mkv_d->index_has_durations)
|
||||
secs = MPMAX(secs, opts->demux_mkv->subtitle_preroll_secs_index);
|
||||
secs = MPMAX(secs, mkv_d->opts->subtitle_preroll_secs_index);
|
||||
int64_t pre = MPMIN(INT64_MAX, secs * 1e9 / mkv_d->tc_scale);
|
||||
int64_t min_tc = pre < index->timecode ? index->timecode - pre : 0;
|
||||
uint64_t prev_target = 0;
|
||||
@@ -2823,7 +2889,7 @@ static void demux_mkv_seek(demuxer_t *demuxer, double seek_pts, int flags)
|
||||
int cueflags = (flags & SEEK_BACKWARD) ? FLAG_BACKWARD : 0;
|
||||
|
||||
mkv_d->subtitle_preroll = NUM_SUB_PREROLL_PACKETS;
|
||||
int preroll_opt = demuxer->opts->demux_mkv->subtitle_preroll;
|
||||
int preroll_opt = mkv_d->opts->subtitle_preroll;
|
||||
if (((flags & SEEK_HR) || preroll_opt == 1 ||
|
||||
(preroll_opt == 2 && mkv_d->index_has_durations))
|
||||
&& st_active[STREAM_SUB] && st_active[STREAM_VIDEO])
|
||||
@@ -2917,7 +2983,7 @@ static void probe_last_timestamp(struct demuxer *demuxer, int64_t start_pos)
|
||||
|
||||
// In full mode, we start reading data from the current file position,
|
||||
// which works because this function is called after headers are parsed.
|
||||
if (demuxer->opts->demux_mkv->probe_duration != 2) {
|
||||
if (mkv_d->opts->probe_duration != 2) {
|
||||
read_deferred_cues(demuxer);
|
||||
if (mkv_d->index_complete) {
|
||||
// Find last cluster that still has video packets
|
||||
@@ -2974,7 +3040,7 @@ static void probe_first_timestamp(struct demuxer *demuxer)
|
||||
{
|
||||
mkv_demuxer_t *mkv_d = demuxer->priv;
|
||||
|
||||
if (!demuxer->opts->demux_mkv->probe_start_time)
|
||||
if (!mkv_d->opts->probe_start_time)
|
||||
return;
|
||||
|
||||
struct block_info block;
|
||||
|
||||
+32
-25
@@ -32,10 +32,10 @@
|
||||
#include "mpv_talloc.h"
|
||||
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
#include "demux/demux.h"
|
||||
#include "demux/timeline.h"
|
||||
#include "demux/matroska.h"
|
||||
#include "options/m_config.h"
|
||||
#include "options/options.h"
|
||||
#include "options/path.h"
|
||||
#include "misc/bstr.h"
|
||||
@@ -46,6 +46,7 @@
|
||||
struct tl_ctx {
|
||||
struct mp_log *log;
|
||||
struct mpv_global *global;
|
||||
struct MPOpts *opts;
|
||||
struct timeline *tl;
|
||||
|
||||
struct demuxer *demuxer;
|
||||
@@ -169,6 +170,7 @@ static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
|
||||
.matroska_wanted_uids = ctx->uids,
|
||||
.matroska_wanted_segment = segment,
|
||||
.matroska_was_valid = &was_valid,
|
||||
.disable_timeline = true,
|
||||
.disable_cache = true,
|
||||
};
|
||||
struct mp_cancel *cancel = ctx->tl->cancel;
|
||||
@@ -186,31 +188,34 @@ static bool check_file_seg(struct tl_ctx *ctx, char *filename, int segment)
|
||||
if (ctx->sources[i])
|
||||
continue;
|
||||
/* Accept the source if the segment uid matches and the edition
|
||||
* either matches or isn't specified. */
|
||||
* either matches or isn't specified. */
|
||||
if (!memcmp(uid->segment, m->uid.segment, 16) &&
|
||||
(!uid->edition || uid->edition == m->uid.edition))
|
||||
{
|
||||
MP_INFO(ctx, "Match for source %d: %s\n", i, d->filename);
|
||||
|
||||
for (int j = 0; j < m->num_ordered_chapters; j++) {
|
||||
struct matroska_chapter *c = m->ordered_chapters + j;
|
||||
if (!uid->edition) {
|
||||
m->uid.edition = 0;
|
||||
} else {
|
||||
for (int j = 0; j < m->num_ordered_chapters; j++) {
|
||||
struct matroska_chapter *c = m->ordered_chapters + j;
|
||||
|
||||
if (!c->has_segment_uid)
|
||||
continue;
|
||||
if (!c->has_segment_uid)
|
||||
continue;
|
||||
|
||||
if (has_source_request(ctx, &c->uid))
|
||||
continue;
|
||||
if (has_source_request(ctx, &c->uid))
|
||||
continue;
|
||||
|
||||
/* Set the requested segment. */
|
||||
MP_TARRAY_GROW(NULL, ctx->uids, ctx->num_sources);
|
||||
ctx->uids[ctx->num_sources] = c->uid;
|
||||
/* Set the requested segment. */
|
||||
MP_TARRAY_GROW(NULL, ctx->uids, ctx->num_sources);
|
||||
ctx->uids[ctx->num_sources] = c->uid;
|
||||
|
||||
/* Add a new source slot. */
|
||||
MP_TARRAY_APPEND(NULL, ctx->sources, ctx->num_sources, NULL);
|
||||
/* Add a new source slot. */
|
||||
MP_TARRAY_APPEND(NULL, ctx->sources, ctx->num_sources, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream_wants_cache(d->stream, &ctx->global->opts->stream_cache))
|
||||
{
|
||||
if (stream_wants_cache(d->stream, ctx->opts->stream_cache)) {
|
||||
free_demuxer_and_stream(d);
|
||||
params.disable_cache = false;
|
||||
params.matroska_wanted_uids = ctx->uids; // potentially reallocated, same data
|
||||
@@ -247,7 +252,7 @@ static bool missing(struct tl_ctx *ctx)
|
||||
|
||||
static void find_ordered_chapter_sources(struct tl_ctx *ctx)
|
||||
{
|
||||
struct MPOpts *opts = ctx->global->opts;
|
||||
struct MPOpts *opts = ctx->opts;
|
||||
void *tmp = talloc_new(NULL);
|
||||
int num_filenames = 0;
|
||||
char **filenames = NULL;
|
||||
@@ -260,7 +265,7 @@ static void find_ordered_chapter_sources(struct tl_ctx *ctx)
|
||||
struct playlist *pl =
|
||||
playlist_parse_file(opts->ordered_chapters_files, ctx->global);
|
||||
talloc_steal(tmp, pl);
|
||||
for (struct playlist_entry *e = pl->first; e; e = e->next)
|
||||
for (struct playlist_entry *e = pl ? pl->first : NULL; e; e = e->next)
|
||||
MP_TARRAY_APPEND(tmp, filenames, num_filenames, e->filename);
|
||||
} else if (ctx->demuxer->stream->uncached_type != STREAMTYPE_FILE) {
|
||||
MP_WARN(ctx, "Playback source is not a "
|
||||
@@ -317,7 +322,7 @@ static int64_t add_timeline_part(struct tl_ctx *ctx,
|
||||
* early; we don't want to try seeking over a one frame gap. */
|
||||
int64_t join_diff = start - ctx->last_end_time;
|
||||
if (ctx->num_parts == 0
|
||||
|| FFABS(join_diff) > ctx->global->opts->chapter_merge_threshold * 1e6
|
||||
|| FFABS(join_diff) > ctx->opts->chapter_merge_threshold * 1e6
|
||||
|| source != ctx->timeline[ctx->num_parts - 1].source)
|
||||
{
|
||||
struct timeline_part new = {
|
||||
@@ -378,7 +383,7 @@ static void build_timeline_loop(struct tl_ctx *ctx,
|
||||
|
||||
/* If we're the source or it's a non-ordered edition reference,
|
||||
* just add a timeline part from the source. */
|
||||
if (current_source == j || !linked_m->num_ordered_chapters) {
|
||||
if (current_source == j || !linked_m->uid.edition) {
|
||||
uint64_t source_full_length =
|
||||
demuxer_get_time_length(linked_source) * 1e9;
|
||||
uint64_t source_length = source_full_length - c->start;
|
||||
@@ -505,20 +510,22 @@ void build_ordered_chapter_timeline(struct timeline *tl)
|
||||
if (!demuxer->matroska_data.ordered_chapters)
|
||||
return;
|
||||
|
||||
if (!demuxer->global->opts->ordered_chapters) {
|
||||
MP_INFO(demuxer, "File uses ordered chapters, but "
|
||||
"you have disabled support for them. Ignoring.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
struct tl_ctx *ctx = talloc_ptrtype(tl, ctx);
|
||||
*ctx = (struct tl_ctx){
|
||||
.log = tl->log,
|
||||
.global = tl->global,
|
||||
.tl = tl,
|
||||
.demuxer = demuxer,
|
||||
.opts = mp_get_config_group(ctx, tl->global, NULL),
|
||||
};
|
||||
|
||||
if (!ctx->opts->ordered_chapters || !demuxer->access_references) {
|
||||
MP_INFO(demuxer, "File uses ordered chapters, but "
|
||||
"you have disabled support for them. Ignoring.\n");
|
||||
talloc_free(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
MP_INFO(ctx, "File uses ordered chapters, will build edit timeline.\n");
|
||||
|
||||
struct matroska_data *m = &demuxer->matroska_data;
|
||||
|
||||
@@ -283,6 +283,8 @@ static int parse_dir(struct pl_parser *p)
|
||||
return 0;
|
||||
|
||||
char *path = mp_file_get_path(p, bstr0(p->real_stream->url));
|
||||
if (!path)
|
||||
return -1;
|
||||
|
||||
char **files = NULL;
|
||||
int num_files = 0;
|
||||
@@ -339,6 +341,9 @@ static const struct pl_format *probe_pl(struct pl_parser *p)
|
||||
|
||||
static int open_file(struct demuxer *demuxer, enum demux_check check)
|
||||
{
|
||||
if (!demuxer->access_references)
|
||||
return -1;
|
||||
|
||||
bool force = check < DEMUX_CHECK_UNSAFE || check == DEMUX_CHECK_REQUEST;
|
||||
|
||||
struct pl_parser *p = talloc_zero(NULL, struct pl_parser);
|
||||
|
||||
Loaded 100 of 300 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user