Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76d771a6fa | ||
|
|
19caba0794 | ||
|
|
391546c52f | ||
|
|
a8af6a7852 | ||
|
|
960aca3386 | ||
|
|
e72a327d74 | ||
|
|
87f955f3e9 | ||
|
|
5536ebe82f | ||
|
|
ef8b37ca2a | ||
|
|
d4a5513847 | ||
|
|
d42212cd67 | ||
|
|
674cc1d3a9 | ||
|
|
c47bb9ca42 | ||
|
|
938dad0b88 | ||
|
|
82b5361946 | ||
|
|
f99f48684b | ||
|
|
25864a5539 | ||
|
|
524d9585fd | ||
|
|
95a72baf92 | ||
|
|
48112a74ed | ||
|
|
8427cdc717 | ||
|
|
b695ca1e27 | ||
|
|
801feb6420 | ||
|
|
de6a452039 | ||
|
|
4b764b6cc6 | ||
|
|
7b7c31b190 | ||
|
|
70a66b461d | ||
|
|
844e8f2b51 | ||
|
|
3f4ed8920f | ||
|
|
7ad2999b5f | ||
|
|
14fdbe07ac | ||
|
|
61ed9a8747 | ||
|
|
0801345d68 | ||
|
|
5bd0fe5daf | ||
|
|
cab3c5fbb8 | ||
|
|
99ceae8cc3 | ||
|
|
ebe04ab692 | ||
|
|
808aa08cc0 | ||
|
|
2065e0ba36 | ||
|
|
3c43a022af | ||
|
|
02513cd5df | ||
|
|
4d986d2a98 | ||
|
|
ada6f96811 | ||
|
|
321b3952b0 | ||
|
|
d49b49ff96 | ||
|
|
f088abb2bf | ||
|
|
4a3975bf7e | ||
|
|
a064143899 | ||
|
|
f264d52099 | ||
|
|
74a3c169b5 | ||
|
|
34d9787fcf | ||
|
|
1c0b80d518 | ||
|
|
a9bafeb589 | ||
|
|
3a33db3d92 | ||
|
|
d276f753f7 | ||
|
|
c06000eef8 | ||
|
|
1bc1cd2305 | ||
|
|
a735a2abd7 | ||
|
|
112c1abe31 | ||
|
|
362b35b631 | ||
|
|
49cf0af294 | ||
|
|
1b26874710 | ||
|
|
8fe636da7a | ||
|
|
812f149585 | ||
|
|
635424c8f0 | ||
|
|
2319918721 | ||
|
|
9fabf85a44 | ||
|
|
54fd5d0979 | ||
|
|
d141d281ca | ||
|
|
ebc3d74b04 | ||
|
|
a63fab8921 | ||
|
|
4ee8d8d3f9 | ||
|
|
574a2484c1 | ||
|
|
f5b92bee5e | ||
|
|
ca3d7144db | ||
|
|
86e7c3defb | ||
|
|
0fe1385d80 | ||
|
|
f4e9371d0f | ||
|
|
909185d49e | ||
|
|
c18c324c6f | ||
|
|
6b20c24413 | ||
|
|
5c42d1eb92 | ||
|
|
e34132f8d5 | ||
|
|
bd7ff23f96 | ||
|
|
e6c184e2b1 | ||
|
|
33c4920967 | ||
|
|
0c36f655e1 | ||
|
|
cd31697dd8 | ||
|
|
c497b82583 | ||
|
|
6f5dfb5102 | ||
|
|
59f970d492 | ||
|
|
29c74b42b4 | ||
|
|
4b2a3e50e2 | ||
|
|
35b01c9604 | ||
|
|
09b95257fe | ||
|
|
0cf2df38d1 | ||
|
|
4326fdfe2a | ||
|
|
9755850b3d | ||
|
|
96b50e770b | ||
|
|
9f6551160a | ||
|
|
89b854165e | ||
|
|
0beece4397 | ||
|
|
8abdf1f56e | ||
|
|
5a81f7678e | ||
|
|
e26286dfdb | ||
|
|
389232e5a2 | ||
|
|
c5de0b0a6c | ||
|
|
f9bdfc0782 | ||
|
|
5c9a7e24f6 | ||
|
|
b56f61d2ab | ||
|
|
82ab3a8160 | ||
|
|
e371f2ebd3 | ||
|
|
29b68d69c0 | ||
|
|
6c770eb6df | ||
|
|
965f336917 | ||
|
|
47fae324c7 | ||
|
|
6ab0dbf2ed | ||
|
|
c002196ffe | ||
|
|
3c8fc4588d | ||
|
|
416015f059 | ||
|
|
773db053aa | ||
|
|
a8c09d003f | ||
|
|
1284a6b6b0 | ||
|
|
78c35e42ea | ||
|
|
f06a870b88 | ||
|
|
392856ed4d | ||
|
|
15f38b89ee | ||
|
|
6878cf2832 | ||
|
|
fc583e74ec | ||
|
|
fd4e3af740 | ||
|
|
a473739fbf | ||
|
|
62be13b019 | ||
|
|
4b695e7972 | ||
|
|
f146e9b15a | ||
|
|
3e562583e5 | ||
|
|
b205e63cdc | ||
|
|
2e8b6419bd | ||
|
|
c7f9c060e4 | ||
|
|
dbf879be14 | ||
|
|
1cbb32cca2 | ||
|
|
dacb6ad98f | ||
|
|
4ea8612b40 | ||
|
|
34bee16faf | ||
|
|
652895abdc | ||
|
|
a410a750c7 | ||
|
|
7d553a4c26 | ||
|
|
f691d8f7b6 | ||
|
|
f751609450 | ||
|
|
3720b3f17d | ||
|
|
9292f537d6 | ||
|
|
2b87415f6f | ||
|
|
1d86134ec1 | ||
|
|
b796f2bb76 | ||
|
|
96e6f3f4b6 | ||
|
|
0656134179 | ||
|
|
28c28d082a | ||
|
|
b59f2c99e7 | ||
|
|
d259d88af8 | ||
|
|
fd081c467d | ||
|
|
9fb0441b16 | ||
|
|
6565ec8b41 | ||
|
|
7ef8d38769 | ||
|
|
90e4c85891 | ||
|
|
aefb100e68 | ||
|
|
a2bc1c28a5 | ||
|
|
88fd763e18 | ||
|
|
ffcf4ece6e | ||
|
|
2e1c423dff | ||
|
|
2de2b60222 | ||
|
|
8c0675b7d0 | ||
|
|
3782fa20ed | ||
|
|
c31ce789c5 | ||
|
|
79b09592c8 | ||
|
|
895932aa0a | ||
|
|
e6bea0ec5a | ||
|
|
8b7048b7d5 | ||
|
|
3a637d411f | ||
|
|
38be9d5fed | ||
|
|
a0e89daf49 | ||
|
|
bd5e0a2ba2 | ||
|
|
4833e92b54 | ||
|
|
cb36f9fddb | ||
|
|
0a3e9a9ac3 | ||
|
|
245e5b8441 | ||
|
|
678ce04b3f | ||
|
|
8e75cc3339 | ||
|
|
6aaea76b4d | ||
|
|
eef36f03ea | ||
|
|
eba5d025d2 | ||
|
|
3fa584e280 | ||
|
|
893aaf8d12 | ||
|
|
455214acef | ||
|
|
1a0e83e708 | ||
|
|
ad2199128d | ||
|
|
232b8de095 | ||
|
|
d9b5652cac | ||
|
|
33c8fd789d | ||
|
|
0335011f11 | ||
|
|
3dbc9007b0 | ||
|
|
9149e2af56 | ||
|
|
8cd1b33a22 | ||
|
|
9428e05b3f | ||
|
|
09e588662e | ||
|
|
d57eaa7e30 | ||
|
|
7bdee8f35e | ||
|
|
9242c34fa2 | ||
|
|
d8d42b44fc | ||
|
|
a2d144fc8f | ||
|
|
ed71606e65 | ||
|
|
dadf3a9a46 | ||
|
|
3846fc7587 | ||
|
|
92f9b51426 | ||
|
|
15ae64d369 | ||
|
|
9825906f73 | ||
|
|
02a9fbd0ce | ||
|
|
5f0fbacf16 | ||
|
|
71b6a52295 | ||
|
|
138d183d83 | ||
|
|
7cc3c3aeec | ||
|
|
60c06fec1e | ||
|
|
1974c9b49d | ||
|
|
4abe6b862f | ||
|
|
57359a1d62 | ||
|
|
eb15265d5a | ||
|
|
fb5ee5bfbe | ||
|
|
95d94238f4 | ||
|
|
823bf0029d | ||
|
|
70af7ab8e5 | ||
|
|
9e0b84c321 | ||
|
|
64278128d8 | ||
|
|
5beedf1967 | ||
|
|
38342436cd | ||
|
|
212ce468d8 | ||
|
|
c8268701d9 | ||
|
|
2eefa31c88 | ||
|
|
426ebbae5f | ||
|
|
877303aaa9 | ||
|
|
fdceef6cc5 | ||
|
|
ad05e76c57 | ||
|
|
e9e68fc399 | ||
|
|
6a8fc3f5e3 | ||
|
|
5162c2709e | ||
|
|
591a6722d2 | ||
|
|
78292058cc | ||
|
|
4d4b822171 | ||
|
|
a4fe95b0d8 | ||
|
|
2d3c7f3b90 | ||
|
|
2f6144c74b | ||
|
|
18930ba7dd | ||
|
|
833eba5304 | ||
|
|
25d4ae74f1 | ||
|
|
8c7ea10873 | ||
|
|
5e0c4ec3e8 | ||
|
|
7a8207a3fb | ||
|
|
b4aa39a817 | ||
|
|
b97b92fe40 | ||
|
|
f5b6710bec | ||
|
|
1fe39b75db | ||
|
|
d2e750ccd6 | ||
|
|
03e53ab430 | ||
|
|
6ae36d3225 | ||
|
|
b170248389 | ||
|
|
80b34ebea4 | ||
|
|
2c08bf1bd7 | ||
|
|
5e0424f17f | ||
|
|
4ed83fe2e5 | ||
|
|
761a013736 | ||
|
|
94369170b8 | ||
|
|
978e099b65 | ||
|
|
ad92c893a0 | ||
|
|
0112143fda | ||
|
|
73a5417950 | ||
|
|
eb15151705 | ||
|
|
793f85945f | ||
|
|
8d5214de0a | ||
|
|
053c743724 | ||
|
|
56eafe3344 | ||
|
|
e449111429 | ||
|
|
7dc7b900c6 | ||
|
|
3e6cd3ef19 | ||
|
|
9662e3e509 | ||
|
|
908dfa7949 | ||
|
|
e51808ea22 | ||
|
|
86bf16f449 | ||
|
|
bc723f1b68 | ||
|
|
19b506ea65 | ||
|
|
5173900ed4 | ||
|
|
b65a6c3cd0 | ||
|
|
fe367272bd | ||
|
|
e8f21b1f0a | ||
|
|
048bb2464d | ||
|
|
cd064c679c | ||
|
|
ff85afa537 | ||
|
|
6dcebd9130 | ||
|
|
78b5324eee | ||
|
|
3194058309 | ||
|
|
4ca740ccf9 | ||
|
|
50b3cfa221 | ||
|
|
2f49fbff93 | ||
|
|
7f3eda41d9 | ||
|
|
683d7e88e4 | ||
|
|
dcf9f77c58 | ||
|
|
a59ebae2ad | ||
|
|
9bd7601c6d | ||
|
|
653980ac3a | ||
|
|
8510ddba4c | ||
|
|
6002520906 | ||
|
|
df4d7fd072 | ||
|
|
fc75506e8b | ||
|
|
1948131a2a | ||
|
|
9a723fc03b | ||
|
|
600bccdf2a | ||
|
|
dd6d204e9e | ||
|
|
ad571d629d | ||
|
|
37319ab644 | ||
|
|
ebf6d0004b | ||
|
|
ce8fe6d815 | ||
|
|
b0bd93cbc1 | ||
|
|
88432b817d | ||
|
|
e66e16e691 | ||
|
|
49f9291164 | ||
|
|
0530447417 | ||
|
|
76ce5434b2 | ||
|
|
a937e93fd6 | ||
|
|
fcfd044090 | ||
|
|
7c7d214775 | ||
|
|
bf003033e3 | ||
|
|
94f1585d5f | ||
|
|
227d087db6 | ||
|
|
5135e93d0c | ||
|
|
e6e6b88b6d | ||
|
|
2f46b23d51 | ||
|
|
5f0585177e | ||
|
|
9838bf5565 | ||
|
|
218b9d3737 | ||
|
|
398bfbe4c1 | ||
|
|
04faf9a1cb | ||
|
|
c7064ce5e5 | ||
|
|
8f4380d6d5 | ||
|
|
2329e46229 | ||
|
|
62925a5c15 | ||
|
|
d658b115a0 | ||
|
|
37fbef2ccb | ||
|
|
0af9ede546 | ||
|
|
bb6165342d | ||
|
|
75d3bf4711 | ||
|
|
6025abffda | ||
|
|
a54b775f80 | ||
|
|
745b9d542b | ||
|
|
070269df73 | ||
|
|
e8a5aaf883 | ||
|
|
5b19a66cff | ||
|
|
e4f35516fb | ||
|
|
20d1fc132e | ||
|
|
a066dae19e | ||
|
|
b454cc0ca3 | ||
|
|
8f3d0b5e53 | ||
|
|
a53abbf4a6 | ||
|
|
4ed4341df8 | ||
|
|
f89bd7c5c2 | ||
|
|
9839bb08df | ||
|
|
2d0a9c7af0 | ||
|
|
66e20ef8ad | ||
|
|
b2c4653b88 | ||
|
|
e3809e5b52 | ||
|
|
4904df42e9 | ||
|
|
e1966e4aae | ||
|
|
a558d84108 | ||
|
|
5cb8439015 | ||
|
|
f6e68b33bc | ||
|
|
84cfe0d8b2 | ||
|
|
ed024aadb6 | ||
|
|
2bcfb49a39 | ||
|
|
e9f49ea84d | ||
|
|
193930ac3b | ||
|
|
09bd19e59e | ||
|
|
82983970b3 | ||
|
|
adc843f984 | ||
|
|
bcd8afc2ad | ||
|
|
71b6115d66 | ||
|
|
4f581a781b | ||
|
|
ad8e3d8c30 | ||
|
|
d74419e6f0 | ||
|
|
54b8a7150a | ||
|
|
ee7ff874ba | ||
|
|
98905f668f | ||
|
|
aaccf9d5e9 | ||
|
|
2c23fae344 | ||
|
|
5b7eb713a1 | ||
|
|
349376aa5c | ||
|
|
0205f3d214 | ||
|
|
a27114bb4b | ||
|
|
59aed93208 | ||
|
|
47c4b5c000 | ||
|
|
0afd121ae6 | ||
|
|
9c2858f37f | ||
|
|
8a84da8102 | ||
|
|
e2dfdc0c7e | ||
|
|
f221d46a44 | ||
|
|
57b11ba706 | ||
|
|
18f3a1a7ee | ||
|
|
6e34b0ec1f | ||
|
|
bf90317ad1 | ||
|
|
aeed84bd27 | ||
|
|
0c2a0a67b6 | ||
|
|
05ba5a08d2 | ||
|
|
a91e51a8f1 | ||
|
|
7ee074813b | ||
|
|
9fcf88e42b | ||
|
|
e872a13d5f | ||
|
|
437ecc5cad | ||
|
|
079e0e7963 | ||
|
|
0d5e4ba799 | ||
|
|
fabfb23232 | ||
|
|
a605fae6fa | ||
|
|
e92f4d4726 | ||
|
|
3201a40234 | ||
|
|
43a6dd1ed5 | ||
|
|
5b7351ee3a | ||
|
|
66f74d031d | ||
|
|
5b48204e84 | ||
|
|
b21cb279d7 | ||
|
|
1acb32201b | ||
|
|
6a2eeedc4c | ||
|
|
ad950be415 | ||
|
|
54d67581d7 | ||
|
|
7d04327e71 | ||
|
|
5064ef87c3 | ||
|
|
86ba9a7f24 | ||
|
|
25635a62c3 | ||
|
|
733c2369d3 | ||
|
|
1176ca9465 | ||
|
|
ca956af446 | ||
|
|
14d92a4685 | ||
|
|
391035d857 | ||
|
|
6fb020f5de | ||
|
|
a74d9c1803 | ||
|
|
36eebc231e | ||
|
|
cb45ea371f | ||
|
|
dc582f2505 | ||
|
|
f30c2c99d1 | ||
|
|
1136a8b6ac | ||
|
|
071ae5613f | ||
|
|
b5d7c95e36 | ||
|
|
48e10da501 | ||
|
|
b18f02d1ad | ||
|
|
aaddcb702e | ||
|
|
fafa2f4b85 | ||
|
|
9d8573cfe8 | ||
|
|
95cfe58e3d | ||
|
|
eea69682a6 | ||
|
|
c8ab12ee4b | ||
|
|
ac0cbd7c5e | ||
|
|
17d72de2ac | ||
|
|
557efff690 | ||
|
|
f388f14c01 | ||
|
|
e61e6e6fd9 | ||
|
|
5fe5fb02b4 | ||
|
|
924c4c1fa5 | ||
|
|
6565c0d979 | ||
|
|
fa620ffc95 | ||
|
|
18345400c0 | ||
|
|
597b8a3550 | ||
|
|
f1072e7629 | ||
|
|
42714f9a99 | ||
|
|
2ec629bede | ||
|
|
54f15bd377 | ||
|
|
9edc2dbcf3 | ||
|
|
2a316c3506 | ||
|
|
60cd300558 | ||
|
|
0d255f07bf | ||
|
|
0a18f3eb9a | ||
|
|
5318a1bea1 | ||
|
|
5411fbdb23 | ||
|
|
dc0b2046cd | ||
|
|
d9b5dedfe9 | ||
|
|
3bed78fdfd | ||
|
|
e8677aa363 | ||
|
|
156dcbbeb1 | ||
|
|
f10cca0e88 | ||
|
|
69220aa1cd | ||
|
|
b5e7169a0f | ||
|
|
6e2ac4d40a | ||
|
|
aa73ac8db8 | ||
|
|
5d97ac229a | ||
|
|
f5219720f8 | ||
|
|
1e96f5bcd9 | ||
|
|
f09b2ff661 | ||
|
|
f2b5267e88 | ||
|
|
55070ea85f | ||
|
|
fb508105d1 | ||
|
|
addfcf9ce3 | ||
|
|
ce1a511e22 | ||
|
|
7a0299478e | ||
|
|
f1eb30a476 | ||
|
|
27d578a847 | ||
|
|
54948f1b62 | ||
|
|
803ff1a283 | ||
|
|
6ead6aa005 | ||
|
|
4c634762b4 | ||
|
|
ac245f96e4 | ||
|
|
7d152965ce | ||
|
|
8846a2f95c | ||
|
|
bdd00d8b65 | ||
|
|
fe73b14eb1 | ||
|
|
49f076d86d | ||
|
|
58bf7fd1f3 | ||
|
|
88099c8617 | ||
|
|
ce4d1f461a | ||
|
|
78fa766fcc | ||
|
|
56d3ff33f1 | ||
|
|
b5b1692593 | ||
|
|
8743d3fbfa | ||
|
|
9f72a9753e | ||
|
|
d8b59aa17f | ||
|
|
88fa420b20 | ||
|
|
83dc3a81f1 | ||
|
|
4205bbf243 | ||
|
|
51bce52d57 | ||
|
|
ded99df98a | ||
|
|
ccb50bfdae | ||
|
|
defbe48dee | ||
|
|
f26fb0098f | ||
|
|
2aeeb28dd1 | ||
|
|
98fca35318 | ||
|
|
4aab1d23e6 | ||
|
|
8b6e05adff | ||
|
|
9d603e9985 | ||
|
|
92779408d3 | ||
|
|
cda0222064 | ||
|
|
dc87c5e452 | ||
|
|
efa7f8f376 | ||
|
|
d8f1a57876 | ||
|
|
2231d5e398 | ||
|
|
ae709b5c03 | ||
|
|
4012c4a96e | ||
|
|
e5311586ab | ||
|
|
df8d00cc1f | ||
|
|
38b7eede1e | ||
|
|
806b410c6a | ||
|
|
aaa1b6f683 | ||
|
|
5a3e01fa80 | ||
|
|
f99aff1b31 | ||
|
|
36744a30fb | ||
|
|
e901bb7c99 | ||
|
|
f90e7ef7ea | ||
|
|
de68b8f23c | ||
|
|
215b3cedda | ||
|
|
4c2fb8f3a2 | ||
|
|
02f96efc50 | ||
|
|
904c73d2d2 | ||
|
|
639e672bd1 | ||
|
|
3486302514 | ||
|
|
057af4697c | ||
|
|
e99ae17a80 | ||
|
|
b5ed614839 | ||
|
|
25855059af | ||
|
|
8260590346 | ||
|
|
acfeb869a3 | ||
|
|
4fa2babacc | ||
|
|
9f4820f6ec | ||
|
|
0e84dafdf0 | ||
|
|
e174d31fdd | ||
|
|
0f5ec05d8f | ||
|
|
705a7310e6 | ||
|
|
55ce26e454 | ||
|
|
f315c93302 | ||
|
|
26f8887422 | ||
|
|
22d8cdfe90 | ||
|
|
3a0ce6acb9 | ||
|
|
60521783c3 | ||
|
|
add439b06b | ||
|
|
bc38a2736d | ||
|
|
1748928e45 | ||
|
|
a8976464ca | ||
|
|
7475f7d957 | ||
|
|
20d354cc0c | ||
|
|
de22d2b1ba | ||
|
|
0619272767 | ||
|
|
f5eed454d4 | ||
|
|
87a7886005 | ||
|
|
f405a468ae | ||
|
|
5e6d9998f5 | ||
|
|
3f594c2e84 | ||
|
|
ede608ed43 | ||
|
|
7e2edad8ef | ||
|
|
0cb9227a73 | ||
|
|
5dca96c1a4 | ||
|
|
d585382f0e | ||
|
|
287d2e0603 | ||
|
|
801ff8319f | ||
|
|
31fc48f0a8 | ||
|
|
702962878b | ||
|
|
52467b2ac9 | ||
|
|
f197198ca3 | ||
|
|
04bdd7af72 | ||
|
|
50837129b2 | ||
|
|
469e488308 | ||
|
|
b0c75a1ff9 | ||
|
|
904ae96795 | ||
|
|
16233bc546 | ||
|
|
b14a7da5d4 | ||
|
|
85f6349c78 | ||
|
|
82068ec56c | ||
|
|
d5bc4ee798 | ||
|
|
5594718b6b | ||
|
|
93852b08f3 | ||
|
|
75dd3ec210 | ||
|
|
03f93dc7a2 | ||
|
|
b1405f3cff | ||
|
|
1151dac5f0 | ||
|
|
8f1151a00e | ||
|
|
b78d11d328 | ||
|
|
2556f45f2e | ||
|
|
8438c557cf | ||
|
|
e403140201 | ||
|
|
b3eed7374e | ||
|
|
fde0e9e84d | ||
|
|
5957f828b0 | ||
|
|
2ea23fd107 | ||
|
|
a2a24b957e | ||
|
|
0cdbc6db6e | ||
|
|
7fbf87e615 | ||
|
|
8cc44a64e8 | ||
|
|
ca455e65a3 | ||
|
|
3f7e1f0492 | ||
|
|
0ed0f4d33a | ||
|
|
6391453fab | ||
|
|
0d4628a7fd | ||
|
|
514c454770 | ||
|
|
2289a479b1 | ||
|
|
3ded03b1f9 | ||
|
|
a9d98082aa | ||
|
|
7f7e9a9fff | ||
|
|
8512a08046 | ||
|
|
4ee51526ae | ||
|
|
53c6d97873 | ||
|
|
e91edf9aed | ||
|
|
d0346e087a | ||
|
|
10bcab6bc1 | ||
|
|
467ad4413e | ||
|
|
597a143ec6 | ||
|
|
2b39c5d87c | ||
|
|
417fa2ffec | ||
|
|
35d7ed7bf1 | ||
|
|
05e2b1f513 | ||
|
|
8444c916d4 | ||
|
|
e5fec0ad07 | ||
|
|
621cff80df | ||
|
|
894bf603e8 | ||
|
|
933fbf7333 | ||
|
|
95ed81c329 | ||
|
|
e4bbb1d348 | ||
|
|
22b3f522ca | ||
|
|
5388a0cd40 | ||
|
|
9127aad2fd | ||
|
|
cc5083cfe0 | ||
|
|
e1656d369a | ||
|
|
4bd690c998 | ||
|
|
6f557aef42 | ||
|
|
a72072c605 | ||
|
|
824e6550f8 | ||
|
|
7510caa0c5 | ||
|
|
dab6eaaa5e | ||
|
|
fedb9229d5 | ||
|
|
347a86198b | ||
|
|
d1ee9ea261 | ||
|
|
380fc765e4 | ||
|
|
d115fb3b0e | ||
|
|
e763d528e2 | ||
|
|
4f31d56eb1 | ||
|
|
45d1510e4e | ||
|
|
bf60281ffb | ||
|
|
d2e7467eb2 | ||
|
|
b2d4b5ee43 | ||
|
|
e4f2fcc0ec | ||
|
|
84dcb76f27 | ||
|
|
2f92056e2a | ||
|
|
59cc8fff19 | ||
|
|
d8882bbfb7 | ||
|
|
149ab3afa2 | ||
|
|
8ff577a2f3 | ||
|
|
3cb4116243 | ||
|
|
6ec1f31765 | ||
|
|
775e08ba65 | ||
|
|
9e40d7155c | ||
|
|
1a5c863a32 | ||
|
|
87ab30669c | ||
|
|
d6abfcd578 | ||
|
|
0f82107535 | ||
|
|
53d3827843 | ||
|
|
0ff863c179 | ||
|
|
142d5c985e | ||
|
|
a89549e8db | ||
|
|
5735b684de | ||
|
|
31f409a3c5 | ||
|
|
65571dd0d5 | ||
|
|
a3e2019c2d | ||
|
|
3af094062e | ||
|
|
d240aa367e | ||
|
|
ebc4ccbbfa | ||
|
|
370c5cc834 | ||
|
|
782c8e72ff | ||
|
|
ed89332acf | ||
|
|
6354a6b07d | ||
|
|
8125252399 | ||
|
|
052a7d54ab | ||
|
|
de577d4e79 | ||
|
|
1b8cd01b1a | ||
|
|
1889c62b85 | ||
|
|
d74bac22b9 | ||
|
|
91626b1c06 | ||
|
|
aa48eeac97 | ||
|
|
6c4b504eca | ||
|
|
633fde4ae5 | ||
|
|
49caa0a775 | ||
|
|
dbb7927a1e | ||
|
|
057c2e25b0 | ||
|
|
ce72aaae7b | ||
|
|
9db560b9a9 | ||
|
|
0cffd98e8e | ||
|
|
d445147be9 | ||
|
|
ac01e6a147 | ||
|
|
ce8bc1320d | ||
|
|
6d2c5fc99a | ||
|
|
3cfbe81038 | ||
|
|
9dc0dfbd54 | ||
|
|
01d12a51f1 | ||
|
|
db6a4eec0a | ||
|
|
5cca9143ab | ||
|
|
ff5a908328 | ||
|
|
91472b84c2 | ||
|
|
d6de87d1d3 | ||
|
|
890d8ea194 | ||
|
|
7df03a1c9e | ||
|
|
f2d67a48c9 | ||
|
|
327c88f4c9 | ||
|
|
ed02cbf92f | ||
|
|
b74edd4069 | ||
|
|
f330884b0a | ||
|
|
2826dcff8f | ||
|
|
859964e0db | ||
|
|
41fedab2d9 | ||
|
|
bb22bee315 | ||
|
|
383d8a8b60 | ||
|
|
78a9bc4a7d | ||
|
|
9087500179 | ||
|
|
5b6eab01af | ||
|
|
3a831b2c01 | ||
|
|
00de1320b0 | ||
|
|
222cfe7fe6 | ||
|
|
3813b13796 | ||
|
|
f7b2d644ef | ||
|
|
25affdcc88 | ||
|
|
2d58fb3b8e | ||
|
|
571e697a7c | ||
|
|
6f17410f88 | ||
|
|
37388ebb0e | ||
|
|
3b5f8ea571 | ||
|
|
388f43f6f7 | ||
|
|
a49ab7cc2f | ||
|
|
847cbe9d5d | ||
|
|
22274f7982 | ||
|
|
84f7e213ab | ||
|
|
891a2a1f47 | ||
|
|
9423a7e23e | ||
|
|
81c602c7d2 | ||
|
|
4d903127ad | ||
|
|
7b52ba87a3 | ||
|
|
6dd97ccf5c | ||
|
|
e0b6fdeb8d | ||
|
|
4b6c00c50a | ||
|
|
24897eb94c | ||
|
|
4aae1ff6de | ||
|
|
dd344b43e8 | ||
|
|
2696148d64 | ||
|
|
482009d170 | ||
|
|
39fc0060fb | ||
|
|
71ded03123 | ||
|
|
94542abf2e | ||
|
|
b55d557822 | ||
|
|
2537f6f467 | ||
|
|
75261165af | ||
|
|
7100a10127 | ||
|
|
ee2ba635f2 | ||
|
|
9e8eba11fa | ||
|
|
55dca5b6a3 | ||
|
|
2c48a16e99 | ||
|
|
03ec49be8f | ||
|
|
50e57c346f | ||
|
|
7743ed2dc6 | ||
|
|
4b2d93f103 | ||
|
|
53218f33b9 | ||
|
|
2ccc3721ef | ||
|
|
fdd1b8cac1 | ||
|
|
a17b5364ea | ||
|
|
7ac88cb05b | ||
|
|
f19f4a36bc | ||
|
|
b19414f3bf | ||
|
|
a84258d769 | ||
|
|
884b4600a4 | ||
|
|
dceccaf169 | ||
|
|
e10b362bdb | ||
|
|
40e68d6514 | ||
|
|
96a60e15ce | ||
|
|
e4d23dd01d | ||
|
|
5e9c226229 | ||
|
|
f2d438a1fd | ||
|
|
6e1eb8add9 | ||
|
|
d2ee5c0330 | ||
|
|
7abc1bef40 | ||
|
|
74e7043cc7 | ||
|
|
6ac5474790 | ||
|
|
13fcb1925a | ||
|
|
f2660c0a29 | ||
|
|
b890093c44 | ||
|
|
3b5657f0c1 | ||
|
|
1fb1868afe | ||
|
|
327cdcb611 | ||
|
|
c341edda35 | ||
|
|
e02ec80192 | ||
|
|
c54335e7df | ||
|
|
3a181bac1f | ||
|
|
ecc0705f83 | ||
|
|
d8896f0dba | ||
|
|
950ad5a412 | ||
|
|
dbb4b00a12 | ||
|
|
de303d8ba4 | ||
|
|
bc49957458 | ||
|
|
607892b741 | ||
|
|
2c6f6c9f4e | ||
|
|
28d3fe1d6d | ||
|
|
75e1c6f295 | ||
|
|
cb3327fe48 | ||
|
|
bef2135a44 | ||
|
|
96432241bd | ||
|
|
60aea74f44 | ||
|
|
f6bceacaff | ||
|
|
7204dee3c7 | ||
|
|
b707589e7e | ||
|
|
4cd143e4d8 | ||
|
|
a217c08b17 | ||
|
|
cfc72d4fff | ||
|
|
8d5f8d5a6b | ||
|
|
cc235d203d | ||
|
|
bbc1469272 | ||
|
|
c54a1f3e41 | ||
|
|
2352a90566 | ||
|
|
d58d4ec93c | ||
|
|
6d44a4dfd1 | ||
|
|
c8930e80a8 | ||
|
|
b08617ff71 | ||
|
|
a46453347f | ||
|
|
247c89d6ba | ||
|
|
943c785619 | ||
|
|
a37c810173 | ||
|
|
68531e23a1 | ||
|
|
e60b8f181d | ||
|
|
33707c6d63 | ||
|
|
bb5fe4d874 | ||
|
|
da6093a5a1 | ||
|
|
3b156caf78 | ||
|
|
afa9e50587 | ||
|
|
6a4e59677e | ||
|
|
bbb1057d06 | ||
|
|
38ef925ab7 | ||
|
|
280c8351ac | ||
|
|
47bd0a6614 | ||
|
|
6d5e887a20 | ||
|
|
f50b105d06 | ||
|
|
d2d0bc31bf | ||
|
|
d7287d60dd | ||
|
|
bea36dc33f | ||
|
|
c01feaaa79 | ||
|
|
e046fa584a | ||
|
|
fd0ffa7b40 | ||
|
|
30384a7e67 | ||
|
|
6e34322dec | ||
|
|
4a4d2155d8 | ||
|
|
fbd932410a | ||
|
|
af0306d48d | ||
|
|
1eda9554d4 | ||
|
|
20e1c5f7d9 | ||
|
|
de3a979f69 | ||
|
|
1b30a0bbf9 | ||
|
|
648e8fbeed | ||
|
|
78545fee42 | ||
|
|
abee328f20 | ||
|
|
e80fd3f9e1 | ||
|
|
d3c140bbf2 | ||
|
|
67eb187826 | ||
|
|
47345219a3 | ||
|
|
f72a900892 | ||
|
|
5cd33853f2 | ||
|
|
d8e5ac00bb | ||
|
|
2fe2be4df3 | ||
|
|
069a2d047d | ||
|
|
4f287f1792 | ||
|
|
07fbba3935 |
No files matched your search
+11
-2
@@ -2,6 +2,7 @@
|
||||
*.a
|
||||
*.d
|
||||
*.exe
|
||||
*.pyc
|
||||
.depend
|
||||
|
||||
/config.h
|
||||
@@ -10,7 +11,7 @@
|
||||
/mpv
|
||||
/mpv.app
|
||||
/version.h
|
||||
/mpvcore/input/input_conf.h
|
||||
/input/input_conf.h
|
||||
/tags
|
||||
/TAGS
|
||||
/video/out/gl_video_shaders.h
|
||||
@@ -18,10 +19,18 @@
|
||||
/demux/ebml_defs.c
|
||||
/demux/ebml_types.h
|
||||
/sub/osd_font.h
|
||||
/mpvcore/lua/*.inc
|
||||
/player/lua/*.inc
|
||||
/DOCS/man/*/mpv.1
|
||||
/DOCS/man/*/mpv.aux
|
||||
/DOCS/man/*/mpv.log
|
||||
/DOCS/man/*/mpv.out
|
||||
/DOCS/man/*/mpv.pdf
|
||||
/DOCS/man/*/mpv.toc
|
||||
|
||||
/waf
|
||||
/build
|
||||
/.waf*
|
||||
/.lock-waf_*
|
||||
|
||||
/old_build
|
||||
/Makefile
|
||||
+5
-5
@@ -13,17 +13,17 @@ branches:
|
||||
- ci
|
||||
|
||||
before_install: ./travis-deps libass-stable $LIBAV
|
||||
script: ./configure && make
|
||||
script:
|
||||
- ./bootstrap.py
|
||||
- ./waf configure
|
||||
- ./waf build
|
||||
|
||||
notifications-policy: ¬ifications-policy
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
- mpv-team@googlegroups.com
|
||||
<<: *notifications-policy
|
||||
email: false
|
||||
irc:
|
||||
channels:
|
||||
- "irc.freenode.org#mpv-player-dev"
|
||||
|
||||
@@ -3,7 +3,7 @@ produce both 32 bit and 64 bit executables. MinGW-w64 is available from
|
||||
http://mingw-w64.sourceforge.net.
|
||||
|
||||
You have to run mpv's configure with these arguments:
|
||||
./configure --enable-cross-compile --target=i686-w64-mingw32
|
||||
DEST_OS=win32 TARGET=i686-w64-mingw32 ./waf configure
|
||||
|
||||
Using mingw-w64-cmake to setup a MinGW-w64 environment is recommended (this will
|
||||
also build mpv and its dependencies): https://github.com/lachs0r/mingw-w64-cmake
|
||||
@@ -22,7 +22,7 @@ Example with MXE
|
||||
# or more!
|
||||
|
||||
cd /opt
|
||||
git clone https://github.com/mxe/mxe
|
||||
git clone https://github.com/mxe/mxe mingw
|
||||
cd mingw
|
||||
|
||||
# Edit the MXE target, so that MinGW-w64 for 32 bit targets is built.
|
||||
@@ -44,8 +44,8 @@ make pthreads
|
||||
git clone https://github.com/mpv-player/mpv.git
|
||||
cd mpv
|
||||
export PATH=/opt/mingw/usr/bin/:$PATH
|
||||
./configure --enable-cross-compile --target=i686-w64-mingw32
|
||||
make
|
||||
DEST_OS=win32 TARGET=i686-w64-mingw32 ./waf configure
|
||||
./waf build
|
||||
|
||||
# This should work. Note however that MXE’s ffmpeg package might be very old
|
||||
# in order to avoid breaking e.g. xine-lib, so you might want to update that
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
EDL files
|
||||
=========
|
||||
|
||||
EDL files basically concatenate ranges of video/audio from multiple source
|
||||
files into a single continuous virtual file. Each such range is called a
|
||||
segment, and consists of source file, source offset, and segment length.
|
||||
|
||||
For example::
|
||||
|
||||
# mpv EDL v0
|
||||
f1.mkv,10,20
|
||||
f2.mkv
|
||||
f1.mkv,40,10
|
||||
|
||||
This would skip the first 10 seconds of the file f1.mkv, then play the next
|
||||
20 seconds, then switch to the file f2.mkv and play all of it, then switch
|
||||
back to f1.mkv, skip to the 40 second mark, and play 10 seconds, and then
|
||||
stop playback. The difference to specifying the files directly on command
|
||||
line (and using ``--{ --start=10 --length=20 f1.mkv --}`` etc.) is that the
|
||||
virtual EDL file appears as a virtual timeline (like a single file), instead
|
||||
as a playlist.
|
||||
|
||||
The general simplified syntax is::
|
||||
|
||||
# mpv EDL v0
|
||||
<filename>
|
||||
<filename>,<start in seconds>,<length in seconds>
|
||||
|
||||
If the start time is omitted, 0 is used. If the length is omitted, the
|
||||
estimated duration of the source file is used.
|
||||
|
||||
Note::
|
||||
|
||||
mpv can't use ordered chapter files or libquvi-resolved URLs in EDL
|
||||
entries. Usage of relative or absolute paths as well as any protocol
|
||||
prefixes is prevented for security reasons.
|
||||
|
||||
|
||||
Syntax of mpv EDL files
|
||||
=======================
|
||||
|
||||
Generally, the format is relatively strict. No superfluous whitespace (except
|
||||
empty lines and commented lines) are allowed. You must use UNIX line breaks.
|
||||
|
||||
The first line in the file must be ``# mpv EDL v0``. This designates that the
|
||||
file uses format version 0, which is not frozen yet and may change any time.
|
||||
(If you need a stable EDL file format, make a feature request. Likewise, if
|
||||
you have suggestions for improvements, it's not too late yet.)
|
||||
|
||||
The rest of the lines belong to one of these classes:
|
||||
|
||||
1) An empty or commented line. A comment starts with ``#``, which must be the
|
||||
first character in the line. The rest of the line (up until the next line
|
||||
break) is ignored. An empty line has 0 bytes between two line feed bytes.
|
||||
2) A segment entry in all other cases.
|
||||
|
||||
Each segment entry consists of a list of named or unnamed parameters.
|
||||
Parameters are separated with ``,``. Named parameters consist of a name,
|
||||
followed by ``=``, followed by the value. Unnamed parameters have only a
|
||||
value, and the name is implicit from the parameter position.
|
||||
|
||||
Syntax::
|
||||
|
||||
segment_entry ::= <param> ( <param> ',' )*
|
||||
param ::= [ <name> '=' ] ( <value> | '%' <number> '%' <valuebytes> )
|
||||
|
||||
The ``name`` string can consist of any characters, except ``=%,;\n``. The
|
||||
``value`` string can consist of any characters except of ``,;\n``.
|
||||
|
||||
The construct starting with ``%`` allows defining any value with arbitrary
|
||||
contents inline, where ``number`` is an integer giving the number of bytes in
|
||||
``valuebytes``. If a parameter value contains disallowed characters, it has to
|
||||
be guarded by a length specifier using this syntax.
|
||||
|
||||
The parameter name defines the meaning of the parameter:
|
||||
|
||||
1) ``file``, the source file to use for this segment.
|
||||
2) ``start``, a time value that specifies the start offset into the source file.
|
||||
3) ``length``, a time value that specifies the length of the segment.
|
||||
|
||||
(Currently, time values are floating point values in seconds.)
|
||||
|
||||
Unnamed parameters carry implicit names. The parameter position determines
|
||||
which of the parameters listed above is set. For example, the second parameter
|
||||
implicitly uses the name ``start``.
|
||||
|
||||
Example::
|
||||
|
||||
# mpv EDL v0
|
||||
%18%filename,with,.mkv,10,length=20,param3=%13%value,escaped,param4=value2
|
||||
|
||||
this sets ``file`` to ``filename,with,.mkv``, ``start`` to ``10``, ``length``
|
||||
to ``20``, ``param3`` to ``value,escaped``, ``param4`` to ``value2``.
|
||||
|
||||
Instead of line breaks, the character ``;`` can be used. Line feed bytes and
|
||||
``;`` are treated equally.
|
||||
|
||||
Syntax of EDL URIs
|
||||
==================
|
||||
|
||||
mpv accepts inline EDL data in form of ``edl://`` URIs. Other than the
|
||||
header, the syntax is exactly the same. It's far more convenient to use ``;``
|
||||
instead of line breaks, but that is orthogonal.
|
||||
|
||||
Example: ``edl://f1.mkv,length=5,start=10;f2.mkv,30,20;f3.mkv``
|
||||
-139
@@ -1,139 +0,0 @@
|
||||
Format of mplayer2 EDL files
|
||||
============================
|
||||
|
||||
The first line in the file must be ``mplayer EDL file, version 2``.
|
||||
The rest of the lines belong to one of these classes:
|
||||
|
||||
1) lines specifying source files
|
||||
2) empty lines
|
||||
3) lines specifying timeline segments.
|
||||
|
||||
Lines beginning with ``<`` specify source files. These lines first
|
||||
contain an identifier used to refer to the source file later, then the
|
||||
filename separated by whitespace. The identifier must start with a
|
||||
letter. Filenames that start or end with whitespace or contain
|
||||
newlines are not supported.
|
||||
|
||||
On other lines ``#`` characters delimit comments. Lines that contain
|
||||
only whitespace after comments have been removed are ignored.
|
||||
|
||||
Timeline segments must appear in the file in chronological order. Each
|
||||
segment has the following information associated with it:
|
||||
|
||||
- duration
|
||||
- output start time
|
||||
- output end time (= output start time + duration)
|
||||
- source id (specifies the file the content of the segment comes from)
|
||||
- source start time (timestamp in the source file)
|
||||
- source end time (= source start time + duration)
|
||||
|
||||
The output timestamps must form a continuous timeline from 0 to the
|
||||
end of the last segment, such that each new segment starts from the
|
||||
time the previous one ends at. Source files and times may change
|
||||
arbitrarily between segments.
|
||||
|
||||
The general format for lines specifying timeline segments is
|
||||
[output time info] source_id [source time info]
|
||||
source_id must be an identifier defined on a ``<`` line. Both the time
|
||||
info parts consists of zero or more of the following elements:
|
||||
|
||||
1) ``timestamp``
|
||||
2) ``-timestamp``
|
||||
3) ``+duration``
|
||||
4) ``*``
|
||||
5) ``-*``
|
||||
|
||||
, where ``timestamp`` and ``duration`` are decimal numbers (computations
|
||||
are done with nanosecond precision). Whitespace around ``+`` and ``-`` is
|
||||
optional. 1) and 2) specify start and end time of the segment on
|
||||
output or source side. 3) specifies duration; the semantics are the
|
||||
same whether this appears on output or source side. 4) and 5) are
|
||||
ignored on the output side (they're always implicitly assumed). On the
|
||||
source side 4) specifies that the segment starts where the previous
|
||||
segment *using this source* ended; if there was no previous segment
|
||||
time 0 is used. 5) specifies that the segment ends where the next
|
||||
segment using this source starts.
|
||||
|
||||
Redundant information may be omitted. It will be filled in using the
|
||||
following rules:
|
||||
|
||||
- output start for first segment is 0
|
||||
- two of [output start, output end, duration] imply third
|
||||
- two of [source start, source end, duration] imply third
|
||||
- output start = output end of previous segment
|
||||
- output end = output start of next segment
|
||||
- if ``*``, source start = source end of earlier segment
|
||||
- if ``-*``, source end = source start of a later segment
|
||||
|
||||
As a special rule, a last zero-duration segment without a source
|
||||
specification may appear. This will produce no corresponding segment
|
||||
in the resulting timeline, but can be used as syntax to specify the
|
||||
end time of the timeline (with effect equal to adding -time on the
|
||||
previous line).
|
||||
|
||||
Examples::
|
||||
|
||||
mplayer EDL file, version 2
|
||||
< id1 filename
|
||||
|
||||
0 id1 123
|
||||
100 id1 456
|
||||
200 id1 789
|
||||
300
|
||||
|
||||
All segments come from the source file ``filename``. First segment
|
||||
(output time 0-100) comes from time 123-223, second 456-556, third
|
||||
789-889.
|
||||
|
||||
::
|
||||
|
||||
mplayer EDL file, version 2
|
||||
< f filename
|
||||
f 60-120
|
||||
f 600-660
|
||||
f 30- 90
|
||||
|
||||
Play first seconds 60-120 from the file, then 600-660, then 30-90.
|
||||
|
||||
::
|
||||
|
||||
mplayer EDL file, version 2
|
||||
< id1 filename1
|
||||
< id2 filename2
|
||||
|
||||
+10 id1 *
|
||||
+10 id2 *
|
||||
+10 id1 *
|
||||
+10 id2 *
|
||||
+10 id1 *
|
||||
+10 id2 *
|
||||
|
||||
This plays time 0-10 from filename1, then 0-10 from filename1, then
|
||||
10-20 from filename1, then 10-20 from filename2, then 20-30 from
|
||||
filename1, then 20-30 from filename2.
|
||||
|
||||
::
|
||||
|
||||
mplayer EDL file, version 2
|
||||
< t1 filename1
|
||||
< t2 filename2
|
||||
|
||||
t1 * +2 # segment 1
|
||||
+2 t2 100 # segment 2
|
||||
t1 * # segment 3
|
||||
t2 *-* # segment 4
|
||||
t1 3 -* # segment 5
|
||||
+0.111111 t2 102.5 # segment 6
|
||||
7.37 t1 5 +1 # segment 7
|
||||
|
||||
This rather pathological example illustrates the rules for filling in
|
||||
implied data. All the values can be determined by recursively applying
|
||||
the rules given above, and the full end result is this::
|
||||
|
||||
+2 0-2 t1 0-2 # segment 1
|
||||
+2 2-4 t2 100-102 # segment 2
|
||||
+0.758889 4-4.758889 t1 2-2.758889 # segment 3
|
||||
+0.5 4.4758889-5.258889 t2 102-102.5 # segment 4
|
||||
+2 5.258889-7.258889 t1 3-5 # segment 5
|
||||
+0.111111 7.258889-7.37 t2 102.5-102.611111 # segment 6
|
||||
+1 7.37-8.37 t1 5-6 # segment 7
|
||||
+5
-6
@@ -43,7 +43,7 @@ One can then encode using this profile using the command::
|
||||
mpv infile -o outfile.mp4 -profile myencprofile
|
||||
|
||||
Some example profiles are provided in a file
|
||||
etc/encoding-example-profiles.conf; as for this, see below.
|
||||
etc/encoding-profiles.conf; as for this, see below.
|
||||
|
||||
|
||||
Encoding examples
|
||||
@@ -89,13 +89,12 @@ Device targets
|
||||
As the options for various devices can get complex, profiles can be used.
|
||||
|
||||
An example profile file for encoding is provided in
|
||||
etc/encoding-example-profiles.conf in the source tree. You can include it into
|
||||
your configuration by doing::
|
||||
etc/encoding-profiles.conf in the source tree. This file is installed and loaded
|
||||
by default (if libavfilter is enabled at compilation). If you want to modify
|
||||
it, you can replace and it with your own copy by doing::
|
||||
|
||||
mkdir -p ~/.mpv
|
||||
curl https://raw.github.com/mpv-player/mpv/master/etc/encoding-example-profiles.conf \
|
||||
> ~/.mpv/encoding-profiles.conf
|
||||
echo "include = $HOME/.mpv/encoding-profiles.conf" >> ~/.mpv/config
|
||||
cp /etc/mpv/encoding-profiles.conf ~/.mpv/encoding-profiles.conf
|
||||
|
||||
Refer to the top of that file for more comments - in a nutshell, the following
|
||||
options are added by it::
|
||||
|
||||
@@ -10,7 +10,6 @@ latex-preamble: \usepackage[usenames,dvipsnames]{xcolor}
|
||||
\makeatletter
|
||||
\renewcommand{\maketitle}{
|
||||
\begin{center}
|
||||
\includegraphics{etc/mpv-icon-8bit-64x64.png}\\
|
||||
\vspace*{-1.5em}
|
||||
\begin{Huge}
|
||||
\@title
|
||||
|
||||
+98
-73
@@ -11,6 +11,12 @@ syntax is:
|
||||
|
||||
To get a full list of available audio filters, see ``--af=help``.
|
||||
|
||||
You can also set defaults for each filter. The defaults are applied before the
|
||||
normal filter parameters.
|
||||
|
||||
``--af-defaults=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Set defaults for each filter.
|
||||
|
||||
Audio filters are managed in lists. There are a few commands to manage the
|
||||
filter list:
|
||||
|
||||
@@ -35,7 +41,7 @@ Available filters are:
|
||||
This filter is automatically enabled if the audio output does not support
|
||||
the audio configuration of the file being played.
|
||||
|
||||
It supports only the following sample formats: u8, s16ne, s32ne, floatne.
|
||||
It supports only the following sample formats: u8, s16, s32, float.
|
||||
|
||||
``filter-size=<length>``
|
||||
Length of the filter with respect to the lower sampling rate. (default:
|
||||
@@ -50,9 +56,9 @@ Available filters are:
|
||||
entries. (default: no)
|
||||
``no-detach``
|
||||
Do not detach if input and output audio format/rate/channels match.
|
||||
You should add this option if you specify additional parameters, as
|
||||
automatically inserted lavrresample instances will use the default
|
||||
settings.
|
||||
(If you just want to set defaults for this filter that will be used
|
||||
even by automatically inserted lavrresample instances, you should
|
||||
prefer setting them with ``--af-defaults=lavrresample:...``.)
|
||||
``o=<string>``
|
||||
Set AVOptions on the SwrContext or AVAudioResampleContext. These should
|
||||
be documented by FFmpeg or Libav.
|
||||
@@ -64,17 +70,18 @@ Available filters are:
|
||||
outputting to S/PDIF. If the input sample rate is not 48 kHz, 44.1 kHz or
|
||||
32 kHz, it will be resampled to 48 kHz.
|
||||
|
||||
``<tospdif>``
|
||||
Output raw AC-3 stream if zero or not set, output to S/PDIF for
|
||||
passthrough when ``<tospdif>`` is set non-zero.
|
||||
``<bitrate>``
|
||||
The bitrate use for the AC-3 stream. Set it to either 384 or 384000
|
||||
to get 384 kbps.
|
||||
``tospdif=<yes|no>``
|
||||
Output raw AC-3 stream if ``no``, output to S/PDIF for
|
||||
passthrough if ``yes`` (default).
|
||||
|
||||
``bitrate=<rate>``
|
||||
The bitrate use for the AC-3 stream. Set it to 384 to get 384 kbps.
|
||||
|
||||
Valid values: 32, 40, 48, 56, 64, 80, 96, 112, 128,
|
||||
160, 192, 224, 256, 320, 384, 448, 512, 576, 640.
|
||||
|
||||
Default bitrate is based on the input channel number:
|
||||
The special value ``default`` selects a default bitrate based on the
|
||||
input channel number:
|
||||
|
||||
:1ch: 96
|
||||
:2ch: 192
|
||||
@@ -83,7 +90,7 @@ Available filters are:
|
||||
:5ch: 448
|
||||
:6ch: 448
|
||||
|
||||
``<minchn>``
|
||||
``minchn=<n>``
|
||||
If the input channel number is less than ``<minchn>``, the filter will
|
||||
detach itself (default: 5).
|
||||
|
||||
@@ -140,7 +147,7 @@ Available filters are:
|
||||
0 no matrix decoding (default)
|
||||
==== ===================================
|
||||
|
||||
``equalizer=[g1:g2:g3:...:g10]``
|
||||
``equalizer=g1:g2:g3:...:g10``
|
||||
10 octave band graphic equalizer, implemented using 10 IIR band-pass
|
||||
filters. This means that it works regardless of what type of audio is
|
||||
being played back. The center frequencies for the 10 bands are:
|
||||
@@ -177,7 +184,7 @@ Available filters are:
|
||||
Would amplify the sound in the upper and lower frequency region
|
||||
while canceling it almost completely around 1kHz.
|
||||
|
||||
``channels=nch[:nr:from1:to1:from2:to2:from3:to3:...]``
|
||||
``channels=nch[:routes]``
|
||||
Can be used for adding, removing, routing and copying audio channels. If
|
||||
only ``<nch>`` is given, the default routing is used. It works as follows:
|
||||
If the number of output channels is greater than the number of input
|
||||
@@ -188,45 +195,52 @@ Available filters are:
|
||||
|
||||
``<nch>``
|
||||
number of output channels (1-8)
|
||||
``<nr>``
|
||||
number of routes (1-8)
|
||||
``<from1:to1:from2:to2:from3:to3:...>``
|
||||
Pairs of numbers between 0 and 7 that define where to route each
|
||||
channel.
|
||||
``<routes>``
|
||||
List of ``,`` separated routes, in the form ``from1-to1,from2-to2,...``.
|
||||
Each pair defines where to route each channel. There can be at most
|
||||
8 routes. Without this argument, the default routing is used. Since
|
||||
``,`` is also used to separate filters, you must quote this argument
|
||||
with ``[...]`` or similar.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --af=channels=4:4:0:1:1:0:2:2:3:3 media.avi``
|
||||
``mpv --af=channels=4:[0-1,1-0,0-2,1-3] media.avi``
|
||||
Would change the number of channels to 4 and set up 4 routes that
|
||||
swap channel 0 and channel 1 and leave channel 2 and 3 intact.
|
||||
Observe that if media containing two channels were played back,
|
||||
channels 2 and 3 would contain silence but 0 and 1 would still be
|
||||
swapped.
|
||||
|
||||
``mpv --af=channels=6:4:0:0:0:1:0:2:0:3 media.avi``
|
||||
``mpv --af=channels=6:[0-0,0-1,0-2,0-3] media.avi``
|
||||
Would change the number of channels to 6 and set up 4 routes that
|
||||
copy channel 0 to channels 0 to 3. Channel 4 and 5 will contain
|
||||
silence.
|
||||
|
||||
``force=in-format:in-srate:in-channels:out-format:out-srate:out-channels``
|
||||
.. note::
|
||||
|
||||
You should probably not use this filter. If you want to change the
|
||||
output channel layout, try the ``format`` filter, which can make mpv
|
||||
automatically up- and downmix standard channel layouts.
|
||||
|
||||
``format=format:srate:channels:out-format:out-srate:out-channels``
|
||||
Force a specific audio format/configuration without actually changing the
|
||||
audio data. Keep in mind that the filter system might auto-insert actual
|
||||
conversion filters before or after this filter if needed.
|
||||
|
||||
All parameters are optional. The ``in-`` variants restrict what the filter
|
||||
accepts as input. The ``out-`` variants change the audio format, without
|
||||
All parameters are optional. The first 3 parameters restrict what the filter
|
||||
accepts as input. The ``out-`` parameters change the audio format, without
|
||||
actually doing a conversion. The data will be 'reinterpreted' by the
|
||||
filters or audio outputs following this filter.
|
||||
|
||||
``<in-format>``
|
||||
Force conversion to this format. See ``format`` filter for valid audio
|
||||
format values.
|
||||
``<format>``
|
||||
Force conversion to this format. Use ``--af=format=format=help`` to get
|
||||
a list of valid formats.
|
||||
|
||||
``<in-srate>``
|
||||
``<srate>``
|
||||
Force conversion to a specific sample rate. The rate is an integer,
|
||||
48000 for example.
|
||||
|
||||
``<in-channels>``
|
||||
``<channels>``
|
||||
Force mixing to a specific channel layout. See ``--channels`` option
|
||||
for possible values.
|
||||
|
||||
@@ -236,55 +250,53 @@ Available filters are:
|
||||
|
||||
``<out-channels>``
|
||||
|
||||
``format[=format]``
|
||||
Convert between different sample formats. Automatically enabled when
|
||||
needed by the audio output or another filter. See also ``--format``.
|
||||
See also ``--format``, ``--srate``, and ``--channels`` for related options.
|
||||
Keep in mind that ``--channels`` does not actually force the number of
|
||||
channels in most cases, while this filter can do this.
|
||||
|
||||
``<format>``
|
||||
Sets the desired format. The general form is 'sbe', where 's' denotes
|
||||
the sign (either 's' for signed or 'u' for unsigned), 'b' denotes the
|
||||
number of bits per sample (16, 24 or 32) and 'e' denotes the
|
||||
endianness ('le' means little-endian, 'be' big-endian and 'ne' the
|
||||
endianness of the computer mpv is running on). Valid values (amongst
|
||||
others) are: 's16le', 'u32be' and 'u24ne'. Exceptions to this rule that
|
||||
are also valid format specifiers: u8, s8, floatle, floatbe, floatne,
|
||||
mpeg2, and ac3.
|
||||
*NOTE*: this filter used to be named ``force``. Also, unlike the old
|
||||
``format`` filter, this does not do any actual conversion anymore.
|
||||
Conversion is done by other, automatically inserted filters.
|
||||
|
||||
``volume[=v[:sc[:fast]]]``
|
||||
``convert24``
|
||||
Filter for internal use only. Converts between 24-bit and 32-bit sample
|
||||
formats.
|
||||
|
||||
``convertsignendian``
|
||||
Filter for internal use only. Converts between signed/unsigned formats
|
||||
and formats with different endian.
|
||||
|
||||
``volume[=volumedb[:softclip[:s16]]]``
|
||||
Implements software volume control. Use this filter with caution since it
|
||||
can reduce the signal to noise ratio of the sound. In most cases it is
|
||||
best to use the *Master* volume control of your sound card or the volume
|
||||
knob on your amplifier.
|
||||
|
||||
This filter has a second feature: It measures the overall maximum sound
|
||||
level and prints out that level when mpv exits. This feature currently
|
||||
only works with floating-point data.
|
||||
|
||||
*NOTE*: This filter is not reentrant and can therefore only be enabled
|
||||
once for every audio stream.
|
||||
|
||||
``<v>``
|
||||
``<volumedb>``
|
||||
Sets the desired gain in dB for all channels in the stream from -200dB
|
||||
to +60dB, where -200dB mutes the sound completely and +60dB equals a
|
||||
gain of 1000 (default: 0).
|
||||
``<sc>``
|
||||
Turns soft clipping on (1) or off (0). Soft-clipping can make the
|
||||
``<softclip>``
|
||||
Turns soft clipping on. Soft-clipping can make the
|
||||
sound more smooth if very high volume levels are used. Enable this
|
||||
option if the dynamic range of the loudspeakers is very low.
|
||||
|
||||
*WARNING*: This feature creates distortion and should be considered a
|
||||
last resort.
|
||||
``<fast>``
|
||||
Force S16 sample format if set to 1. Lower quality, but might be faster
|
||||
``<s16>``
|
||||
Force S16 sample format if set. Lower quality, but might be faster
|
||||
in some situations.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=volume=10.1:0 media.avi``
|
||||
``mpv --af=volume=10.1 media.avi``
|
||||
Would amplify the sound by 10.1dB and hard-clip if the sound level
|
||||
is too high.
|
||||
|
||||
``pan=n[:L00:L01:L02:...L10:L11:L12:...Ln0:Ln1:Ln2:...]``
|
||||
``pan=n[:<matrix>]``
|
||||
Mixes channels arbitrarily. Basically a combination of the volume and the
|
||||
channels filter that can be used to down-mix many channels to only a few,
|
||||
e.g. stereo to mono, or vary the "width" of the center speaker in a
|
||||
@@ -296,19 +308,23 @@ Available filters are:
|
||||
|
||||
``<n>``
|
||||
Number of output channels (1-8).
|
||||
``<Lij>``
|
||||
How much of input channel i is mixed into output channel j (0-1). So
|
||||
in principle you first have n numbers saying what to do with the first
|
||||
input channel, then n numbers that act on the second input channel
|
||||
etc. If you do not specify any numbers for some input channels, 0 is
|
||||
assumed.
|
||||
``<matrix>``
|
||||
A list of values ``[L00,L01,L02,...,L10,L11,L12,...,Ln0,Ln1,Ln2,...]``,
|
||||
where each element ``Lij`` means how much of input channel i is mixed
|
||||
into output channel j (range 0-1). So in principle you first have n
|
||||
numbers saying what to do with the first input channel, then n numbers
|
||||
that act on the second input channel etc. If you do not specify any
|
||||
numbers for some input channels, 0 is assumed.
|
||||
Note that the values are separated by ``,``, which is already used
|
||||
by the option parser to separate filters. This is why you must quote
|
||||
the value list with ``[...]`` or similar.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
``mpv --af=pan=1:0.5:0.5 media.avi``
|
||||
``mpv --af=pan=1:[0.5,0.5] media.avi``
|
||||
Would downmix from stereo to mono.
|
||||
|
||||
``mpv --af=pan=3:1:0:0.5:0:1:0.5 media.avi``
|
||||
``mpv --af=pan=3:[1,0,0.5,0,1,0.5] media.avi``
|
||||
Would give 3 channel output leaving channels 0 and 1 intact, and mix
|
||||
channels 0 and 1 into output channel 2 (which could be sent to a
|
||||
subwoofer for example).
|
||||
@@ -316,8 +332,8 @@ Available filters are:
|
||||
.. note::
|
||||
|
||||
If you just want to force remixing to a certain output channel layout,
|
||||
it is easier to use the ``force`` filter. For example,
|
||||
``mpv '--af=force=channels=5.1' '--channels=5.1'`` would always force
|
||||
it is easier to use the ``format`` filter. For example,
|
||||
``mpv '--af=format=channels=5.1' '--channels=5.1'`` would always force
|
||||
remixing audio to 5.1 and output it like this.
|
||||
|
||||
``sub[=fc:ch]``
|
||||
@@ -378,12 +394,12 @@ Available filters are:
|
||||
Would add surround sound decoding with 15ms delay for the sound to
|
||||
the rear speakers.
|
||||
|
||||
``delay[=ch1:ch2:...]``
|
||||
``delay[=[ch1,ch2,...]]``
|
||||
Delays the sound to the loudspeakers such that the sound from the
|
||||
different channels arrives at the listening position simultaneously. It is
|
||||
only useful if you have more than 2 loudspeakers.
|
||||
|
||||
``ch1,ch2,...``
|
||||
``[ch1,ch2,...]``
|
||||
The delay in ms that should be imposed on each channel (floating point
|
||||
number between 0 and 1000).
|
||||
|
||||
@@ -402,11 +418,11 @@ Available filters are:
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=delay=10.5:10.5:0:0:7:0 media.avi``
|
||||
``mpv --af=delay=[10.5,10.5,0,0,7,0] media.avi``
|
||||
Would delay front left and right by 10.5ms, the two rear channels
|
||||
and the subwoofer by 0ms and the center channel by 7ms.
|
||||
|
||||
``export[=mmapped_file[:nsamples]]``
|
||||
``export=mmapped_file:nsamples]``
|
||||
Exports the incoming signal to other processes using memory mapping
|
||||
(``mmap()``). Memory mapped areas contain a header::
|
||||
|
||||
@@ -418,7 +434,7 @@ Available filters are:
|
||||
The rest is payload (non-interleaved) 16-bit data.
|
||||
|
||||
``<mmapped_file>``
|
||||
File to map data to (default: ``~/.mpv/mpv-af_export``).
|
||||
File to map data to (required)
|
||||
``<nsamples>``
|
||||
number of samples per channel (default: 512).
|
||||
|
||||
@@ -459,7 +475,7 @@ Available filters are:
|
||||
This filter can cause distortion with audio signals that have a very
|
||||
large dynamic range.
|
||||
|
||||
``ladspa=file:label[:controls...]``
|
||||
``ladspa=file:label:[<control0>,<control1>,...]``
|
||||
Load a LADSPA (Linux Audio Developer's Simple Plugin API) plugin. This
|
||||
filter is reentrant, so multiple LADSPA plugins can be used at once.
|
||||
|
||||
@@ -475,12 +491,21 @@ Available filters are:
|
||||
one filter, but others contain many of them. Entering 'help' here
|
||||
will list all available filters within the specified library, which
|
||||
eliminates the use of 'listplugins' from the LADSPA SDK.
|
||||
``<controls>``
|
||||
Controls are zero or more floating point values that determine the
|
||||
behavior of the loaded plugin (for example delay, threshold or gain).
|
||||
``[<control0>,<control1>,...]``
|
||||
Controls are zero or more ``,`` separated floating point values that
|
||||
determine the behavior of the loaded plugin (for example delay,
|
||||
threshold or gain).
|
||||
In verbose mode (add ``-v`` to the mpv command line), all
|
||||
available controls and their valid ranges are printed. This eliminates
|
||||
the use of 'analyseplugin' from the LADSPA SDK.
|
||||
Note that ``,`` is already used by the option parser to separate
|
||||
filters, so you must quote the list of values with ``[...]`` or
|
||||
similar.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``mpv --af=ladspa='/usr/lib/ladspa/delay.so':delay_5s:[0.5,0.2] media.avi``
|
||||
Does something.
|
||||
|
||||
``karaoke``
|
||||
Simple voice removal filter exploiting the fact that voice is usually
|
||||
@@ -547,7 +572,7 @@ Available filters are:
|
||||
``mpv --af=scaletempo=stride=30:overlap=.50:search=10 media.ogg``
|
||||
Would tweak the quality and performace parameters.
|
||||
|
||||
``mpv --af=format=floatne,scaletempo media.ogg``
|
||||
``mpv --af=format=float,scaletempo media.ogg``
|
||||
Would make scaletempo use float code. Maybe faster on some
|
||||
platforms.
|
||||
|
||||
|
||||
+22
-2
@@ -10,6 +10,12 @@ syntax is:
|
||||
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.
|
||||
|
||||
.. note::
|
||||
|
||||
See ``--ao=help`` for a list of compiled-in audio output drivers.
|
||||
@@ -23,10 +29,13 @@ in the list. Suboptions are optional and can mostly be omitted.
|
||||
Available audio output drivers are:
|
||||
|
||||
``alsa`` (Linux only)
|
||||
ALSA 0.9/1.x audio output driver
|
||||
ALSA audio output driver
|
||||
|
||||
``no-block``
|
||||
Sets noblock-mode.
|
||||
``resample=yes``
|
||||
Enable ALSA resampling plugin. (This is disabled by default, because
|
||||
some drivers report incorrect audio delay in some cases.)
|
||||
``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.
|
||||
@@ -150,7 +159,18 @@ Available audio output drivers are:
|
||||
|
||||
``null``
|
||||
Produces no audio output but maintains video playback speed. Use
|
||||
``--no-audio`` for benchmarking.
|
||||
``--ao=null:untimed`` for benchmarking.
|
||||
|
||||
``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``
|
||||
Simulated buffer length in seconds.
|
||||
|
||||
``outburst``
|
||||
Simulated chunk size in samples.
|
||||
|
||||
``pcm``
|
||||
Raw PCM/WAVE file writer audio output
|
||||
|
||||
+14
-1
@@ -103,6 +103,7 @@ General Changes for mplayer2 to mpv
|
||||
* Native VAAPI support
|
||||
* OSX: VDA support using libavcodec hwaccel API insted of FFmpeg's decoder. Up
|
||||
to 2-2.5x reduction in CPU usage.
|
||||
* Make hardware decoding in general work with the ``opengl`` video output.
|
||||
* General bug fixes and removal of long-standing issues
|
||||
* General code cleanups (including refactoring or rewrites of many parts)
|
||||
* Many more changes
|
||||
@@ -135,7 +136,7 @@ Command Line Switches
|
||||
=========================== ========================================
|
||||
``-no<opt>`` ``--no-<opt>`` (add a dash)
|
||||
``-nosound`` ``--no-audio``
|
||||
``-use-filename-title`` ``--title="${filename}"``
|
||||
``-use-filename-title`` ``--title='${filename}'``
|
||||
``-loop 0`` ``--loop=inf``
|
||||
``-hardframedrop`` ``--framedrop=hard``
|
||||
``-osdlevel`` ``--osd-level``
|
||||
@@ -176,12 +177,24 @@ Command Line Switches
|
||||
``-af volnorm`` ``--af=drc`` (renamed)
|
||||
``-zoom`` Inverse available as ``--video-unscaled``
|
||||
``-panscanrange`` ``--video-zoom``, ``--video-pan-x/y``
|
||||
``-pp ...`` ``'--vf=pp=[...]'``
|
||||
``-pphelp`` ``--vf=pp:help``
|
||||
``dvdnav://`` ``dvdnav://menu``
|
||||
=========================== ========================================
|
||||
|
||||
.. note::
|
||||
|
||||
``-opt val`` becomes ``--opt=val``.
|
||||
|
||||
.. note::
|
||||
|
||||
Quite some video filters, video outputs, audio filters, audio outputs, had
|
||||
changes in their option parsing. These aren't mentioned in the table above.
|
||||
|
||||
Also, some video and audio filters have been removed, and you have to use
|
||||
libavfilter (using ``--vf=lavfi=[...]`` or ``--af=lavfi=[...]``) to get
|
||||
them back.
|
||||
|
||||
input.conf and Slave Commands
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
+61
-11
@@ -31,6 +31,10 @@ General Input Command Syntax
|
||||
|
||||
``[Shift+][Ctrl+][Alt+][Meta+]<key> [{<section>}] [<prefixes>] <command> (<argument>)* [; <command>]``
|
||||
|
||||
Note that by default, the right Alt key can be used to create special
|
||||
characters, and thus does not register as a modifier. The option
|
||||
``--no-right-alt-gr`` changes this behavior.
|
||||
|
||||
Newlines always start a new binding. ``#`` starts a comment (outside of quoted
|
||||
string arguments). To bind commands to the ``#`` key, ``SHARP`` can be used.
|
||||
|
||||
@@ -44,8 +48,6 @@ Arguments are separated by whitespace. This applies even to string arguments.
|
||||
For this reason, string arguments should be quoted with ``"``. Inside quotes,
|
||||
C-style escaping can be used.
|
||||
|
||||
Optional arguments can be skipped with ``-``.
|
||||
|
||||
You can bind multiple commands to one key. For example:
|
||||
|
||||
| a show_text "command 1" ; show_text "command 2"
|
||||
@@ -86,6 +88,12 @@ List of Input Commands
|
||||
keyframes
|
||||
Always restart playback at keyframe boundaries (fast).
|
||||
|
||||
``revert_seek``
|
||||
Undoes the ``seek`` command, and some other commands that seek (but not
|
||||
necessarily all of them). Calling this command once will jump to the
|
||||
playback position before the seek. Calling it a second time undoes the
|
||||
``revert_seek`` command itself.
|
||||
|
||||
``frame_step``
|
||||
Play one frame, then pause.
|
||||
|
||||
@@ -112,8 +120,8 @@ List of Input Commands
|
||||
overflow, set the property back to the minimum, on underflow set it to the
|
||||
maximum. If ``up`` or ``down`` is omitted, assume ``up``.
|
||||
|
||||
``speed_mult <value>``
|
||||
Multiply the ``speed`` property by the given value.
|
||||
``multiply <property> <factor>``
|
||||
Multiplies the value of a property with the numeric factor.
|
||||
|
||||
``screenshot [subtitles|video|window|- [single|each-frame]]``
|
||||
Take a screenshot.
|
||||
@@ -185,9 +193,11 @@ List of Input Commands
|
||||
``playlist_clear``
|
||||
Clear the playlist, except the currently played file.
|
||||
|
||||
``playlist_remove <index>``
|
||||
``playlist_remove current|<index>``
|
||||
Remove the playlist entry at the given index. Index values start counting
|
||||
with 0. You cannot remove the entry for the currently played file.
|
||||
with 0. The special value ``current`` removes the current entry. Note that
|
||||
removing the current entry also stops playback and starts playing the next
|
||||
entry.
|
||||
|
||||
``playlist_move <index1> <index2>``
|
||||
Move the playlist entry at index1, so that it takes the place of the
|
||||
@@ -196,9 +206,28 @@ List of Input Commands
|
||||
because index2 refers to the target entry, not the index the entry
|
||||
will have after moving.)
|
||||
|
||||
``run "<command>"``
|
||||
Run the given command with ``/bin/sh -c``. The string is expanded like in
|
||||
`Property Expansion`_.
|
||||
``run "command" "arg1" "arg2" ...``
|
||||
(Unix only)
|
||||
Run the given command. Unlike in MPlayer/mplayer2 and earlier versions of
|
||||
mpv (0.2.x and older), this doesn't call the shell. Instead, the command
|
||||
is run directly, with each argument passed separately. Each argument is
|
||||
expanded like in `Property Expansion`_. Note that there is a static limit
|
||||
of (as of this writing) 10 arguments (this limit could be raised on demand).
|
||||
|
||||
The program is run in a detached way. mpv doesn't wait until the command
|
||||
is completed, but continues playback right after spawning it.
|
||||
|
||||
To get the old behavior, use ``/bin/sh`` and ``-c`` as the first two
|
||||
arguments.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``run "/bin/sh" "-c" "echo ${title} > /tmp/playing"``
|
||||
|
||||
This is not a particularly good example, because it doesn't handle
|
||||
escaping, and a specially prepared file might allow an attacker to
|
||||
execute arbitrary shell commands. It is recommended to write a small
|
||||
shell script, and call that with ``run``.
|
||||
|
||||
``quit [<code>]``
|
||||
Exit the player using the given exit code.
|
||||
@@ -309,6 +338,24 @@ Input Commands that are Possibly Subject to Change
|
||||
- ``b vf set ""`` remove all video filters on ``b``
|
||||
- ``c vf toggle lavfi=gradfun`` toggle debanding on ``c``
|
||||
|
||||
``cycle_values ["!reverse"] <property> "<value1>" "<value2>" ...``
|
||||
Cycle through a list of values. Each invocation of the command will set the
|
||||
given property to the next value in the list. The command maintains an
|
||||
internal counter which value to pick next, and which is initially 0. It is
|
||||
reset to 0 once the last value is reached.
|
||||
|
||||
The internal counter is associated using the property name and the value
|
||||
list. If multiple commands (bound to different keys) use the same name
|
||||
and value list, they will share the internal counter.
|
||||
|
||||
The special argument ``!reverse`` can be used to cycle the value list in
|
||||
reverse. Compared with a command that just lists the value in reverse, this
|
||||
command will actually share the internal counter with the forward-cycling
|
||||
key binding.
|
||||
|
||||
Note that there is a static limit of (as of this writing) 10 arguments
|
||||
(this limit could be raised on demand).
|
||||
|
||||
``enable_section "<section>" [default|exclusive]``
|
||||
Enable all key bindings in the named input section.
|
||||
|
||||
@@ -398,8 +445,7 @@ Input Commands that are Possibly Subject to Change
|
||||
|
||||
Undocumented commands: ``tv_start_scan``, ``tv_step_channel``, ``tv_step_norm``,
|
||||
``tv_step_chanlist``, ``tv_set_channel``, ``tv_last_channel``, ``tv_set_freq``,
|
||||
``tv_step_freq``, ``tv_set_norm``, ``dvb_set_channel``, ``radio_step_channel``,
|
||||
``radio_set_channel``, ``radio_set_freq``, ``radio_step_freq`` (all of these
|
||||
``tv_step_freq``, ``tv_set_norm``, ``dvb_set_channel`` (all of these
|
||||
should be replaced by properties), ``stop`` (questionable use), ``get_property``
|
||||
(?), ``vo_cmdline`` (experimental).
|
||||
|
||||
@@ -487,6 +533,7 @@ Name W Comment
|
||||
``ratio-pos`` x position in current file (0.0-1.0)
|
||||
``time-pos`` x position in current file in seconds
|
||||
``time-remaining`` estimated remaining length of the file in seconds
|
||||
``playtime-remaining`` ``time-remaining`` scaled by the the current ``speed``
|
||||
``chapter`` x current chapter number
|
||||
``edition`` x current MKV edition number
|
||||
``titles`` number of DVD titles
|
||||
@@ -533,6 +580,7 @@ Name W Comment
|
||||
``fps`` container FPS (may contain bogus values)
|
||||
``dwidth`` video width (after filters and aspect scaling)
|
||||
``dheight`` video height
|
||||
``window-scale`` x window size multiplier (1 means video size)
|
||||
``aspect`` x video aspect
|
||||
``osd-width`` last known OSD width (can be 0)
|
||||
``osd-height`` last known OSD height (can be 0)
|
||||
@@ -544,8 +592,10 @@ Name W Comment
|
||||
``video-pan-x`` x see ``--video-pan-x``
|
||||
``video-pan-y`` x see ``--video-pan-y``
|
||||
``video-zoom`` x see ``--video-zoom``
|
||||
``video-unscaled`` x see ``--video-unscaled``
|
||||
``program`` x switch TS program (write-only)
|
||||
``sid`` x current subtitle track (similar to ``--sid``)
|
||||
``secondary-sid`` x secondary subtitle track (``--secondary-sid``)
|
||||
``sub`` x alias for ``sid``
|
||||
``sub-delay`` x see ``--sub-delay``
|
||||
``sub-pos`` x see ``--sub-pos``
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ LUA SCRIPTING
|
||||
|
||||
mpv can load Lua scripts. These scripts can be used to control mpv in a similar
|
||||
way to slave mode. mpv provides the builtin module ``mp`` (can be loaded
|
||||
with ``require 'mpv'``), which provides functions to send commands to the
|
||||
with ``require 'mp'``), which provides functions to send commands to the
|
||||
mpv core and to retrieve information about playback state, user settings,
|
||||
file information, and so on.
|
||||
|
||||
|
||||
+29
-1
@@ -24,6 +24,7 @@ SYNOPSIS
|
||||
| **mpv** [options] {group of files and options}
|
||||
| **mpv** [bd]://[title][/device] [options]
|
||||
| **mpv** dvd://[title|[start\_title]-end\_title][/device] [options]
|
||||
| **mpv** dvdnav://[longest|menu|title][/device] [options]
|
||||
| **mpv** \vcd://[/device]
|
||||
| **mpv** \tv://[channel][/input_id] [options]
|
||||
| **mpv** radio://[channel|frequency][/capture] [options]
|
||||
@@ -32,6 +33,7 @@ SYNOPSIS
|
||||
| **mpv** \mf://[filemask|\@listfile] [-mf options] [options]
|
||||
| **mpv** cdda://track[-endtrack][:speed][/device] [options]
|
||||
| **mpv** [file|mms[t]|http|httpproxy|rt[s]p|ftp|udp|smb]://[user:pass\@]URL[:port] [options]
|
||||
| **mpv** edl://[edl specification as in edl-mpv.rst]
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
@@ -90,6 +92,10 @@ p / SPACE
|
||||
Step forward. Pressing once will pause movie, every consecutive press will
|
||||
play one frame and then go into pause mode again.
|
||||
|
||||
,
|
||||
Step backward. Pressing once will pause movie, every consecutive press will
|
||||
play one frame in reverse and then go into pause mode again.
|
||||
|
||||
q / ESC
|
||||
Stop playing and quit.
|
||||
|
||||
@@ -279,6 +285,28 @@ It has the following format::
|
||||
|
||||
``mpv --ao=pcm:file=%`expr length "$NAME"`%"$NAME" test.avi``
|
||||
|
||||
Paths
|
||||
-----
|
||||
|
||||
Some care must be taken when passing arbitrary paths and filenames to mpv. For
|
||||
example, paths starting with ``-`` will be interpreted as options. Likewise,
|
||||
if a path contains the sequence ``://``, the string before that might be
|
||||
interpreted as protocol prefix, even though ``://`` can be part of a legal
|
||||
UNIX path. To avoid problems with arbitrary paths, you should be sure that
|
||||
absolute paths passed to mpv start with ``/``, and relative paths with ``./``.
|
||||
|
||||
The name ``-`` itself is interpreted as stdin, and will cause mpv to disable
|
||||
console controls. (Which makes it suitable for playing data piped to stdin.)
|
||||
|
||||
For paths passed to suboptions, the situation is further complicated by the
|
||||
need to escape special characters. To work this around, the path can be
|
||||
additionally wrapped in the ``%n%string_of_length_n`` syntax (see above).
|
||||
|
||||
Some mpv options interpret paths starting with ``~``. Currently, the prefix
|
||||
``~~/`` expands to the mpv configuration directory (usually ``~/.mpv/``).
|
||||
``~/`` expands to the user's home directory. (The trailing ``/`` is always
|
||||
required.)
|
||||
|
||||
Per-File Options
|
||||
----------------
|
||||
|
||||
@@ -400,7 +428,7 @@ profile name ``default`` to continue with normal options.
|
||||
|
||||
[extension.flv]
|
||||
profile-desc="profile for .flv files"
|
||||
flip=yes
|
||||
vf=flip
|
||||
|
||||
[ao.alsa]
|
||||
device=spdif
|
||||
|
||||
+174
-51
@@ -42,6 +42,12 @@ OPTIONS
|
||||
requested number of output channels is set with the ``--channels`` option.
|
||||
Useful for playing surround audio on a stereo system.
|
||||
|
||||
``--ad-lavc-threads=<0-16>``
|
||||
Number of threads to use for decoding. Whether threading is actually
|
||||
supported depends on codec. As of this writing, it's supported for some
|
||||
lossless codecs only. 0 means autodetect number of cores on the
|
||||
machine and use that, up to the maximum of 16 (default: 1).
|
||||
|
||||
``--ad-lavc-o=<key>=<value>[,<key>=<value>[,...]]``
|
||||
Pass AVOptions to libavcodec decoder. Note, a patch to make the o=
|
||||
unneeded and pass all unknown options through the AVOption system is
|
||||
@@ -55,6 +61,8 @@ OPTIONS
|
||||
DTS-HD tracks can be sent over HDMI but not over the original
|
||||
coax/toslink S/PDIF system.
|
||||
|
||||
Note: this feature is broken.
|
||||
|
||||
``--dtshd`` and ``--no-dtshd`` are deprecated aliases.
|
||||
|
||||
``--af=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
@@ -95,6 +103,13 @@ OPTIONS
|
||||
Override movie aspect ratio, in case aspect information is incorrect or
|
||||
missing in the file being played. See also ``--no-aspect``.
|
||||
|
||||
Two values have special meaning:
|
||||
|
||||
:0: disable aspect ratio handling, pretend the video has square pixels
|
||||
:-1: use the video stream or container aspect (default)
|
||||
|
||||
But note that handling of these special values might change in the future.
|
||||
|
||||
.. admonition:: Examples
|
||||
|
||||
- ``--aspect=4:3`` or ``--aspect=1.3333``
|
||||
@@ -625,15 +640,11 @@ OPTIONS
|
||||
``--demuxer-lavf-format=<name>``
|
||||
Force a specific libavformat demuxer.
|
||||
|
||||
``--demuxer-lavf-genpts-mode=<auto|lavf|builtin|no>``
|
||||
``--demuxer-lavf-genpts-mode=<no|lavf>``
|
||||
Mode for deriving missing packet PTS values from packet DTS. ``lavf``
|
||||
enables libavformat's ``genpts`` option. ``builtin`` enables equivalent
|
||||
code in mpv. ``auto`` will enable either lavf (normal playback) or builtin
|
||||
(DVD playback) in correct-pts mode. The difference between them is that
|
||||
the builtin code will not potentially read until EOF trying to derive the
|
||||
PTS (which is very bad for DVD playback). On the other hand, builtin might
|
||||
give up too early, which is why lavf is preferred normally. ``no`` disables
|
||||
both.
|
||||
enables libavformat's ``genpts`` option. ``no`` disables it. This used
|
||||
to be enabled by default, but then it was deemed as not needed anymore.
|
||||
Enabling this might help with timestamp problems, or make them worse.
|
||||
|
||||
``--demuxer-lavf-o=<key>=<value>[,<key>=<value>[,...]]``
|
||||
Pass AVOptions to libavformat demuxer.
|
||||
@@ -796,9 +807,6 @@ OPTIONS
|
||||
``--no-fixed-vo`` enforces closing and reopening the video window for
|
||||
multiple files (one (un)initialization for each file).
|
||||
|
||||
``--flip``
|
||||
Flip image upside-down.
|
||||
|
||||
``--force-rgba-osd-rendering``
|
||||
Change how some video outputs render the OSD and text subtitles. This
|
||||
does not change appearance of the subtitles and only has performance
|
||||
@@ -1045,9 +1053,14 @@ OPTIONS
|
||||
|
||||
``mpv --heartbeat-cmd="gnome-screensaver-command -p" file``
|
||||
|
||||
|
||||
``--heartbeat-interval=<sec>``
|
||||
Time between ``--heartbeat-cmd`` invocations in seconds (default: 30).
|
||||
|
||||
.. note::
|
||||
|
||||
This does not affect the normal screensaver operation in any way.
|
||||
|
||||
``--help``
|
||||
Show short summary of options.
|
||||
|
||||
@@ -1112,11 +1125,10 @@ OPTIONS
|
||||
|
||||
:no: always use software decoding (default)
|
||||
:auto: see below
|
||||
:vdpau: requires ``--vo=vdpau`` (Linux only)
|
||||
:vaapi: requires ``--vo=vaapi`` (Linux with Intel GPUs only)
|
||||
:vdpau: requires ``--vo=vdpau`` or ``--vo=opengl`` (Linux only)
|
||||
:vaapi: requires ``--vo=opengl`` or ``--vo=vaapi`` (Linux with Intel GPUs only)
|
||||
:vaapi-copy: copies video back into system RAM (Linux with Intel GPUs only)
|
||||
:vda: requires ``--vo=corevideo`` (OSX only)
|
||||
:crystalhd: Broadcom Crystal HD
|
||||
:vda: requires ``--vo=opengl`` or ``--vo=corevideo`` (OSX only)
|
||||
|
||||
``auto`` tries to automatically enable hardware decoding using the first
|
||||
available method. This still depends what VO you are using. For example,
|
||||
@@ -1127,12 +1139,23 @@ OPTIONS
|
||||
The ``vaapi-copy`` function allows you to use vaapi with any VO. Because
|
||||
this copies the decoded video back to system RAM, it's quite inefficient.
|
||||
|
||||
``--hwdec-codecs=<codec1,codec2,...|all>``
|
||||
Allow hardware decoding for a given list of codecs only. The default is the
|
||||
special value ``all``, which always allows all codecs.
|
||||
.. note::
|
||||
|
||||
This is usually only needed with broken GPUs, where fallback on software
|
||||
decoding does not work properly.
|
||||
When using this switch, hardware decoding is still only done for some
|
||||
codecs. See ``--hwdec-codecs`` to enable hardware decoding for more
|
||||
codecs.
|
||||
|
||||
``--hwdec-codecs=<codec1,codec2,...|all>``
|
||||
Allow hardware decoding for a given list of codecs only. The special value
|
||||
``all`` always allows all codecs.
|
||||
|
||||
You can get the list of allowed codecs with ``mpv --vd=help``. Remove the
|
||||
prefix, e.g. instead of ``lavc:h264`` use ``h264``.
|
||||
|
||||
By default this is set to ``h264,vc1,wmv3``.
|
||||
|
||||
This is usually only needed with broken GPUs, where a codec is reported
|
||||
as supported, but decoding causes more problems than it solves.
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
@@ -1209,7 +1232,7 @@ OPTIONS
|
||||
like any other binding). See `INPUT.CONF`_.
|
||||
|
||||
``--joystick``, ``--no-joystick``
|
||||
Enable/disable joystick support. Enabled by default.
|
||||
Enable/disable joystick support. Disabled by default.
|
||||
|
||||
``--no-keepaspect``, ``--keepaspect``
|
||||
``--no-keepaspect`` will always stretch the video to window size, and will
|
||||
@@ -1286,12 +1309,21 @@ OPTIONS
|
||||
``--media-keys``, ``--no-media-keys``
|
||||
OSX only: Enabled by default. Enables/disable media keys support.
|
||||
|
||||
``--merge-files``
|
||||
Pretend that all files passed to mpv are concatenated into a single, big
|
||||
file. This uses timeline/EDL support internally. Note that this won't work
|
||||
for ordered chapter files or quvi-resolved URLs (such as youtube links).
|
||||
|
||||
This option is interpreted at program start, and doesn't affect for
|
||||
example files or playlists loaded with the ``loadfile`` or ``loadlist``
|
||||
commands.
|
||||
|
||||
``--mf=<option1:option2:...>``
|
||||
Used when decoding from multiple PNG or JPEG files with ``mf://``.
|
||||
|
||||
Available options are:
|
||||
|
||||
:fps=<value>: output fps (default: 25)
|
||||
:fps=<value>: output fps (default: 1)
|
||||
:type=<value>: input file type (available: jpeg, png, tga, sgi)
|
||||
|
||||
``--monitoraspect=<ratio>``
|
||||
@@ -1323,7 +1355,9 @@ OPTIONS
|
||||
Control verbosity directly for each module. The ``all`` module changes the
|
||||
verbosity of all the modules not explicitly specified on the command line.
|
||||
|
||||
See ``--msglevel=help`` for a list of all modules.
|
||||
Run mpv with ``--msglevel=all=trace`` to see all messages mpv outputs. You
|
||||
can use the module names printed in the output (prefixed to each line in
|
||||
``[...]``) to limit the output to interesting modules.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -1334,17 +1368,18 @@ OPTIONS
|
||||
|
||||
Available levels:
|
||||
|
||||
:-1: complete silence
|
||||
:0: fatal messages only
|
||||
:1: error messages
|
||||
:2: warning messages
|
||||
:3: short hints
|
||||
:4: informational messages
|
||||
:5: status messages (default)
|
||||
:6: verbose messages
|
||||
:7: debug level 2
|
||||
:8: debug level 3
|
||||
:9: debug level 4
|
||||
:no: complete silence
|
||||
:fatal: fatal messages only
|
||||
:error: error messages
|
||||
:warn: warning messages
|
||||
:info: informational messages
|
||||
:status: status messages (default)
|
||||
:v: verbose messages
|
||||
:debug: debug messages
|
||||
:trace: very noisy debug messages
|
||||
|
||||
One special case is the ``identify`` module name. This is silenced by
|
||||
default, and can be enabled with the ``-identify`` option.
|
||||
|
||||
``--msgmodule``
|
||||
Prepend module name in front of each console message.
|
||||
@@ -1414,8 +1449,20 @@ OPTIONS
|
||||
search for video segments from other files, and will also ignore any
|
||||
chapter order specified for the main file.
|
||||
|
||||
``--ordered-chapters-files=<playlist-file>``
|
||||
Loads the given file as playlist, and tries to use the files contained in
|
||||
it as reference files when opening a Matroska file that uses ordered
|
||||
chapters. This overrides the normal mechanism for loading referenced
|
||||
files by scanning the same directory the main file is located in.
|
||||
|
||||
Useful for loading ordered chapter files that are not located on the local
|
||||
filesystem, or if the referenced files are in different directories.
|
||||
|
||||
Note: a playlist can be as simple as a text file containing filenames
|
||||
separated by newlines.
|
||||
|
||||
``--osc``, ``--no-osc``
|
||||
Whether to load the on-screen-controller (default: no).
|
||||
Whether to load the on-screen-controller (default: yes).
|
||||
|
||||
``--no-osd-bar``, ``--osd-bar``
|
||||
Disable display of the OSD bar. This will make some things (like seeking)
|
||||
@@ -1536,6 +1583,12 @@ OPTIONS
|
||||
``--osd-scale=<factor>``
|
||||
OSD font size multiplicator, multiplied with ``--osd-font-size`` value.
|
||||
|
||||
``--osd-scale-by-window=yes|no``
|
||||
Whether to scale the OSD with the window size (default: yes). If this is
|
||||
disabled, ``--osd-font-size`` and other OSD options that use scaled pixels
|
||||
are always in actual pixels. The effect is that changing the window size
|
||||
won't change the OSD font size.
|
||||
|
||||
``--osd-shadow-color=<#RRGGBB>, --sub-text-shadow-color=<#RRGGBB>``
|
||||
See ``--osd-color``. Color used for OSD/sub text shadow.
|
||||
|
||||
@@ -1565,6 +1618,9 @@ OPTIONS
|
||||
controls how much of the image is cropped. May not work with all video
|
||||
output drivers.
|
||||
|
||||
``--pause``
|
||||
Start the player in paused state.
|
||||
|
||||
``--playing-msg=<string>``
|
||||
Print out a string after starting playback. The string is expanded for
|
||||
properties, e.g. ``--playing-msg='file: ${filename}'`` will print the string
|
||||
@@ -1605,12 +1661,6 @@ OPTIONS
|
||||
|
||||
FIXME: This needs to be clarified and documented thoroughly.
|
||||
|
||||
``--pp=<quality>``
|
||||
See also ``--vf=pp``.
|
||||
|
||||
``--pphelp``
|
||||
See also ``--vf=pp``.
|
||||
|
||||
``--priority=<prio>``
|
||||
(Windows only.)
|
||||
Set process priority for mpv according to the predefined priorities
|
||||
@@ -1625,16 +1675,19 @@ OPTIONS
|
||||
Use the given profile(s), ``--profile=help`` displays a list of the
|
||||
defined profiles.
|
||||
|
||||
``--pts-association-mode=<auto|decode|sort>``
|
||||
``--pts-association-mode=<decode|sort|auto>``
|
||||
Select the method used to determine which container packet timestamp
|
||||
corresponds to a particular output frame from the video decoder. Normally
|
||||
you should not need to change this option.
|
||||
|
||||
:auto: Try to pick a working mode from the ones below automatically
|
||||
(default)
|
||||
:decoder: Use decoder reordering functionality.
|
||||
:decoder: Use decoder reordering functionality. Unlike in classic MPlayer
|
||||
and mplayer2, this includes a dTS fallback. (Default.)
|
||||
:sort: Maintain a buffer of unused pts values and use the lowest value
|
||||
for the frame.
|
||||
:auto: Try to pick a working mode from the ones above automatically.
|
||||
|
||||
You can also try to use ``--no-correct-pts`` for files with completely
|
||||
broken timestamps.
|
||||
|
||||
``--pvr=<option1:option2:...>``
|
||||
This option tunes various encoding properties of the PVR capture module.
|
||||
@@ -1700,6 +1753,15 @@ OPTIONS
|
||||
|
||||
Also see ``--really-quiet`` and ``--msglevel``.
|
||||
|
||||
``--quvi-fetch-subtitles=<yes|no>``
|
||||
Toggles fetching of subtitles from streaming sites with libquvi. Disabled
|
||||
by default, because it's unreliable and slow. Note that when enabled,
|
||||
subtitles will always be fetched, even if subtitles are explicitly
|
||||
disabled with ``--no-sub`` (because you might want to enable subtitles
|
||||
at runtime).
|
||||
|
||||
Supported when using libquvi 0.9.x.
|
||||
|
||||
``--quvi-format=<best|default|...>``
|
||||
Video format/quality that is directly passed to libquvi (default: ``best``).
|
||||
This is used when opening links to streaming sites like YouTube. The
|
||||
@@ -1815,6 +1877,11 @@ OPTIONS
|
||||
- ``--reset-on-next-file=""``
|
||||
Do not reset pause mode.
|
||||
|
||||
``--right-alt-gr``, ``--no-right-alt-gr``
|
||||
(Cocoa and Windows only)
|
||||
Use the right Alt key as Alt Gr to produce special characters. If disabled,
|
||||
count the right Alt as an Alt modifier key. Enabled by default.
|
||||
|
||||
``--rtsp-transport=<lavf|udp|tcp|http>``
|
||||
Select RTSP transport method (default: tcp). This selects the underlying
|
||||
network transport when playing ``rtsp://...`` URLs. The value ``lavf``
|
||||
@@ -1971,6 +2038,30 @@ OPTIONS
|
||||
Specify the screen width for video output drivers which do not know the
|
||||
screen resolution, like ``x11`` and TV-out.
|
||||
|
||||
``--secondary-sid=<ID|auto|no>``
|
||||
Select a secondary subtitle stream. This is similar to ``--sid``. If a
|
||||
secondary subtitle is selected, it will be rendered as toptitle (i.e. on
|
||||
the top of the screen) alongside the normal subtitle, and provides a way
|
||||
to render two subtitles at once.
|
||||
|
||||
there are some caveats associated with this feature. For example, bitmap
|
||||
subtitles will always be rendered in their usual position, so selecting a
|
||||
bitmap subtitle as secondary subtitle will result in overlapping subtitles.
|
||||
Secondary subtitles are never shown on the terminal if video is disabled.
|
||||
|
||||
.. note::
|
||||
|
||||
Styling and interpretation of any formatting tags is disabled for the
|
||||
secondary subtitle. Internally, the same mechanism as ``--no-ass`` is
|
||||
used to strip the styling.
|
||||
|
||||
.. note::
|
||||
|
||||
If the main subtitle stream contains formatting tags which display the
|
||||
subtitle at the top of the screen, it will overlap with the secondary
|
||||
subtitle. To prevent this, you could use ``--no-ass`` to disable
|
||||
styling in the main subtitle stream.
|
||||
|
||||
``--show-profile=<profile>``
|
||||
Show the description and content of a profile.
|
||||
|
||||
@@ -2084,6 +2175,19 @@ OPTIONS
|
||||
``--start='#2' --end='#4'``
|
||||
Plays chapters 2 and 3, and exits.
|
||||
|
||||
``--stretch-dvd-subs=<yes|no>``
|
||||
Stretch DVD subtitles when playing anamorphic videos for better looking
|
||||
fonts on badly mastered DVDs. This switch has no effect when the
|
||||
video is stored with square pixels - which for DVD input cannot be the case
|
||||
though.
|
||||
|
||||
Many studios tend to use bitmap fonts designed for square pixels when
|
||||
authoring DVDs, causing the fonts to look stretched on playback on DVD
|
||||
players. This option fixes them, however at the price of possibly
|
||||
misaligning sume subtitles (e.g. sign translations).
|
||||
|
||||
Disabled by default.
|
||||
|
||||
``--ssf=<mode>``
|
||||
Specifies software scaler parameters.
|
||||
|
||||
@@ -2108,7 +2212,7 @@ OPTIONS
|
||||
``--stop-screensaver``, ``--no-stop-screensaver``
|
||||
Turns off the screensaver (or screen blanker and similar mechanisms) at
|
||||
startup and turns it on again on exit (default: yes). The screensaver is
|
||||
always reenabled when the player is paused.
|
||||
always re-enabled when the player is paused.
|
||||
|
||||
This is not supported on all video outputs or platforms. Sometimes it is
|
||||
implemented, but does not work (happens often on GNOME). You might be able
|
||||
@@ -2281,7 +2385,7 @@ OPTIONS
|
||||
Some ``--sws`` options are tunable. The description of the ``scale``
|
||||
video filter has further information.
|
||||
|
||||
``--term-osd, --no-term-osd``
|
||||
``--term-osd, --no-term-osd``, ``--term-osd=force``
|
||||
Display OSD messages on the console when no video output is available.
|
||||
Enabled by default.
|
||||
|
||||
@@ -2290,6 +2394,8 @@ OPTIONS
|
||||
console. The escape sequence should move the pointer to the beginning of
|
||||
the line used for the OSD and clear it (default: ``^[[A\r^[[K``).
|
||||
|
||||
``force`` enables terminal OSD even if a video window is created.
|
||||
|
||||
``--title=<string>``
|
||||
Set the window title. Properties are expanded on playback start.
|
||||
(See `Property Expansion`_.)
|
||||
@@ -2521,6 +2627,13 @@ OPTIONS
|
||||
|
||||
See ``--vd=help`` for a full list of available decoders.
|
||||
|
||||
``--vd-lavc-check-hw-profile=<yes|no>``
|
||||
Check hardware decoder profile (default: yes). If ``no`` is set, the
|
||||
highest profile of the hardware decoder is unconditionally selected, and
|
||||
decoding is forced even if the profile of the video is higher than that.
|
||||
The result is most likely broken decoding, but may also help if the
|
||||
detected or reported profiles are somehow incorrect.
|
||||
|
||||
``--vd-lavc-bitexact``
|
||||
Only use bit-exact algorithms in all decoding steps (for codec testing).
|
||||
|
||||
@@ -2543,6 +2656,11 @@ OPTIONS
|
||||
|
||||
``--vd--lavc-o=debug=pict``
|
||||
|
||||
``--vd-lavc-show-all=<yes|no>``
|
||||
Show even broken/corrupt frames (default: yes). If this option is set to
|
||||
no, libavcodec won't output frames that were either decoded before an
|
||||
initial keyframe was decoded, or frames that are recognized as corrupted.
|
||||
|
||||
``--vd-lavc-skiploopfilter=<skipvalue> (H.264 only)``
|
||||
Skips the loop filter (AKA deblocking) during H.264 decoding. Since
|
||||
the filtered frame is supposed to be used as reference for decoding
|
||||
@@ -2591,7 +2709,7 @@ OPTIONS
|
||||
to pad the video to screen if video and screen aspect ratios are different.
|
||||
``--video-align-y=-1`` would move the video to the top of the screen
|
||||
(leaving a border only on the bottom), a value of ``0`` centers it
|
||||
(default), and a value of ``-1`` would put the video at the bottom of the
|
||||
(default), and a value of ``1`` would put the video at the bottom of the
|
||||
screen.
|
||||
|
||||
If video and screen aspect match perfectly, these options do nothing.
|
||||
@@ -2617,6 +2735,10 @@ OPTIONS
|
||||
``--video-zoom`` option is set to a value other than ``1``, scaling is
|
||||
enabled, but the video isn't automatically scaled to the window size.)
|
||||
|
||||
The video and monitor aspects aspect will be ignored. Aspect correction
|
||||
would require to scale the video in the X or Y direction, but this option
|
||||
disables scaling, disabling all aspect correction.
|
||||
|
||||
Note that the scaler algorithm may still be used, even if the video isn't
|
||||
scaled. For example, this can influence chroma conversion.
|
||||
|
||||
@@ -2624,10 +2746,11 @@ OPTIONS
|
||||
|
||||
``--video-zoom=<value>``
|
||||
Adjust the video display scale factor by the given value. The unit is in
|
||||
fractions of original video size.
|
||||
fractions of the (scaled) window video size.
|
||||
|
||||
For example, given a 1280x720 video, ``--video-zoom=-0.1`` would make the
|
||||
video by 128 pixels smaller in X direction, and 72 pixels in Y direction.
|
||||
For example, given a 1280x720 video shown in a 1280x720 window,
|
||||
``--video-zoom=-0.1`` would make the video by 128 pixels smaller in
|
||||
X direction, and 72 pixels in Y direction.
|
||||
|
||||
This option is disabled if the ``--no-keepaspect`` option is used.
|
||||
|
||||
|
||||
+6
-2
@@ -6,7 +6,7 @@ offer basic mouse-controllability. It is intended to make interaction easier
|
||||
for new users and to enable precise and direct seeking.
|
||||
|
||||
The OSC is enabled by default if mpv was compiled with lua support. It can be
|
||||
disabled using ``--osc=no``.
|
||||
disabled entirely using the ``--osc=no`` option.
|
||||
|
||||
Using the OSC
|
||||
-------------
|
||||
@@ -113,7 +113,7 @@ time passed
|
||||
============= ================================================
|
||||
|
||||
cache status
|
||||
| Shows current cache fill status (only visible when below 48%)
|
||||
| Shows current cache fill status (only visible when below 45%)
|
||||
|
||||
time remaining
|
||||
| Shows remaining playback time timestamp
|
||||
@@ -176,6 +176,10 @@ Configurable parameters
|
||||
| Default: 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, negative value
|
||||
|
||||
+77
-89
@@ -7,10 +7,11 @@ syntax is:
|
||||
``--vf=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Setup a chain of video filters.
|
||||
|
||||
Many parameters are optional and set to default values if omitted. To
|
||||
explicitly use a default value, set a parameter to '-1'. Parameters ``w:h``
|
||||
means width x height in pixels, ``x:y`` means x;y position counted from the
|
||||
upper left corner of the bigger image.
|
||||
You can also set defaults for each filter. The defaults are applied before the
|
||||
normal filter parameters.
|
||||
|
||||
``--vf-defaults=<filter1[=parameter1:parameter2:...],filter2,...>``
|
||||
Set defaults for each filter.
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -74,7 +75,7 @@ Available filters are:
|
||||
|
||||
.. admonition:: Example
|
||||
|
||||
``expand=800:::::4/3``
|
||||
``expand=800::::4/3``
|
||||
Expands to 800x600, unless the source is higher resolution, in
|
||||
which case it expands to fill a 4/3 aspect.
|
||||
|
||||
@@ -82,7 +83,7 @@ Available filters are:
|
||||
Rounds up to make both width and height divisible by <r> (default: 1).
|
||||
|
||||
``flip``
|
||||
Flips the image upside down. See also ``--flip``.
|
||||
Flips the image upside down.
|
||||
|
||||
``mirror``
|
||||
Mirrors the image on the Y axis.
|
||||
@@ -150,7 +151,7 @@ Available filters are:
|
||||
:0: Disable accurate rounding (default).
|
||||
:1: Enable accurate rounding.
|
||||
|
||||
``dsize[=aspect|w:h:aspect-method:r]``
|
||||
``dsize[=w:h:aspect-method:r:aspect]``
|
||||
Changes the intended display size/aspect at an arbitrary point in the
|
||||
filter chain. Aspect can be given as a fraction (4/3) or floating point
|
||||
number (1.33). Alternatively, you may specify the exact display width and
|
||||
@@ -199,6 +200,9 @@ Available filters are:
|
||||
Rounds up to make both width and height divisible by ``<r>``
|
||||
(default: 1).
|
||||
|
||||
``<aspect>``
|
||||
Force an aspect ratio.
|
||||
|
||||
``format[=fmt[:outfmt]]``
|
||||
Restricts the color space for the next filter without doing any conversion.
|
||||
Use together with the scale filter for a real conversion.
|
||||
@@ -232,7 +236,7 @@ Available filters are:
|
||||
``<fmt>``
|
||||
Format name, e.g. rgb15, bgr24, 420p, etc. (default: 420p).
|
||||
|
||||
``pp[=filter1[:option1[:option2...]]/[-]filter2...]``
|
||||
``pp[=[filter1[:option1[:option2...]]/[-]filter2...]]``
|
||||
Enables the specified chain of postprocessing subfilters. Subfilters must
|
||||
be separated by '/' and can be disabled by prepending a '-'. Each
|
||||
subfilter and some options have a short and a long name that can be used
|
||||
@@ -250,7 +254,12 @@ Available filters are:
|
||||
|
||||
.. note::
|
||||
|
||||
``--pphelp`` shows a list of available subfilters.
|
||||
``--vf=pp:help`` shows a list of available subfilters.
|
||||
|
||||
.. note::
|
||||
|
||||
Unlike in MPlayer or in earlier versions, you must quote the pp string
|
||||
if it contains ``:`` characters, e.g. ``'--vf=pp=[...]'``.
|
||||
|
||||
Available subfilters are:
|
||||
|
||||
@@ -364,14 +373,14 @@ Available filters are:
|
||||
``--vf=pp=de/-al``
|
||||
default filters without brightness/contrast correction
|
||||
|
||||
``--vf=pp=default/tmpnoise:1:2:3``
|
||||
``--vf=pp=[default/tmpnoise:1:2:3]``
|
||||
Enable default filters & temporal denoiser.
|
||||
|
||||
``--vf=pp=hb:y/vb:a``
|
||||
``--vf=pp=[hb:y/vb:a]``
|
||||
Horizontal deblocking on luminance only, and switch vertical
|
||||
deblocking on or off automatically depending on available CPU time.
|
||||
|
||||
``lavfi=graph[:sws_flags[:o=opts]]``
|
||||
``lavfi=graph[:sws-flags[:o=opts]]``
|
||||
Filter video using FFmpeg's libavfilter.
|
||||
|
||||
``<graph>``
|
||||
@@ -405,7 +414,7 @@ Available filters are:
|
||||
``'--vf=lavfi=graph="gradfun=radius=30:strength=20,vflip"'``
|
||||
Same as before, but uses named parameters for everything.
|
||||
|
||||
``<sws_flags>``
|
||||
``<sws-flags>``
|
||||
If libavfilter inserts filters for pixel format conversion, this
|
||||
option gives the flags which should be passed to libswscale. This
|
||||
option is numeric and takes a bit-wise combination of ``SWS_`` flags.
|
||||
@@ -420,21 +429,30 @@ Available filters are:
|
||||
``'--vf=lavfi=yadif:o="threads=2,thread_type=slice"'``
|
||||
forces a specific threading configuration.
|
||||
|
||||
``noise[=luma[u][t|a][h][p]:chroma[u][t|a][h][p]]``
|
||||
``noise[=<strength>[:averaged][:pattern][:temporal][:uniform][:hq]``
|
||||
Adds noise.
|
||||
|
||||
:<0-100>: luma noise
|
||||
:<0-100>: chroma noise
|
||||
:u: uniform noise (gaussian otherwise)
|
||||
:t: temporal noise (noise pattern changes between frames)
|
||||
:a: averaged temporal noise (smoother, but a lot slower)
|
||||
:h: high quality (slightly better looking, slightly slower)
|
||||
:p: mix random noise with a (semi)regular pattern
|
||||
``strength``
|
||||
Set the noise for all components. If you want different strength
|
||||
values for luma and chroma, use libavfilter's noise filter directly
|
||||
(using ``--vf=lavfi=[noise=...]``), or tell the libavfilter developers
|
||||
to stop being stupid.
|
||||
|
||||
.. note::
|
||||
``averaged``
|
||||
averaged temporal noise (smoother, but a lot slower)
|
||||
|
||||
Deprecated. Use libavfilter's ``noise`` filter through ``--vf=lavfi``
|
||||
instead.
|
||||
``pattern``
|
||||
mix random noise with a (semi)regular pattern
|
||||
|
||||
``temporal``
|
||||
temporal noise (noise pattern changes between frames)
|
||||
|
||||
``uniform``
|
||||
uniform noise (gaussian otherwise)
|
||||
|
||||
``hq``
|
||||
high quality (slightly better looking, slightly slower) - not available
|
||||
when using libavfilter
|
||||
|
||||
``hqdn3d[=luma_spatial:chroma_spatial:luma_tmp:chroma_tmp]``
|
||||
This filter aims to reduce image noise producing smooth images and making
|
||||
@@ -450,11 +468,6 @@ Available filters are:
|
||||
chroma temporal strength (default:
|
||||
``luma_tmp*chroma_spatial/luma_spatial``)
|
||||
|
||||
.. note::
|
||||
|
||||
Deprecated. Use libavfilter's ``hqdn3d`` filter through ``--vf=lavfi``
|
||||
instead.
|
||||
|
||||
``eq[=gamma:contrast:brightness:saturation:rg:gg:bg:weight]``
|
||||
Software equalizer that uses lookup tables (slow), allowing gamma correction
|
||||
in addition to simple brightness and contrast adjustment. The parameters are
|
||||
@@ -494,39 +507,26 @@ Available filters are:
|
||||
:0: nearest-neighbor sampling, fast but incorrect
|
||||
:1: linear interpolation (default)
|
||||
|
||||
``unsharp[=l|cWxH:amount[:l|cWxH:amount]]``
|
||||
``unsharp[=lx:ly:la:cx:cy:ca]``
|
||||
unsharp mask / gaussian blur
|
||||
|
||||
``l``
|
||||
Apply effect on luma component.
|
||||
``l`` is for the luma component, ``c`` for the chroma component. ``x``/``y``
|
||||
is the filter size. ``a`` is the amount.
|
||||
|
||||
``c``
|
||||
Apply effect on chroma components.
|
||||
|
||||
``<width>x<height>``
|
||||
``lx``, ``ly``, ``cx``, ``cy``
|
||||
width and height of the matrix, odd sized in both directions (min =
|
||||
3x3, max = 13x11 or 11x13, usually something between 3x3 and 7x7)
|
||||
3:3, max = 13:11 or 11:13, usually something between 3:3 and 7:7)
|
||||
|
||||
``amount``
|
||||
``la``, ``ca``
|
||||
Relative amount of sharpness/blur to add to the image (a sane range
|
||||
should be -1.5-1.5).
|
||||
|
||||
:<0: blur
|
||||
:>0: sharpen
|
||||
|
||||
.. note::
|
||||
|
||||
Deprecated. Use libavfilter's ``unsharp`` filter through ``--vf=lavfi``
|
||||
instead.
|
||||
|
||||
``swapuv``
|
||||
Swap U & V plane.
|
||||
|
||||
.. note::
|
||||
|
||||
Deprecated. Use libavfilter's ``swapuv`` filter through ``--vf=lavfi``
|
||||
instead.
|
||||
|
||||
``pullup[=jl:jr:jt:jb:sb:mp]``
|
||||
Pulldown reversal (inverse telecine) filter, capable of handling mixed
|
||||
hard-telecine, 24000/1001 fps progressive, and 30000/1001 fps progressive
|
||||
@@ -551,7 +551,7 @@ Available filters are:
|
||||
frames in the output.
|
||||
|
||||
``mp`` (metric plane)
|
||||
This option may be set to 1 or 2 to use a chroma plane instead of the
|
||||
This option may be set to ``u`` or ``v`` to use a chroma plane instead of the
|
||||
luma plane for doing ``pullup``'s computations. This may improve accuracy
|
||||
on very clean source material, but more likely will decrease accuracy,
|
||||
especially if there is chroma noise (rainbow effect) or any grayscale
|
||||
@@ -670,41 +670,36 @@ Available filters are:
|
||||
``v``
|
||||
Verbose operation. Prints the selected mode for each frame and the
|
||||
average squared difference between fields for ``t``, ``b``, and ``p``
|
||||
alternatives.
|
||||
alternatives. (Ignored when libavfilter is used.)
|
||||
|
||||
``yadif=[mode[:enabled=yes|no]]``
|
||||
Yet another deinterlacing filter
|
||||
|
||||
``<mode>``
|
||||
:0: Output 1 frame for each frame.
|
||||
:1: Output 1 frame for each field.
|
||||
:2: Like 0 but skips spatial interlacing check.
|
||||
:3: Like 1 but skips spatial interlacing check.
|
||||
:frame: Output 1 frame for each frame.
|
||||
:field: Output 1 frame for each field.
|
||||
:frame-nospatial: Like ``frame`` but skips spatial interlacing check.
|
||||
:field-nospatial: Like ``field`` but skips spatial interlacing check.
|
||||
|
||||
``<enabled>``
|
||||
:yes: Filter is active (default).
|
||||
:no: Filter is not active, but can be activated with the ``D`` key
|
||||
(or any other key that toggles the ``deinterlace`` property).
|
||||
|
||||
.. note::
|
||||
|
||||
Deprecated. Use libavfilter's ``yadif`` filter through ``--vf=lavfi``
|
||||
instead.
|
||||
|
||||
This filter, or libavfilter's implementation if available, is automatically
|
||||
inserted when using the ``D`` key (or any other key that toggles the
|
||||
``deinterlace`` property), assuming the video output does not have native
|
||||
This filter, is automatically inserted when using the ``D`` key (or any
|
||||
other key that toggles the ``deinterlace`` property or when using the
|
||||
``--deinterlace`` switch), assuming the video output does not have native
|
||||
deinterlacing support.
|
||||
|
||||
``down3dright[=lines]``
|
||||
Reposition and resize stereoscopic images. Extracts both stereo fields and
|
||||
places them side by side, resizing them to maintain the original movie
|
||||
aspect.
|
||||
If you just want to set the default mode, put this filter and its options
|
||||
into ``--vf-defaults`` instead, and enable deinterlacing with ``D`` or
|
||||
``--deinterlace``.
|
||||
|
||||
``<lines>``
|
||||
number of lines to select from the middle of the image (default: 12)
|
||||
Also note that the ``D`` key is stupid enough to insert an interlacer twice
|
||||
when inserting yadif with ``--vf``, so using the above methods is
|
||||
recommended.
|
||||
|
||||
``delogo[=x:y:w:h:t]``
|
||||
``delogo[=x:y:w:h:t:show]``
|
||||
Suppresses a TV station logo by a simple interpolation of the surrounding
|
||||
pixels. Just set a rectangle covering the logo and watch it disappear (and
|
||||
sometimes something even uglier appear - your mileage may vary).
|
||||
@@ -721,11 +716,9 @@ Available filters are:
|
||||
You can specify a text file to load the coordinates from. Each line
|
||||
must have a timestamp (in seconds, and in ascending order) and the
|
||||
``x:y:w:h:t`` coordinates (``t`` can be omitted).
|
||||
|
||||
.. note::
|
||||
|
||||
Deprecated. Use libavfilter's ``delogo`` or ``removelogo`` filters
|
||||
through ``--vf=lavfi`` instead.
|
||||
(Not supported when using libavfilter.)
|
||||
``show``
|
||||
Draw a rectangle showing the area defined by x/y/w/h.
|
||||
|
||||
``screenshot``
|
||||
Optional filter for screenshot support. This is only needed if the video
|
||||
@@ -818,29 +811,24 @@ Available filters are:
|
||||
``mr`` or ``mono_right``
|
||||
mono output (right eye only)
|
||||
|
||||
.. note::
|
||||
|
||||
Deprecated. Use libavfilter's ``stereo3d`` filter through ``--vf=lavfi``
|
||||
instead.
|
||||
|
||||
``gradfun[=strength[:radius]]``
|
||||
``gradfun[=strength[:radius|:size=<size>]]``
|
||||
Fix the banding artifacts that are sometimes introduced into nearly flat
|
||||
regions by truncation to 8bit color depth. Interpolates the gradients that
|
||||
should go where the bands are, and dithers them.
|
||||
|
||||
``<strength>``
|
||||
Maximum amount by which the filter will change any one pixel. Also the
|
||||
threshold for detecting nearly flat regions (default: 1.2).
|
||||
threshold for detecting nearly flat regions (default: 1.5).
|
||||
|
||||
``<radius>``
|
||||
Neighborhood to fit the gradient to. Larger radius makes for smoother
|
||||
gradients, but also prevents the filter from modifying pixels near
|
||||
detailed regions (default: 16).
|
||||
detailed regions (default: disabled).
|
||||
|
||||
.. note::
|
||||
``<size>``
|
||||
size of the filter in percent of the image diagonal size. This is
|
||||
used to calculate the final radius size (default: 1).
|
||||
|
||||
Deprecated. Use libavfilter's ``gradfun`` filter through ``--vf=lavfi``
|
||||
instead.
|
||||
|
||||
``dlopen=dll[:a0[:a1[:a2[:a3]]]]``
|
||||
Loads an external library to filter the image. The library interface
|
||||
@@ -863,10 +851,10 @@ Available filters are:
|
||||
Specify the fourth parameter to pass to the library.
|
||||
|
||||
``vavpp``
|
||||
VA-API video post processing. Works with ``--vo=vaapi`` only. Currently
|
||||
deinterlaces. This filter is automatically inserted if deinterlacing is
|
||||
requested (either using the ``D`` key, by default mapped to the command
|
||||
``cycle deinterlace``, or the ``--deinterlace`` option).
|
||||
VA-AP-API video post processing. Works with ``--vo=vaapi`` and ``--vo=opengl``
|
||||
only. Currently deinterlaces. This filter is automatically inserted if
|
||||
deinterlacing is requested (either using the ``D`` key, by default mapped to
|
||||
the command ``cycle deinterlace``, or the ``--deinterlace`` option).
|
||||
|
||||
``deint=<method>``
|
||||
Select the deinterlacing algorithm.
|
||||
|
||||
+24
-6
@@ -10,6 +10,12 @@ syntax is:
|
||||
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.
|
||||
|
||||
``--vo-defaults=<driver1[:parameter1:parameter2:...],driver2,...>``
|
||||
Set defaults for each driver.
|
||||
|
||||
.. note::
|
||||
|
||||
See ``--vo=help`` for a list of compiled-in video output drivers.
|
||||
@@ -241,6 +247,11 @@ Available video output drivers are:
|
||||
Some features are available with OpenGL 3 capable graphics drivers only
|
||||
(or if the necessary extensions are available).
|
||||
|
||||
Hardware decoding over OpenGL-interop is supported to some degree. Note
|
||||
that in this mode, some corner case might not be gracefully handled, and
|
||||
colorspace conversion and chroma upsampling is generally in the hand of
|
||||
the hardware decoder APIs.
|
||||
|
||||
``lscale=<filter>``
|
||||
|
||||
``bilinear``
|
||||
@@ -320,8 +331,10 @@ Available video output drivers are:
|
||||
slightly different gamma.
|
||||
|
||||
``pbo``
|
||||
Enable use of PBOs. This is faster, but can sometimes lead to sporadic
|
||||
and temporary image corruption.
|
||||
Enable use of PBOs. This is slightly faster, but can sometimes lead to
|
||||
sporadic and temporary image corruption (in theory, because reupload
|
||||
is not retried when it fails), and perhaps actually triggers slower
|
||||
paths with drivers that don't support PBOs properly.
|
||||
|
||||
``dither-depth=<N|no|auto>``
|
||||
Set dither target depth to N. Default: no.
|
||||
@@ -422,8 +435,8 @@ Available video output drivers are:
|
||||
Selects the internal format of textures used for FBOs. The format can
|
||||
influence performance and quality of the video output. (FBOs are not
|
||||
always used, and typically only when using extended scalers.)
|
||||
``fmt`` can be one of: rgb, rgba, rgb8, rgb10, rgb16, rgb16f, rgb32f,
|
||||
rgba12, rgba16, rgba16f, rgba32f.
|
||||
``fmt`` can be one of: rgb, rgba, rgb8, rgb10, rgb10_a2, rgb16, rgb16f,
|
||||
rgb32f, rgba12, rgba16, rgba16f, rgba32f.
|
||||
Default: rgb.
|
||||
|
||||
``gamma``
|
||||
@@ -473,12 +486,17 @@ Available video output drivers are:
|
||||
Set the YUV chroma sample location. auto means use the bitstream
|
||||
flags (default: auto).
|
||||
|
||||
``rectangle-textures``
|
||||
Force use of rectangle textures (default: no). Normally this shouldn't
|
||||
have any advantages over normal textures. Note that hardware decoding
|
||||
overrides this flag.
|
||||
|
||||
``opengl-hq``
|
||||
Same as ``opengl``, but with default settings for high quality rendering.
|
||||
|
||||
This is equivalent to::
|
||||
|
||||
--vo=opengl:lscale=lanczos2:dither-depth=auto:pbo:fbo-format=rgb16
|
||||
--vo=opengl:lscale=lanczos2:dither-depth=auto:fbo-format=rgb16
|
||||
|
||||
Note that some cheaper LCDs do dithering that gravely interferes with
|
||||
``opengl``'s dithering. Disabling dithering with ``dither-depth=no`` helps.
|
||||
@@ -776,4 +794,4 @@ Available video output drivers are:
|
||||
|
||||
``alpha``
|
||||
Use a buffer format that supports videos and images with alpha
|
||||
informations
|
||||
information
|
||||
+36
-25
@@ -2,11 +2,12 @@ NOTE: DOCS/OUTDATED-tech/* may contain more detailed information, but most of it
|
||||
is possibly or definitely outdated. This file intends to give a big
|
||||
picture of how mpv is structured.
|
||||
|
||||
mpvcore/mplayer.c:
|
||||
This contains the main play loop, anything related to mpv and playback
|
||||
related initializations. It also contains the main function. Generally, it
|
||||
accesses all other subsystems, initializes them, and pushes data between
|
||||
them during playback.
|
||||
player/*.c:
|
||||
Essentially makes up the player applications, including the main() function
|
||||
and the playback loop.
|
||||
|
||||
Generally, it accesses all other subsystems, initializes them, and pushes
|
||||
data between them during playback.
|
||||
|
||||
The structure is as follows (as of commit e13c05366557cb):
|
||||
* main():
|
||||
@@ -42,7 +43,7 @@ mpvcore/mplayer.c:
|
||||
- the other subsystems rarely call back into the frontend, and the frontend
|
||||
polls them instead (probably a good thing)
|
||||
|
||||
I like to call mplayer.c (and some other files) the "frontend".
|
||||
I like to call the player/*.c files the "frontend".
|
||||
|
||||
talloc.h & talloc.c:
|
||||
Hierarchical memory manager copied from Samba. It's like a malloc() with
|
||||
@@ -82,37 +83,47 @@ talloc.h & talloc.c:
|
||||
rid of the talloc emulation later and use TA natively.
|
||||
(See ta/README for details.)
|
||||
|
||||
mpvcore/mp_core.h:
|
||||
Data structures for mplayer.c and command.c. They are usually not accessed
|
||||
by other parts of mpv for the sake of modularization.
|
||||
player/command.c:
|
||||
This contains the implementation for slave commands and properties.
|
||||
Properties are essentially dynamic variables changed by certain commands.
|
||||
This is basically responsible for all user commands, like initiating
|
||||
seeking, switching tracks, etc. It calls into other player/*.c files,
|
||||
where most of the work is done, but also calls other parts of mpv.
|
||||
|
||||
player/core.h:
|
||||
Data structures and function prototypes for most of player/*.c. They are
|
||||
usually not accessed by other parts of mpv for the sake of modularization.
|
||||
|
||||
Note that there are lots of global variables floating around everywhere
|
||||
else. This is an ongoing transition, and eventually there should be no
|
||||
global variables anymore.
|
||||
|
||||
options.h contains the global option struct MPOpts, and its default values
|
||||
are in defaultopts.c for some reason.
|
||||
options/options.h, options/options.c
|
||||
options.h contains the global option struct MPOpts. The option declarations
|
||||
(option names, types, and MPOpts offsets for the option parser) are in
|
||||
options.c. Most default values for options and MPOpts are in
|
||||
mp_default_opts at the end of options.c.
|
||||
|
||||
mpvcore/input/input.c:
|
||||
MPOpts is unfortunarely quite monolithic, and virtually accessed by
|
||||
everything.But some components (like video outputs and video filters) have
|
||||
their own sub-option tables separate from MPOpts.
|
||||
|
||||
The actual option parser is spread over m_option.c, m_config.c, and
|
||||
parser-mpcmd.c, and uses the option table in options.c.
|
||||
|
||||
input/input.c:
|
||||
This translates keyboard input comming from libvo and other sources (such
|
||||
as remote control devices like Apple IR or slave mode commands) to the
|
||||
key bindings listed in the user's (or the builtin) input.conf and turns
|
||||
them into items of type struct mp_cmd. These commands are queued, and read
|
||||
by mplayer.c. They get pushed with run_command() to command.c.
|
||||
by playloop.c. They get pushed with run_command() to command.c.
|
||||
|
||||
Note that keyboard input and slave mode input are essentially the same
|
||||
things. Just looking at input.conf should make this clear. (The other
|
||||
direction of slave mode communication, mpv to application, consists of
|
||||
random mp_msg() calls all over the code in all parts of the player.)
|
||||
|
||||
mpvcore/command.c:
|
||||
This contains the implementation for slave commands and properties.
|
||||
Properties are essentially dynamic variables changed by certain commands.
|
||||
This is basically responsible for all user commands, like initiating
|
||||
seeking, switching tracks, etc. It calls into mplayer.c, where most of the
|
||||
work is done, but also into other parts of mpv.
|
||||
|
||||
mpvcore/mp_msg.h:
|
||||
common/msg.h:
|
||||
All terminal output should go through mp_msg().
|
||||
|
||||
stream/*:
|
||||
@@ -188,8 +199,8 @@ audio/decode/:
|
||||
|
||||
audio/filter/:
|
||||
Audio filter chain. af_lavrresample is inserted if any form of conversion
|
||||
between audio formats is needed. (af_format.c is also used for some corner
|
||||
cases.)
|
||||
between audio formats is needed. (af_convert24.c and af_convertsignendian.c
|
||||
are also used for some formats not directly supported by FFmpeg.)
|
||||
|
||||
audio/out/:
|
||||
Audio outputs.
|
||||
@@ -229,8 +240,8 @@ sub/:
|
||||
detection as well as timing postprocessing work. (Timing postprocessing
|
||||
removes tiny gaps or overlaps between subtitle events.)
|
||||
|
||||
mpvcore/timeline/:
|
||||
A timeline is the abstraction used by mplayer.c to combine several files
|
||||
player/timeline/:
|
||||
A timeline is the abstraction used by loadfile.c to combine several files
|
||||
into one seemingly linear video. It's mainly used for ordered chapters
|
||||
playback. The high level code to find and load other files containing the
|
||||
segments for playing an ordered chapters file is in tl_matroska.c.
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
waf build system overview
|
||||
=========================
|
||||
|
||||
mpv's new build system is based on waf and it should completly replace the
|
||||
custom ./configure + Makefile based system inherited from MPlayer.
|
||||
|
||||
Goals and the choice of waf
|
||||
===========================
|
||||
|
||||
The new system comes with some goals, which can be summed up as: be as good as
|
||||
the old one at what it did well (customizability) and fix some of it's major
|
||||
shortcomings:
|
||||
|
||||
1) The build system must be uniform in how it handles any single feature check.
|
||||
Repetition and boilerplate have to be avoided.
|
||||
|
||||
When adding a new feature using the old configure, one had to add a fair
|
||||
amount of code to the shell script to do option parsing, detection of the
|
||||
feature and declaration of variables for the Makefile to pickup. The worst
|
||||
part is this pieces are spread apart in the configure and copy pasted for
|
||||
any single case. That brings us to..
|
||||
|
||||
2) --enable-feature has to override the user and help him understand that he
|
||||
has libraries missing and should install them for the feature to be enabled.
|
||||
|
||||
3) Must be customizable, hackable, pleasant to the developer eyes and to work
|
||||
with in general.
|
||||
|
||||
4) Must have separate configuration and build steps.
|
||||
|
||||
Goal 2 comes as a given on pretty much any build system, since autotools made
|
||||
this behaviour very popular among users (and rightly so).
|
||||
|
||||
Goal 1+3 were somewhat harder to accomplish as it looks like all of the build
|
||||
systems we evaluated (waf included!) had problems with them. For reference we
|
||||
had proof of concept build systems with waf, CMake and autotools.
|
||||
|
||||
What puts waf apart from CMake and autotools, is that projects using it use
|
||||
Python to program their build system. Also while the Waf Book shows really
|
||||
simple API usages, you can write your own build system on top of waf that is
|
||||
tailored to the project's specific needs.
|
||||
|
||||
mpv's custom configure step on top of waf
|
||||
=========================================
|
||||
|
||||
To some extents mpv has a custom build system written on top of waf. This
|
||||
document will not go over the standard waf behaviour as that is documented in
|
||||
the ``Waf book``.
|
||||
|
||||
All of the configuration process is handled with a declarative approach. Lists
|
||||
of dictionaries define the checks, and some custom Python code traverses these
|
||||
lists and depending on the check definition it calls into the actual waf API.
|
||||
|
||||
A simple example using pkg-config would be::
|
||||
|
||||
{
|
||||
'name': '--vdpau',
|
||||
'desc': 'VDPAU acceleration',
|
||||
'deps': [ 'x11' ],
|
||||
'func': check_pkg_config('vdpau', '>= 0.2'),
|
||||
}
|
||||
|
||||
This defines a feature called ``vdpau`` which can be enabled or disabled by
|
||||
the users with configure flags (that's the meaning of ``--``). This feature
|
||||
depends on another feature whose name is ``x11``, and the autodetection check
|
||||
consists of running ``pkg-config`` and looking for ``vdpau`` with version
|
||||
``>= 0.2``. If the check succeds a ``#define HAVE_VDPAU 1`` will be added to
|
||||
``config.h``, if not ``#define HAVE_VDPAU 0`` will be added.
|
||||
|
||||
The defines names are automatically prepended with ``HAVE_``, capitalized and
|
||||
special characters are replaced with underscores. This happens in
|
||||
``waftools/inflectors.py``.
|
||||
|
||||
Mandatory fields:
|
||||
-----------------
|
||||
|
||||
``name``: indicates the unique identifier used by the custom dependency code
|
||||
to refer to a feature. If the unique identifier is prepended with ``--``
|
||||
the build system will also generate options for ``./waf configure`` so that
|
||||
the feature can be enabled and disabled.
|
||||
|
||||
``desc``: this is the textual representation of the feature used in the
|
||||
interactions with the users.
|
||||
|
||||
``func``: function that will perform the check. These functions are defined in
|
||||
``waftools/checks``. The reusable checks are all functions that return
|
||||
functions. The return functions will then be applied using waf's configuration
|
||||
context.
|
||||
|
||||
The source code for the reusable checks is a bit convoluted, but it should be
|
||||
easy to pick up their usage from the ``wscript``. Their signature mirrors
|
||||
the semantics of some of the shell functions used in mplayer.
|
||||
|
||||
If someone expresses some interest, I will extend this document with official
|
||||
documentation for each check function.
|
||||
|
||||
Optional fields
|
||||
---------------
|
||||
|
||||
``deps``: list of dependencies of this feature. It is a list of names of
|
||||
other features as defined in the ``name`` field (minus the eventual leading
|
||||
``--``). All of the dependencies must be satisfied. If they are not the check
|
||||
will be skipped without even running ``func``.
|
||||
|
||||
``deps_any``: like deps but it is satisfied even if only one of the dependencies
|
||||
is satisfied. You can think of ``deps`` as a 'and' condition and ``deps_any``
|
||||
as a 'or' condition.
|
||||
|
||||
``deps_neg``: like deps but it is satisfied when none of the dependencies is
|
||||
satisfied.
|
||||
|
||||
``req``: defaults to False. If set to True makes this feature a hard
|
||||
dependency of mpv (configuration will fail if autodetection fails). If set to
|
||||
True you must also provide ``fmsg``.
|
||||
|
||||
``fmsg``: string with the failure message in case a required dependency is not
|
||||
satisfied.
|
||||
|
||||
``os_specific_checks``: this takes a dictionary that has ``os-`` dependencies
|
||||
as keys (such as ``os-win32``), and by values has another dictionary that is
|
||||
merged on top of the current feature definition only for that specific OS.
|
||||
For example::
|
||||
|
||||
{
|
||||
'name': '--pthreads',
|
||||
'desc': 'POSIX threads',
|
||||
'func': check_pthreads,
|
||||
'os_specific_checks': {
|
||||
'os-win32': {
|
||||
'func': check_pthreads_w32_static.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
will override the value of ``func`` with ``check_pthreads_w32_static`` only
|
||||
if the target OS of the build is Windows.
|
||||
|
||||
``groups``: groups a dependency with another one. This can be used to disabled
|
||||
all the grouped dependencies with one ``--disable-``. At the moment this is
|
||||
only used for OpenGL backends, where you want to disable them when
|
||||
``--disable-gl`` is passed to the configure.
|
||||
|
||||
mpv's custom build step on top of waf
|
||||
=====================================
|
||||
|
||||
Build step is pretty much vanilla waf. The only difference being that the list
|
||||
of source files can contain both strings or tuples. If a tuple is found,
|
||||
the second element in the tuple will the used to match the features detected
|
||||
in the configure step (the ``name`` field described above). If this feature
|
||||
was not enabled during configure, the source file will not be compiled in.
|
||||
|
||||
All of the custom Python for this is inside the function ``filtered_sources``
|
||||
contained in the file ``waftools/dependencies.py``.
|
||||
|
||||
Also ``dependencies_use`` and ``dependencies_includes`` collect cflags and
|
||||
ldflags that were generated from the features checks in the configure step.
|
||||
@@ -16,12 +16,29 @@ Compilation
|
||||
-----------
|
||||
|
||||
Compiling with full features requires development files for several
|
||||
external libraries. Below is a list of some important requirements. For
|
||||
more information see the output of `./configure --help` for a list of options,
|
||||
or look at the list of enabled and disabled features printed after running
|
||||
`./configure`. If you think you have support for some feature installed
|
||||
but configure fails to detect it, the file `config.log` may contain
|
||||
information about the reasons for the failure.
|
||||
external libraries. Below is a list of some important requirements.
|
||||
|
||||
The mpv build system uses *waf* but we don't store it in your source tree. The
|
||||
script './bootstrap.py' will download the latest version of waf that was tested
|
||||
with the build system.
|
||||
|
||||
For a list of the available build options use `./waf configure --help`. If
|
||||
you think you have support for some feature installed but configure fails to
|
||||
detect it, the file `build/config.log` may contain information about the
|
||||
reasons for the failure.
|
||||
|
||||
NOTE: To avoid cluttering the output with unreadable spam, `--help` only shows
|
||||
one of the two switches for each option. If the option is autodetected by
|
||||
default, the `--disable-***` switch is printed; if the option is disabled by
|
||||
default, the `--enable-***` switch is printed. Either way, you can use
|
||||
`--enable-***` or `--disable-**` regardless of what is printed by `--help`.
|
||||
|
||||
To build the software you can use `./waf build`: the result of the compilation
|
||||
will be located in `build/mpv`. You can use `./waf install` to install mpv
|
||||
to the *prefix* after it is compiled.
|
||||
|
||||
NOTE: Using the old build system (with `./old-configure`) should still work,
|
||||
but will be removed in a future version of mpv.
|
||||
|
||||
Essential dependencies (incomplete list):
|
||||
|
||||
@@ -47,21 +64,12 @@ If you are running Mac OSX and using homebrew we provide [homebrew-mpv][homebrew
|
||||
to date formula that compiles mpv with sensible dependencies and defaults for
|
||||
OSX.
|
||||
|
||||
### configure `--enable-*` parameters
|
||||
FFmpeg vs. Libav
|
||||
----------------
|
||||
|
||||
The `--enable-*` parameters unconditionally force options on, completely
|
||||
skipping autodetection. This behavior is unlike what you may be used to from
|
||||
autoconf-based configure scripts that can decide to override you. This greater
|
||||
level of control comes at a price. You may have to provide the correct compiler
|
||||
and linker flags yourself.
|
||||
|
||||
If you used one of these options and experience a compilation or
|
||||
linking failure, make sure you have passed the necessary compiler/linker flags
|
||||
to configure.
|
||||
|
||||
mpv's configure script is greedy and automatically enables features as a result
|
||||
of autodetection. The cases where you may want to use `--enable-*` are very
|
||||
limited.
|
||||
Generally, mpv should work with the latest release as well as the git version
|
||||
of both FFmpeg and Libav. But FFmpeg is preferred, and some mpv features work
|
||||
with FFmpeg only. See the [wiki article][ffmpeg_vs_libav] about the issue.
|
||||
|
||||
Bug reports
|
||||
-----------
|
||||
@@ -97,3 +105,4 @@ To contact the `mpv` team in private write to `mpv-team@googlegroups.com`.
|
||||
[issue-tracker]: https://github.com/mpv-player/mpv/issues
|
||||
[mpv-users]: https://groups.google.com/forum/?hl=en#!forum/mpv-users
|
||||
[mpv-devel]: https://groups.google.com/forum/?hl=en#!forum/mpv-devel
|
||||
[ffmpeg_vs_libav]: https://github.com/mpv-player/mpv/wiki/FFmpeg-versus-Libav
|
||||
@@ -0,0 +1,41 @@
|
||||
Changes
|
||||
-------
|
||||
|
||||
- Build system switches to waf. The old build system is still available by
|
||||
running ``./old-configure``, but it will be removed some time in the future.
|
||||
- The "run" input command changes in a not backwards compatible way.
|
||||
- Some input.conf commands were replaced (such as speed_mult).
|
||||
- Many audio/video filter have changed sub-options in a not backwards
|
||||
compatible way. (This is a consequence of unifying audio/video filter
|
||||
option parsing.)
|
||||
- Most video filters now use libavfilter internally if available.
|
||||
- Disable key autorepeat for some key binds, which prevents accidentally
|
||||
triggering a binding multiple times.
|
||||
- Encoding: install and include encoding profiles by default.
|
||||
|
||||
Bug fixes
|
||||
---------
|
||||
|
||||
- Fix a bug that degraded vdpau playback after ~1 hour of runtime.
|
||||
- Restrict hardware decoding to codecs where it generally makes sense. This
|
||||
excludes mpeg4 for example, which often causes hardware decoders to decode to
|
||||
garbage. Use ``--hwdec-codecs`` to enable hardware decoding for more codecs.
|
||||
|
||||
New features
|
||||
------------
|
||||
|
||||
- Add window-scale property, which allows changing the window size via mpv key
|
||||
bindings.
|
||||
- Add revert_seek command, which is meant to undo accidental seeks.
|
||||
- Hardware decoding (vdpau, vda, vaapi) can now be used with vo_opengl.
|
||||
- Pulseaudio: set pulse stream name to mpv window title (like MPlayer).
|
||||
- Support showing 2 subtitles at a time (``--secondary-sid``).
|
||||
- Re-add some basic ``dvdnav://`` support. This doesn't handle navigation
|
||||
highlights correctly, but navigation should mostly work. (Note that
|
||||
``dvdnav://`` by default starts the longest title, instead of the DVD menu.
|
||||
The menu is available with ``dvdnav://menu``.)
|
||||
- Win32: you can drop files on the mpv window.
|
||||
|
||||
This listing is not complete. There are many more bug fixes and changes. The
|
||||
complete change log can be viewed by running ``git log f572d2..f06a87`` in
|
||||
the git repository.
|
||||
+3
-3
@@ -56,7 +56,7 @@ sub generate_c_header {
|
||||
my $l = max(map { length $_->{valname} } values %{$el->{subelements}});
|
||||
|
||||
# Output each variable, with pointers for array (multiple) elements
|
||||
for my $subel (values %{$el->{subelements}}) {
|
||||
for my $subel (sort { $a->{definename} cmp $b->{definename} } values %{$el->{subelements}}) {
|
||||
printf " %-${l}s %s%s;\n",
|
||||
$subel->{valname}, $subel->{multiple}?'*':' ', $subel->{fieldname};
|
||||
}
|
||||
@@ -101,7 +101,7 @@ sub generate_c_definitions {
|
||||
|
||||
# define a field for each subelement
|
||||
# also does lots of macro magic, but doesn't open a scope
|
||||
for my $subel (values %{$el->{subelements}}) {
|
||||
for my $subel (sort { $a->{definename} cmp $b->{definename} } values %{$el->{subelements}}) {
|
||||
print "F($subel->{definename}, $subel->{fieldname}, ".
|
||||
($subel->{multiple}?'1':'0').")\n";
|
||||
}
|
||||
@@ -166,4 +166,4 @@ sub process_elem {
|
||||
} else {
|
||||
print "$elem->{type} ". $elem->get_value ."\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ for __midentify__key in $__midentify__allprops; do
|
||||
eval unset $__midentify__nextprefix$__midentify__key
|
||||
done
|
||||
|
||||
__midentify__output=`$MPV --playing-msg="$__midentify__propstr" --vo=null --ao=null --frames=1 --quiet "$@"`
|
||||
__midentify__output=`$MPV --playing-msg="$__midentify__propstr" --vo=null --ao=null --frames=1 --quiet --no-cache --no-config "$@"`
|
||||
__midentify__fileindex=0
|
||||
__midentify__prefix=
|
||||
while :; do
|
||||
|
||||
+13
-9
@@ -30,24 +30,28 @@ def user_dylib_lst(input_file):
|
||||
return [lib for lib in dylib_lst(input_file).split("\n") if
|
||||
is_user_lib(lib, input_file)]
|
||||
|
||||
def bundle_name(binary_name):
|
||||
def bundle_path(binary_name):
|
||||
return "%s.app" % binary_name
|
||||
|
||||
def bundle_name(binary_name):
|
||||
return os.path.basename(bundle_path(binary_name))
|
||||
|
||||
def target_plist(binary_name):
|
||||
return os.path.join(bundle_name(binary_name), 'Contents', 'Info.plist')
|
||||
return os.path.join(bundle_path(binary_name), 'Contents', 'Info.plist')
|
||||
|
||||
def target_directory(binary_name):
|
||||
return os.path.join(bundle_name(binary_name), 'Contents', 'MacOS')
|
||||
return os.path.join(bundle_path(binary_name), 'Contents', 'MacOS')
|
||||
|
||||
def target_binary(binary_name):
|
||||
return os.path.join(target_directory(binary_name), binary_name)
|
||||
return os.path.join(target_directory(binary_name),
|
||||
os.path.basename(binary_name))
|
||||
|
||||
def copy_bundle(binary_name):
|
||||
if os.path.isdir(bundle_name(binary_name)):
|
||||
shutil.rmtree(bundle_name(binary_name))
|
||||
if os.path.isdir(bundle_path(binary_name)):
|
||||
shutil.rmtree(bundle_path(binary_name))
|
||||
shutil.copytree(
|
||||
os.path.join('TOOLS', 'osxbundle', bundle_name(binary_name)),
|
||||
bundle_name(binary_name))
|
||||
bundle_path(binary_name))
|
||||
|
||||
def copy_binary(binary_name):
|
||||
shutil.copy(binary_name, target_binary(binary_name))
|
||||
@@ -91,7 +95,7 @@ def fix_dylibs_paths(target_file, dest_dir, root=True):
|
||||
fix_dylibs_paths(dylib_dest_path, dest_dir, False)
|
||||
|
||||
def apply_plist_template(plist_file, version):
|
||||
sh("sed -i -e 's/{{VERSION}}/%s/g' %s" % (version, plist_file))
|
||||
sh("sed -i -e 's/${VERSION}/%s/g' %s" % (version, plist_file))
|
||||
|
||||
def bundle_dependencies(binary_name):
|
||||
lib_bundle_directory = os.path.join(target_directory(binary_name), "lib")
|
||||
@@ -99,7 +103,7 @@ def bundle_dependencies(binary_name):
|
||||
fix_dylibs_paths(target_binary(binary_name), lib_bundle_directory)
|
||||
|
||||
def main():
|
||||
version = sh("TOOLS/osxbundle/version.sh").strip()
|
||||
version = sh("./version.sh --print").strip()
|
||||
|
||||
usage = "usage: %prog [options] arg"
|
||||
parser = OptionParser(usage)
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>{{VERSION}}</string>
|
||||
<string>${VERSION}</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<dir>/System/Library/Fonts</dir>
|
||||
<dir>/Library/Fonts</dir>
|
||||
<dir>~/Library/Fonts</dir>
|
||||
<dir>/Network/Library/Fonts</dir>
|
||||
<cachedir>~/.fontconfig</cachedir>
|
||||
|
||||
<!--
|
||||
Accept deprecated 'mono' alias, replacing it with 'monospace'
|
||||
-->
|
||||
<match target="pattern">
|
||||
<test qual="any" name="family">
|
||||
<string>mono</string>
|
||||
</test>
|
||||
<edit name="family" mode="assign" binding="same">
|
||||
<string>monospace</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<!--
|
||||
Accept alternate 'sans serif' spelling, replacing it with 'sans-serif'
|
||||
-->
|
||||
<match target="pattern">
|
||||
<test qual="any" name="family">
|
||||
<string>sans serif</string>
|
||||
</test>
|
||||
<edit name="family" mode="assign" binding="same">
|
||||
<string>sans-serif</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<!--
|
||||
Accept deprecated 'sans' alias, replacing it with 'sans-serif'
|
||||
-->
|
||||
<match target="pattern">
|
||||
<test qual="any" name="family">
|
||||
<string>sans</string>
|
||||
</test>
|
||||
<edit name="family" mode="assign" binding="same">
|
||||
<string>sans-serif</string>
|
||||
</edit>
|
||||
</match>
|
||||
|
||||
<config>
|
||||
<!--
|
||||
These are the default Unicode chars that are expected to be blank
|
||||
in fonts. All other blank chars are assumed to be broken and
|
||||
won't appear in the resulting charsets
|
||||
-->
|
||||
<blank>
|
||||
<int>0x0020</int> <!-- SPACE -->
|
||||
<int>0x00A0</int> <!-- NO-BREAK SPACE -->
|
||||
<int>0x00AD</int> <!-- SOFT HYPHEN -->
|
||||
<int>0x034F</int> <!-- COMBINING GRAPHEME JOINER -->
|
||||
<int>0x0600</int> <!-- ARABIC NUMBER SIGN -->
|
||||
<int>0x0601</int> <!-- ARABIC SIGN SANAH -->
|
||||
<int>0x0602</int> <!-- ARABIC FOOTNOTE MARKER -->
|
||||
<int>0x0603</int> <!-- ARABIC SIGN SAFHA -->
|
||||
<int>0x06DD</int> <!-- ARABIC END OF AYAH -->
|
||||
<int>0x070F</int> <!-- SYRIAC ABBREVIATION MARK -->
|
||||
<int>0x115F</int> <!-- HANGUL CHOSEONG FILLER -->
|
||||
<int>0x1160</int> <!-- HANGUL JUNGSEONG FILLER -->
|
||||
<int>0x1680</int> <!-- OGHAM SPACE MARK -->
|
||||
<int>0x17B4</int> <!-- KHMER VOWEL INHERENT AQ -->
|
||||
<int>0x17B5</int> <!-- KHMER VOWEL INHERENT AA -->
|
||||
<int>0x180E</int> <!-- MONGOLIAN VOWEL SEPARATOR -->
|
||||
<int>0x2000</int> <!-- EN QUAD -->
|
||||
<int>0x2001</int> <!-- EM QUAD -->
|
||||
<int>0x2002</int> <!-- EN SPACE -->
|
||||
<int>0x2003</int> <!-- EM SPACE -->
|
||||
<int>0x2004</int> <!-- THREE-PER-EM SPACE -->
|
||||
<int>0x2005</int> <!-- FOUR-PER-EM SPACE -->
|
||||
<int>0x2006</int> <!-- SIX-PER-EM SPACE -->
|
||||
<int>0x2007</int> <!-- FIGURE SPACE -->
|
||||
<int>0x2008</int> <!-- PUNCTUATION SPACE -->
|
||||
<int>0x2009</int> <!-- THIN SPACE -->
|
||||
<int>0x200A</int> <!-- HAIR SPACE -->
|
||||
<int>0x200B</int> <!-- ZERO WIDTH SPACE -->
|
||||
<int>0x200C</int> <!-- ZERO WIDTH NON-JOINER -->
|
||||
<int>0x200D</int> <!-- ZERO WIDTH JOINER -->
|
||||
<int>0x200E</int> <!-- LEFT-TO-RIGHT MARK -->
|
||||
<int>0x200F</int> <!-- RIGHT-TO-LEFT MARK -->
|
||||
<int>0x2028</int> <!-- LINE SEPARATOR -->
|
||||
<int>0x2029</int> <!-- PARAGRAPH SEPARATOR -->
|
||||
<int>0x202A</int> <!-- LEFT-TO-RIGHT EMBEDDING -->
|
||||
<int>0x202B</int> <!-- RIGHT-TO-LEFT EMBEDDING -->
|
||||
<int>0x202C</int> <!-- POP DIRECTIONAL FORMATTING -->
|
||||
<int>0x202D</int> <!-- LEFT-TO-RIGHT OVERRIDE -->
|
||||
<int>0x202E</int> <!-- RIGHT-TO-LEFT OVERRIDE -->
|
||||
<int>0x202F</int> <!-- NARROW NO-BREAK SPACE -->
|
||||
<int>0x205F</int> <!-- MEDIUM MATHEMATICAL SPACE -->
|
||||
<int>0x2060</int> <!-- WORD JOINER -->
|
||||
<int>0x2061</int> <!-- FUNCTION APPLICATION -->
|
||||
<int>0x2062</int> <!-- INVISIBLE TIMES -->
|
||||
<int>0x2063</int> <!-- INVISIBLE SEPARATOR -->
|
||||
<int>0x206A</int> <!-- INHIBIT SYMMETRIC SWAPPING -->
|
||||
<int>0x206B</int> <!-- ACTIVATE SYMMETRIC SWAPPING -->
|
||||
<int>0x206C</int> <!-- INHIBIT ARABIC FORM SHAPING -->
|
||||
<int>0x206D</int> <!-- ACTIVATE ARABIC FORM SHAPING -->
|
||||
<int>0x206E</int> <!-- NATIONAL DIGIT SHAPES -->
|
||||
<int>0x206F</int> <!-- NOMINAL DIGIT SHAPES -->
|
||||
<int>0x2800</int> <!-- BRAILLE PATTERN BLANK -->
|
||||
<int>0x3000</int> <!-- IDEOGRAPHIC SPACE -->
|
||||
<int>0x3164</int> <!-- HANGUL FILLER -->
|
||||
<int>0xFEFF</int> <!-- ZERO WIDTH NO-BREAK SPACE -->
|
||||
<int>0xFFA0</int> <!-- HALFWIDTH HANGUL FILLER -->
|
||||
<int>0xFFF9</int> <!-- INTERLINEAR ANNOTATION ANCHOR -->
|
||||
<int>0xFFFA</int> <!-- INTERLINEAR ANNOTATION SEPARATOR -->
|
||||
<int>0xFFFB</int> <!-- INTERLINEAR ANNOTATION TERMINATOR -->
|
||||
</blank>
|
||||
<!--
|
||||
Rescan configuration every 30 seconds when FcFontSetList is called
|
||||
-->
|
||||
<rescan>
|
||||
<int>30</int>
|
||||
</rescan>
|
||||
</config>
|
||||
</fontconfig>
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Extract revision number from file used by daily tarball snapshots
|
||||
# or from "git describe" output
|
||||
git_revision=$(cat snapshot_version 2> /dev/null)
|
||||
test $git_revision || test ! -d .git || \
|
||||
git_revision=`git describe --match "v[0-9]*" --always`
|
||||
git_revision=$(expr "$git_revision" : v*'\(.*\)')
|
||||
test $git_revision || git_revision=UNKNOWN
|
||||
|
||||
# releases extract the version number from the VERSION file
|
||||
version=$(cat VERSION 2> /dev/null)
|
||||
test $version || version=$git_revision
|
||||
|
||||
echo $version
|
||||
+15
-13
@@ -3,20 +3,18 @@
|
||||
"""
|
||||
This script emulates "unique application" functionality on Linux. When starting
|
||||
playback with this script, it will try to reuse an already running instance of
|
||||
mpv (but only if that was started with umpv).
|
||||
mpv (but only if that was started with umpv). Other mpv instances (not started
|
||||
by umpv) are ignored, and the script doesn't know about them.
|
||||
|
||||
This script only uses mpv instances started with umpv. Other instances are
|
||||
ignored, and don't exist for the script.
|
||||
|
||||
This only takes filenames as arguments. If mpv is already running, they are
|
||||
appended to mpv's internal playlist. If the file does not exist or is otherwise
|
||||
not playable, mpv will skip the playlist entry when attempting to play it (from
|
||||
the GUI perspective, it's silently ignored).
|
||||
This only takes filenames as arguments. Custom options can't be used; the script
|
||||
interprets them as filenames. If mpv is already running, the files passed to
|
||||
umpv are appended to mpv's internal playlist. If a file does not exist or is
|
||||
otherwise not playable, mpv will skip the playlist entry when attempting to
|
||||
play it (from the GUI perspective, it's silently ignored).
|
||||
|
||||
If mpv isn't running yet, this script will start mpv and let it control the
|
||||
current terminal. If you play an audio-only file, mpv will not open a window
|
||||
(that is its normal behavior; but it might not be what a user expects if this
|
||||
script is used in a GUI setting).
|
||||
current terminal. It will not write output to stdout/stderr, because this
|
||||
will typically just fill ~/.xsession-errors with garbage.
|
||||
|
||||
mpv will terminate if there are no more files to play, and running the umpv
|
||||
script after that will start a new mpv instance.
|
||||
@@ -28,7 +26,10 @@ Note that you can control the mpv instance by writing to the command fifo:
|
||||
Warning:
|
||||
|
||||
The script attempts to make sure the FIFO is safely created (i.e. not world-
|
||||
readable, not trying to open a bogus
|
||||
writable), and checks that it's really a FIFO. This is important for security,
|
||||
because the FIFO allows anyone with write access to run arbitrary commands
|
||||
in mpv's context using the "run" input command. If you are worried about
|
||||
security, you should verify that the code handles these concerns correctly.
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -88,4 +89,5 @@ else:
|
||||
raise e
|
||||
os.mkfifo(FIFO, 0600)
|
||||
|
||||
subprocess.check_call(["mpv", "--input-file=" + FIFO] + files)
|
||||
subprocess.check_call(["mpv", "--really-quiet", "--force-window",
|
||||
"--input-file=" + FIFO, "--"] + files)
|
||||
@@ -16,6 +16,7 @@ esac
|
||||
: ${ILDETECT_DRY_RUN:=}
|
||||
: ${ILDETECT_QUIET:=}
|
||||
: ${ILDETECT_RUN_INTERLACED_ONLY:=}
|
||||
: ${ILDETECT_FORCE_RUN:=}
|
||||
: ${MAKE:=make}
|
||||
|
||||
# exit status:
|
||||
@@ -76,7 +77,12 @@ case "$out" in
|
||||
exit 2
|
||||
;;
|
||||
*"probably: "*)
|
||||
exit 8
|
||||
[ -n "$ILDETECT_FORCE_RUN" ] || exit 8
|
||||
[ -n "$ILDETECT_DRY_RUN" ] || \
|
||||
$ILDETECT_MPV "$@" -vf-pre yadif
|
||||
r=$?
|
||||
[ $r -eq 0 ] || exit $(($r | 16))
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
exit 16
|
||||
|
||||
+174
-13
@@ -15,44 +15,65 @@
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mpvcore/mp_talloc.h"
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "talloc.h"
|
||||
#include "common/common.h"
|
||||
#include "audio.h"
|
||||
|
||||
static void update_redundant_info(struct mp_audio *mpa)
|
||||
{
|
||||
assert(mp_chmap_is_empty(&mpa->channels) ||
|
||||
mp_chmap_is_valid(&mpa->channels));
|
||||
mpa->nch = mpa->channels.num;
|
||||
mpa->bps = af_fmt2bits(mpa->format) / 8;
|
||||
if (af_fmt_is_planar(mpa->format)) {
|
||||
mpa->spf = 1;
|
||||
mpa->num_planes = mpa->nch;
|
||||
mpa->sstride = mpa->bps;
|
||||
} else {
|
||||
mpa->spf = mpa->nch;
|
||||
mpa->num_planes = 1;
|
||||
mpa->sstride = mpa->bps * mpa->nch;
|
||||
}
|
||||
}
|
||||
|
||||
void mp_audio_set_format(struct mp_audio *mpa, int format)
|
||||
{
|
||||
mpa->format = format;
|
||||
mpa->bps = af_fmt2bits(format) / 8;
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_set_num_channels(struct mp_audio *mpa, int num_channels)
|
||||
{
|
||||
struct mp_chmap map;
|
||||
mp_chmap_from_channels(&map, num_channels);
|
||||
mp_audio_set_channels(mpa, &map);
|
||||
mp_chmap_from_channels(&mpa->channels, num_channels);
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
// Use old MPlayer/ALSA channel layout.
|
||||
void mp_audio_set_channels_old(struct mp_audio *mpa, int num_channels)
|
||||
{
|
||||
struct mp_chmap map;
|
||||
mp_chmap_from_channels_alsa(&map, num_channels);
|
||||
mp_audio_set_channels(mpa, &map);
|
||||
mp_chmap_from_channels_alsa(&mpa->channels, num_channels);
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap)
|
||||
{
|
||||
assert(mp_chmap_is_empty(chmap) || mp_chmap_is_valid(chmap));
|
||||
mpa->channels = *chmap;
|
||||
mpa->nch = mpa->channels.num;
|
||||
update_redundant_info(mpa);
|
||||
}
|
||||
|
||||
void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src)
|
||||
{
|
||||
mp_audio_set_format(dst, src->format);
|
||||
mp_audio_set_channels(dst, &src->channels);
|
||||
dst->format = src->format;
|
||||
dst->channels = src->channels;
|
||||
dst->rate = src->rate;
|
||||
update_redundant_info(dst);
|
||||
}
|
||||
|
||||
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b)
|
||||
@@ -61,11 +82,17 @@ bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b)
|
||||
mp_chmap_equals(&a->channels, &b->channels);
|
||||
}
|
||||
|
||||
bool mp_audio_config_valid(const struct mp_audio *mpa)
|
||||
{
|
||||
return mp_chmap_is_valid(&mpa->channels) && af_fmt_is_valid(mpa->format)
|
||||
&& mpa->rate >= 1 && mpa->rate < 10000000;
|
||||
}
|
||||
|
||||
char *mp_audio_fmt_to_str(int srate, const struct mp_chmap *chmap, int format)
|
||||
{
|
||||
char *chstr = mp_chmap_to_str(chmap);
|
||||
char *res = talloc_asprintf(NULL, "%dHz %s %dch %s", srate, chstr,
|
||||
chmap->num, af_fmt2str_short(format));
|
||||
chmap->num, af_fmt_to_str(format));
|
||||
talloc_free(chstr);
|
||||
return res;
|
||||
}
|
||||
@@ -74,3 +101,137 @@ char *mp_audio_config_to_str(struct mp_audio *mpa)
|
||||
{
|
||||
return mp_audio_fmt_to_str(mpa->rate, &mpa->channels, mpa->format);
|
||||
}
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa)
|
||||
{
|
||||
if (af_fmt_is_planar(mpa->format))
|
||||
mp_audio_set_format(mpa, af_fmt_from_planar(mpa->format));
|
||||
}
|
||||
|
||||
// Return used size of a plane. (The size is the same for all planes.)
|
||||
int mp_audio_psize(struct mp_audio *mpa)
|
||||
{
|
||||
return mpa->samples * mpa->sstride;
|
||||
}
|
||||
|
||||
void mp_audio_set_null_data(struct mp_audio *mpa)
|
||||
{
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
mpa->planes[n] = NULL;
|
||||
mpa->samples = 0;
|
||||
}
|
||||
|
||||
static void mp_audio_destructor(void *ptr)
|
||||
{
|
||||
struct mp_audio *mpa = ptr;
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++) {
|
||||
// Note: don't free if not allocated by mp_audio_realloc
|
||||
if (mpa->allocated[n])
|
||||
av_free(mpa->planes[n]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Reallocate the data stored in mpa->planes[n] so that enough samples are
|
||||
* available on every plane. The previous data is kept (for the smallest
|
||||
* common number of samples before/after resize).
|
||||
*
|
||||
* mpa->samples is not set or used.
|
||||
*
|
||||
* This function is flexible enough to handle format and channel layout
|
||||
* changes. In these cases, all planes are reallocated as needed. Unused
|
||||
* planes are freed.
|
||||
*
|
||||
* mp_audio_realloc(mpa, 0) will still yield non-NULL for mpa->data[n].
|
||||
*
|
||||
* Allocated data is implicitly freed on talloc_free(mpa).
|
||||
*/
|
||||
void mp_audio_realloc(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
assert(samples >= 0);
|
||||
if (samples >= INT_MAX / mpa->sstride)
|
||||
abort(); // oom
|
||||
int size = MPMAX(samples * mpa->sstride, 1);
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
if (size != mpa->allocated[n]) {
|
||||
// Note: av_realloc() can't be used (see libavutil doxygen)
|
||||
void *new = av_malloc(size);
|
||||
if (!new)
|
||||
abort();
|
||||
if (mpa->allocated[n])
|
||||
memcpy(new, mpa->planes[n], MPMIN(mpa->allocated[n], size));
|
||||
av_free(mpa->planes[n]);
|
||||
mpa->planes[n] = new;
|
||||
mpa->allocated[n] = size;
|
||||
}
|
||||
}
|
||||
for (int n = mpa->num_planes; n < MP_NUM_CHANNELS; n++) {
|
||||
av_free(mpa->planes[n]);
|
||||
mpa->planes[n] = NULL;
|
||||
mpa->allocated[n] = 0;
|
||||
}
|
||||
talloc_set_destructor(mpa, mp_audio_destructor);
|
||||
}
|
||||
|
||||
// Like mp_audio_realloc(), but only reallocate if the audio grows in size.
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples)
|
||||
{
|
||||
if (samples > mp_audio_get_allocated_size(mpa))
|
||||
mp_audio_realloc(mpa, samples);
|
||||
}
|
||||
|
||||
/* Get the size allocated for the data, in number of samples. If the allocated
|
||||
* size isn't on sample boundaries (e.g. after format changes), the returned
|
||||
* sample number is a rounded down value.
|
||||
*
|
||||
* Note that this only works in situations where mp_audio_realloc() also works!
|
||||
*/
|
||||
int mp_audio_get_allocated_size(struct mp_audio *mpa)
|
||||
{
|
||||
int size = 0;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
int s = mpa->allocated[n] / mpa->sstride;
|
||||
size = n == 0 ? s : MPMIN(size, s);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
// Clear the samples [start, start + length) with silence.
|
||||
void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length)
|
||||
{
|
||||
assert(start >= 0 && length >= 0 && start + length <= mpa->samples);
|
||||
int offset = start * mpa->sstride;
|
||||
int size = length * mpa->sstride;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
if (n > 0 && mpa->planes[n] == mpa->planes[0])
|
||||
continue; // silly optimization for special cases
|
||||
af_fill_silence((char *)mpa->planes[n] + offset, size, mpa->format);
|
||||
}
|
||||
}
|
||||
|
||||
// All integer parameters are in samples.
|
||||
// dst and src can overlap.
|
||||
void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length)
|
||||
{
|
||||
assert(mp_audio_config_equals(dst, src));
|
||||
assert(length >= 0);
|
||||
assert(dst_offset >= 0 && dst_offset + length <= dst->samples);
|
||||
assert(src_offset >= 0 && src_offset + length <= src->samples);
|
||||
|
||||
for (int n = 0; n < dst->num_planes; n++) {
|
||||
memmove((char *)dst->planes[n] + dst_offset * dst->sstride,
|
||||
(char *)src->planes[n] + src_offset * src->sstride,
|
||||
length * dst->sstride);
|
||||
}
|
||||
}
|
||||
|
||||
// Set data to the audio after the given number of samples (i.e. slice it).
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= data->samples);
|
||||
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
data->planes[n] = (uint8_t *)data->planes[n] + samples * data->sstride;
|
||||
|
||||
data->samples -= samples;
|
||||
}
|
||||
+30
-5
@@ -23,14 +23,22 @@
|
||||
|
||||
// Audio data chunk
|
||||
struct mp_audio {
|
||||
void *audio; // data buffer
|
||||
int len; // buffer length (in bytes)
|
||||
int rate; // sample rate
|
||||
int samples; // number of samples in data (per channel)
|
||||
void *planes[MP_NUM_CHANNELS]; // data buffer (one per plane)
|
||||
int rate; // sample rate
|
||||
struct mp_chmap channels; // channel layout, use mp_audio_set_*() to set
|
||||
int format; // format (AF_FORMAT_...), use mp_audio_set_format() to set
|
||||
// Redundant fields, for convenience
|
||||
int nch; // number of channels (redundant with chmap)
|
||||
int bps; // bytes per sample (redundant with format)
|
||||
int sstride; // distance between 2 samples in bytes on a plane
|
||||
// interleaved: bps * nch
|
||||
// planar: bps
|
||||
int nch; // number of channels (redundant with chmap)
|
||||
int spf; // sub-samples per sample on each plane
|
||||
int num_planes; // number of planes
|
||||
int bps; // size of sub-samples (af_fmt2bits(format) / 8)
|
||||
|
||||
// private
|
||||
int allocated[MP_NUM_CHANNELS]; // use mp_audio_get_allocated_size()
|
||||
};
|
||||
|
||||
void mp_audio_set_format(struct mp_audio *mpa, int format);
|
||||
@@ -39,8 +47,25 @@ void mp_audio_set_channels_old(struct mp_audio *mpa, int num_channels);
|
||||
void mp_audio_set_channels(struct mp_audio *mpa, const struct mp_chmap *chmap);
|
||||
void mp_audio_copy_config(struct mp_audio *dst, const struct mp_audio *src);
|
||||
bool mp_audio_config_equals(const struct mp_audio *a, const struct mp_audio *b);
|
||||
bool mp_audio_config_valid(const struct mp_audio *mpa);
|
||||
|
||||
char *mp_audio_fmt_to_str(int srate, const struct mp_chmap *chmap, int format);
|
||||
char *mp_audio_config_to_str(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_force_interleaved_format(struct mp_audio *mpa);
|
||||
|
||||
int mp_audio_psize(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_set_null_data(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_realloc(struct mp_audio *mpa, int samples);
|
||||
void mp_audio_realloc_min(struct mp_audio *mpa, int samples);
|
||||
int mp_audio_get_allocated_size(struct mp_audio *mpa);
|
||||
|
||||
void mp_audio_fill_silence(struct mp_audio *mpa, int start, int length);
|
||||
|
||||
void mp_audio_copy(struct mp_audio *dst, int dst_offset,
|
||||
struct mp_audio *src, int src_offset, int length);
|
||||
void mp_audio_skip_samples(struct mp_audio *data, int samples);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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 <stddef.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include "audio_buffer.h"
|
||||
#include "audio.h"
|
||||
#include "format.h"
|
||||
|
||||
struct mp_audio_buffer {
|
||||
struct mp_audio *buffer;
|
||||
};
|
||||
|
||||
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx)
|
||||
{
|
||||
struct mp_audio_buffer *ab = talloc(talloc_ctx, struct mp_audio_buffer);
|
||||
*ab = (struct mp_audio_buffer) {
|
||||
.buffer = talloc_zero(ab, struct mp_audio),
|
||||
};
|
||||
return ab;
|
||||
}
|
||||
|
||||
// Reinitialize the buffer, set a new format, drop old data.
|
||||
// The audio data in fmt is not used, only the format.
|
||||
void mp_audio_buffer_reinit(struct mp_audio_buffer *ab, struct mp_audio *fmt)
|
||||
{
|
||||
mp_audio_copy_config(ab->buffer, fmt);
|
||||
mp_audio_realloc(ab->buffer, 1);
|
||||
ab->buffer->samples = 0;
|
||||
}
|
||||
|
||||
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
|
||||
const struct mp_chmap *channels, int srate)
|
||||
{
|
||||
struct mp_audio mpa = {0};
|
||||
mp_audio_set_format(&mpa, format);
|
||||
mp_audio_set_channels(&mpa, channels);
|
||||
mpa.rate = srate;
|
||||
mp_audio_buffer_reinit(ab, &mpa);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_get_format(struct mp_audio_buffer *ab,
|
||||
struct mp_audio *out_fmt)
|
||||
{
|
||||
*out_fmt = (struct mp_audio){0};
|
||||
mp_audio_copy_config(out_fmt, ab->buffer);
|
||||
}
|
||||
|
||||
// Make the total size of the internal buffer at least this number of samples.
|
||||
void mp_audio_buffer_preallocate_min(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
mp_audio_realloc_min(ab->buffer, samples);
|
||||
}
|
||||
|
||||
// Get number of samples that can be written without forcing a resize of the
|
||||
// internal buffer.
|
||||
int mp_audio_buffer_get_write_available(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return mp_audio_get_allocated_size(ab->buffer) - ab->buffer->samples;
|
||||
}
|
||||
|
||||
// Get a pointer to the end of the buffer (where writing would append). If the
|
||||
// internal buffer is too small for the given number of samples, it's resized.
|
||||
// After writing to the buffer, mp_audio_buffer_finish_write() has to be used
|
||||
// to make the written data part of the readable buffer.
|
||||
void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int samples,
|
||||
struct mp_audio *out_buffer)
|
||||
{
|
||||
assert(samples >= 0);
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples + samples);
|
||||
*out_buffer = *ab->buffer;
|
||||
out_buffer->samples = ab->buffer->samples + samples;
|
||||
mp_audio_skip_samples(out_buffer, ab->buffer->samples);
|
||||
}
|
||||
|
||||
void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= mp_audio_buffer_get_write_available(ab));
|
||||
ab->buffer->samples += samples;
|
||||
}
|
||||
|
||||
// Append data to the end of the buffer.
|
||||
// If the buffer is not large enough, it is transparently resized.
|
||||
// For now always copies the data.
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa)
|
||||
{
|
||||
int offset = ab->buffer->samples;
|
||||
ab->buffer->samples += mpa->samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, offset, mpa, 0, mpa->samples);
|
||||
}
|
||||
|
||||
// Prepend silence to the start of the buffer.
|
||||
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0);
|
||||
int oldlen = ab->buffer->samples;
|
||||
ab->buffer->samples += samples;
|
||||
mp_audio_realloc_min(ab->buffer, ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, samples, ab->buffer, 0, oldlen);
|
||||
mp_audio_fill_silence(ab->buffer, 0, samples);
|
||||
}
|
||||
|
||||
// Get the start of the current readable buffer.
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa)
|
||||
{
|
||||
*out_mpa = *ab->buffer;
|
||||
}
|
||||
|
||||
// Skip leading samples. (Used with mp_audio_buffer_peek() to read data.)
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples)
|
||||
{
|
||||
assert(samples >= 0 && samples <= ab->buffer->samples);
|
||||
mp_audio_copy(ab->buffer, 0, ab->buffer, samples,
|
||||
ab->buffer->samples - samples);
|
||||
ab->buffer->samples -= samples;
|
||||
}
|
||||
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab)
|
||||
{
|
||||
ab->buffer->samples = 0;
|
||||
}
|
||||
|
||||
// Return number of buffered audio samples
|
||||
int mp_audio_buffer_samples(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->buffer->samples;
|
||||
}
|
||||
|
||||
// Return amount of buffered audio in seconds.
|
||||
double mp_audio_buffer_seconds(struct mp_audio_buffer *ab)
|
||||
{
|
||||
return ab->buffer->samples / (double)ab->buffer->rate;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MP_AUDIO_BUFFER_H
|
||||
#define MP_AUDIO_BUFFER_H
|
||||
|
||||
struct mp_audio_buffer;
|
||||
struct mp_audio;
|
||||
struct mp_chmap;
|
||||
|
||||
struct mp_audio_buffer *mp_audio_buffer_create(void *talloc_ctx);
|
||||
void mp_audio_buffer_reinit(struct mp_audio_buffer *ab, struct mp_audio *fmt);
|
||||
void mp_audio_buffer_reinit_fmt(struct mp_audio_buffer *ab, int format,
|
||||
const struct mp_chmap *channels, int srate);
|
||||
void mp_audio_buffer_get_format(struct mp_audio_buffer *ab,
|
||||
struct mp_audio *out_fmt);
|
||||
void mp_audio_buffer_preallocate_min(struct mp_audio_buffer *ab, int samples);
|
||||
int mp_audio_buffer_get_write_available(struct mp_audio_buffer *ab);
|
||||
void mp_audio_buffer_get_write_buffer(struct mp_audio_buffer *ab, int minsamples,
|
||||
struct mp_audio *out_buffer);
|
||||
void mp_audio_buffer_finish_write(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_append(struct mp_audio_buffer *ab, struct mp_audio *mpa);
|
||||
void mp_audio_buffer_prepend_silence(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_peek(struct mp_audio_buffer *ab, struct mp_audio *out_mpa);
|
||||
void mp_audio_buffer_skip(struct mp_audio_buffer *ab, int samples);
|
||||
void mp_audio_buffer_clear(struct mp_audio_buffer *ab);
|
||||
int mp_audio_buffer_samples(struct mp_audio_buffer *ab);
|
||||
double mp_audio_buffer_seconds(struct mp_audio_buffer *ab);
|
||||
|
||||
#endif
|
||||
+9
-9
@@ -18,7 +18,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
#include "chmap.h"
|
||||
|
||||
// Names taken from libavutil/channel_layout.c (Not accessible by API.)
|
||||
@@ -471,19 +471,19 @@ bool mp_chmap_from_str(struct mp_chmap *dst, bstr src)
|
||||
return true;
|
||||
}
|
||||
|
||||
void mp_chmap_print_help(int msgt, int msgl)
|
||||
void mp_chmap_print_help(struct mp_log *log)
|
||||
{
|
||||
mp_msg(msgt, msgl, "Speakers:\n");
|
||||
mp_info(log, "Speakers:\n");
|
||||
for (int n = 0; n < MP_SPEAKER_ID_COUNT; n++) {
|
||||
if (speaker_names[n][0])
|
||||
mp_msg(msgt, msgl, " %-16s (%s)\n",
|
||||
speaker_names[n][0], speaker_names[n][1]);
|
||||
mp_info(log, " %-16s (%s)\n",
|
||||
speaker_names[n][0], speaker_names[n][1]);
|
||||
}
|
||||
mp_msg(msgt, msgl, "Standard layouts:\n");
|
||||
mp_info(log, "Standard layouts:\n");
|
||||
for (int n = 0; std_layout_names[n][0]; n++) {
|
||||
mp_msg(msgt, msgl, " %-16s (%s)\n",
|
||||
std_layout_names[n][0], std_layout_names[n][1]);
|
||||
mp_info(log, " %-16s (%s)\n",
|
||||
std_layout_names[n][0], std_layout_names[n][1]);
|
||||
}
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
mp_msg(msgt, msgl, " unknown%d\n", n);
|
||||
mp_info(log, " unknown%d\n", n);
|
||||
}
|
||||
+4
-2
@@ -20,7 +20,7 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include "mpvcore/bstr.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
#define MP_NUM_CHANNELS 8
|
||||
|
||||
@@ -121,7 +121,9 @@ void mp_chmap_get_reorder(int dst[MP_NUM_CHANNELS], const struct mp_chmap *from,
|
||||
|
||||
char *mp_chmap_to_str(const struct mp_chmap *src);
|
||||
bool mp_chmap_from_str(struct mp_chmap *dst, bstr src);
|
||||
void mp_chmap_print_help(int msgt, int msgl);
|
||||
|
||||
struct mp_log;
|
||||
void mp_chmap_print_help(struct mp_log *log);
|
||||
|
||||
// Use these to avoid chaos in case lavc's definition should diverge from MS.
|
||||
#define mp_chmap_to_waveext mp_chmap_to_lavc
|
||||
|
||||
+8
-8
@@ -19,11 +19,13 @@
|
||||
#ifndef MPLAYER_AD_H
|
||||
#define MPLAYER_AD_H
|
||||
|
||||
#include "mpvcore/codecs.h"
|
||||
#include "common/codecs.h"
|
||||
#include "demux/stheader.h"
|
||||
#include "demux/demux.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "audio/audio.h"
|
||||
#include "dec_audio.h"
|
||||
|
||||
struct mp_decoder_list;
|
||||
|
||||
@@ -31,16 +33,14 @@ struct mp_decoder_list;
|
||||
struct ad_functions {
|
||||
const char *name;
|
||||
void (*add_decoders)(struct mp_decoder_list *list);
|
||||
int (*preinit)(sh_audio_t *sh);
|
||||
int (*init)(sh_audio_t *sh, const char *decoder);
|
||||
void (*uninit)(sh_audio_t *sh);
|
||||
int (*control)(sh_audio_t *sh, int cmd, void *arg);
|
||||
int (*decode_audio)(sh_audio_t *sh, unsigned char *buffer, int minlen,
|
||||
int maxlen);
|
||||
int (*init)(struct dec_audio *da, const char *decoder);
|
||||
void (*uninit)(struct dec_audio *da);
|
||||
int (*control)(struct dec_audio *da, int cmd, void *arg);
|
||||
int (*decode_audio)(struct dec_audio *da, struct mp_audio *buffer, int maxlen);
|
||||
};
|
||||
|
||||
enum ad_ctrl {
|
||||
ADCTRL_RESYNC_STREAM = 1, // resync, called after seeking
|
||||
ADCTRL_RESET = 1, // flush and reset state, e.g. after seeking
|
||||
};
|
||||
|
||||
#endif /* MPLAYER_AD_H */
|
||||
+140
-201
@@ -29,38 +29,34 @@
|
||||
#include "talloc.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "mpvcore/av_common.h"
|
||||
#include "mpvcore/codecs.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "mpvcore/options.h"
|
||||
#include "mpvcore/av_opts.h"
|
||||
#include "common/av_common.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/options.h"
|
||||
#include "common/av_opts.h"
|
||||
|
||||
#include "ad.h"
|
||||
#include "audio/reorder_ch.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
#include "compat/mpbswap.h"
|
||||
#include "compat/libav.h"
|
||||
|
||||
struct priv {
|
||||
AVCodecContext *avctx;
|
||||
AVFrame *avframe;
|
||||
uint8_t *output;
|
||||
uint8_t *output_packed; // used by deplanarize to store packed audio samples
|
||||
int output_left;
|
||||
int unitsize;
|
||||
struct mp_audio frame;
|
||||
bool force_channel_map;
|
||||
struct demux_packet *packet;
|
||||
};
|
||||
|
||||
static void uninit(sh_audio_t *sh);
|
||||
static int decode_audio(sh_audio_t *sh,unsigned char *buffer,int minlen,int maxlen);
|
||||
static void uninit(struct dec_audio *da);
|
||||
static int decode_new_packet(struct dec_audio *da);
|
||||
|
||||
#define OPT_BASE_STRUCT struct MPOpts
|
||||
|
||||
const m_option_t ad_lavc_decode_opts_conf[] = {
|
||||
OPT_FLOATRANGE("ac3drc", ad_lavc_param.ac3drc, 0, 0, 2),
|
||||
OPT_FLAG("downmix", ad_lavc_param.downmix, 0),
|
||||
OPT_INTRANGE("threads", ad_lavc_param.threads, 0, 1, 16),
|
||||
OPT_STRING("o", ad_lavc_param.avopt, 0),
|
||||
{0}
|
||||
};
|
||||
@@ -68,12 +64,10 @@ const m_option_t ad_lavc_decode_opts_conf[] = {
|
||||
struct pcm_map
|
||||
{
|
||||
int tag;
|
||||
const char *codecs[5]; // {any, 1byte, 2bytes, 3bytes, 4bytes}
|
||||
const char *codecs[6]; // {any, 1byte, 2bytes, 3bytes, 4bytes, 8bytes}
|
||||
};
|
||||
|
||||
// NOTE: some of these are needed to make rawaudio with demux_mkv and others
|
||||
// work. ffmpeg does similar mapping internally, not part of the public
|
||||
// API. Some of these might be dead leftovers for demux_mov support.
|
||||
// NOTE: these are needed to make rawaudio with demux_mkv work.
|
||||
static const struct pcm_map tag_map[] = {
|
||||
// Microsoft PCM
|
||||
{0x0, {NULL, "pcm_u8", "pcm_s16le", "pcm_s24le", "pcm_s32le"}},
|
||||
@@ -81,26 +75,12 @@ static const struct pcm_map tag_map[] = {
|
||||
// MS PCM, Extended
|
||||
{0xfffe, {NULL, "pcm_u8", "pcm_s16le", "pcm_s24le", "pcm_s32le"}},
|
||||
// IEEE float
|
||||
{0x3, {"pcm_f32le"}},
|
||||
{0x3, {"pcm_f32le", [5] = "pcm_f64le"}},
|
||||
// 'raw '
|
||||
{0x20776172, {"pcm_s16be", [1] = "pcm_u8"}},
|
||||
// 'twos'/'sowt'
|
||||
{0x736F7774, {"pcm_s16be", [1] = "pcm_s8"}},
|
||||
{0x74776F73, {"pcm_s16be", [1] = "pcm_s8"}},
|
||||
// 'fl32'/'FL32'
|
||||
{0x32336c66, {"pcm_f32be"}},
|
||||
{0x32334C46, {"pcm_f32be"}},
|
||||
// '23lf'/'lpcm'
|
||||
{0x666c3332, {"pcm_f32le"}},
|
||||
{0x6D63706C, {"pcm_f32le"}},
|
||||
// 'in24', bigendian int24
|
||||
{0x34326e69, {"pcm_s24be"}},
|
||||
// '42ni', little endian int24, MPlayer internal fourCC
|
||||
{0x696e3234, {"pcm_s24le"}},
|
||||
// 'in32', bigendian int32
|
||||
{0x32336e69, {"pcm_s32be"}},
|
||||
// '23ni', little endian int32, MPlayer internal fourCC
|
||||
{0x696e3332, {"pcm_s32le"}},
|
||||
// 'twos', used by demux_mkv.c internally
|
||||
{MKTAG('t', 'w', 'o', 's'),
|
||||
{NULL, "pcm_s8", "pcm_s16be", "pcm_s24be", "pcm_s32be"}},
|
||||
{-1},
|
||||
};
|
||||
|
||||
@@ -123,6 +103,8 @@ static const struct pcm_map af_map[] = {
|
||||
{AF_FORMAT_S32_BE, {"pcm_s32be"}},
|
||||
{AF_FORMAT_FLOAT_LE, {"pcm_f32le"}},
|
||||
{AF_FORMAT_FLOAT_BE, {"pcm_f32be"}},
|
||||
{AF_FORMAT_DOUBLE_LE, {"pcm_f64le"}},
|
||||
{AF_FORMAT_DOUBLE_BE, {"pcm_f64be"}},
|
||||
{-1},
|
||||
};
|
||||
|
||||
@@ -130,11 +112,13 @@ static const char *find_pcm_decoder(const struct pcm_map *map, int format,
|
||||
int bits_per_sample)
|
||||
{
|
||||
int bytes = (bits_per_sample + 7) / 8;
|
||||
if (bytes == 8)
|
||||
bytes = 5; // 64 bit entry
|
||||
for (int n = 0; map[n].tag != -1; n++) {
|
||||
const struct pcm_map *entry = &map[n];
|
||||
if (entry->tag == format) {
|
||||
const char *dec = NULL;
|
||||
if (bytes >= 1 && bytes <= 4)
|
||||
if (bytes >= 1 && bytes <= 5)
|
||||
dec = entry->codecs[bytes];
|
||||
if (!dec)
|
||||
dec = entry->codecs[0];
|
||||
@@ -145,27 +129,25 @@ static const char *find_pcm_decoder(const struct pcm_map *map, int format,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int preinit(sh_audio_t *sh)
|
||||
static int setup_format(struct dec_audio *da)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *lavc_context = priv->avctx;
|
||||
struct sh_audio *sh_audio = da->header->audio;
|
||||
|
||||
/* Prefer playing audio with the samplerate given in container data
|
||||
* if available, but take number the number of channels and sample format
|
||||
* from the codec, since if the codec isn't using the correct values for
|
||||
* those everything breaks anyway.
|
||||
*/
|
||||
static int setup_format(sh_audio_t *sh_audio,
|
||||
const AVCodecContext *lavc_context)
|
||||
{
|
||||
struct priv *priv = sh_audio->context;
|
||||
int sample_format =
|
||||
af_from_avformat(av_get_packed_sample_fmt(lavc_context->sample_fmt));
|
||||
int samplerate = lavc_context->sample_rate;
|
||||
// If not set, try container samplerate
|
||||
if (!samplerate && sh_audio->wf) {
|
||||
samplerate = sh_audio->wf->nSamplesPerSec;
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_V, "ad_lavc: using container rate.\n");
|
||||
// Note: invalid parameters are rejected by dec_audio.c
|
||||
|
||||
int fmt = lavc_context->sample_fmt;
|
||||
mp_audio_set_format(&da->decoded, af_from_avformat(fmt));
|
||||
if (!da->decoded.format)
|
||||
MP_FATAL(da, "unsupported lavc format %s", av_get_sample_fmt_name(fmt));
|
||||
|
||||
da->decoded.rate = lavc_context->sample_rate;
|
||||
if (!da->decoded.rate && sh_audio->wf) {
|
||||
// If not set, try container samplerate.
|
||||
// (Maybe this can't happen, and it's an artifact from the past.)
|
||||
da->decoded.rate = sh_audio->wf->nSamplesPerSec;
|
||||
MP_WARN(da, "using container rate.\n");
|
||||
}
|
||||
|
||||
struct mp_chmap lavc_chmap;
|
||||
@@ -177,20 +159,12 @@ static int setup_format(sh_audio_t *sh_audio,
|
||||
if (lavc_chmap.num == sh_audio->channels.num)
|
||||
lavc_chmap = sh_audio->channels;
|
||||
}
|
||||
mp_audio_set_channels(&da->decoded, &lavc_chmap);
|
||||
|
||||
if (!mp_chmap_equals(&lavc_chmap, &sh_audio->channels) ||
|
||||
samplerate != sh_audio->samplerate ||
|
||||
sample_format != sh_audio->sample_format) {
|
||||
sh_audio->channels = lavc_chmap;
|
||||
sh_audio->samplerate = samplerate;
|
||||
sh_audio->sample_format = sample_format;
|
||||
sh_audio->samplesize = af_fmt2bits(sh_audio->sample_format) / 8;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_from_wf(AVCodecContext *avctx, WAVEFORMATEX *wf)
|
||||
static void set_from_wf(AVCodecContext *avctx, MP_WAVEFORMATEX *wf)
|
||||
{
|
||||
avctx->channels = wf->nChannels;
|
||||
avctx->sample_rate = wf->nSamplesPerSec;
|
||||
@@ -205,29 +179,30 @@ static void set_from_wf(AVCodecContext *avctx, WAVEFORMATEX *wf)
|
||||
}
|
||||
}
|
||||
|
||||
static int init(sh_audio_t *sh_audio, const char *decoder)
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct MPOpts *mpopts = sh_audio->opts;
|
||||
struct MPOpts *mpopts = da->opts;
|
||||
struct ad_lavc_param *opts = &mpopts->ad_lavc_param;
|
||||
AVCodecContext *lavc_context;
|
||||
AVCodec *lavc_codec;
|
||||
struct sh_stream *sh = da->header;
|
||||
struct sh_audio *sh_audio = sh->audio;
|
||||
|
||||
struct priv *ctx = talloc_zero(NULL, struct priv);
|
||||
sh_audio->context = ctx;
|
||||
da->priv = ctx;
|
||||
|
||||
if (sh_audio->wf && strcmp(decoder, "pcm") == 0) {
|
||||
decoder = find_pcm_decoder(tag_map, sh_audio->format,
|
||||
decoder = find_pcm_decoder(tag_map, sh->format,
|
||||
sh_audio->wf->wBitsPerSample);
|
||||
} else if (sh_audio->wf && strcmp(decoder, "mp-pcm") == 0) {
|
||||
decoder = find_pcm_decoder(af_map, sh_audio->format, 0);
|
||||
decoder = find_pcm_decoder(af_map, sh->format, 0);
|
||||
ctx->force_channel_map = true;
|
||||
}
|
||||
|
||||
lavc_codec = avcodec_find_decoder_by_name(decoder);
|
||||
if (!lavc_codec) {
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_ERR,
|
||||
"Cannot find codec '%s' in libavcodec...\n", decoder);
|
||||
uninit(sh_audio);
|
||||
MP_ERR(da, "Cannot find codec '%s' in libavcodec...\n", decoder);
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -238,9 +213,12 @@ static int init(sh_audio_t *sh_audio, const char *decoder)
|
||||
lavc_context->codec_id = lavc_codec->id;
|
||||
|
||||
if (opts->downmix) {
|
||||
lavc_context->request_channels = mpopts->audio_output_channels.num;
|
||||
lavc_context->request_channel_layout =
|
||||
mp_chmap_to_lavc(&mpopts->audio_output_channels);
|
||||
// Compatibility for Libav 9
|
||||
av_opt_set_int(lavc_context, "request_channels",
|
||||
mpopts->audio_output_channels.num,
|
||||
AV_OPT_SEARCH_CHILDREN);
|
||||
}
|
||||
|
||||
// Always try to set - option only exists for AC3 at the moment
|
||||
@@ -249,14 +227,13 @@ static int init(sh_audio_t *sh_audio, const char *decoder)
|
||||
|
||||
if (opts->avopt) {
|
||||
if (parse_avopts(lavc_context, opts->avopt) < 0) {
|
||||
mp_msg(MSGT_DECVIDEO, MSGL_ERR,
|
||||
"ad_lavc: setting AVOptions '%s' failed.\n", opts->avopt);
|
||||
uninit(sh_audio);
|
||||
MP_ERR(da, "setting AVOptions '%s' failed.\n", opts->avopt);
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
lavc_context->codec_tag = sh_audio->format;
|
||||
lavc_context->codec_tag = sh->format;
|
||||
lavc_context->sample_rate = sh_audio->samplerate;
|
||||
lavc_context->bit_rate = sh_audio->i_bps * 8;
|
||||
lavc_context->channel_layout = mp_chmap_to_lavc(&sh_audio->channels);
|
||||
@@ -274,72 +251,64 @@ static int init(sh_audio_t *sh_audio, const char *decoder)
|
||||
lavc_context->extradata_size);
|
||||
}
|
||||
|
||||
if (sh_audio->gsh->lav_headers)
|
||||
mp_copy_lav_codec_headers(lavc_context, sh_audio->gsh->lav_headers);
|
||||
if (sh->lav_headers)
|
||||
mp_copy_lav_codec_headers(lavc_context, sh->lav_headers);
|
||||
|
||||
mp_set_avcodec_threads(lavc_context, opts->threads);
|
||||
|
||||
/* open it */
|
||||
if (avcodec_open2(lavc_context, lavc_codec, NULL) < 0) {
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, "Could not open codec.\n");
|
||||
uninit(sh_audio);
|
||||
MP_ERR(da, "Could not open codec.\n");
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "INFO: libavcodec \"%s\" init OK!\n",
|
||||
MP_VERBOSE(da, "INFO: libavcodec \"%s\" init OK!\n",
|
||||
lavc_codec->name);
|
||||
|
||||
// Decode at least 1 byte: (to get header filled)
|
||||
for (int tries = 0;;) {
|
||||
int x = decode_audio(sh_audio, sh_audio->a_buffer, 1,
|
||||
sh_audio->a_buffer_size);
|
||||
if (x > 0) {
|
||||
sh_audio->a_buffer_len = x;
|
||||
// Decode at least 1 sample: (to get header filled)
|
||||
for (int tries = 1; ; tries++) {
|
||||
int x = decode_new_packet(da);
|
||||
if (x >= 0 && ctx->frame.samples > 0) {
|
||||
MP_VERBOSE(da, "Initial decode succeeded after %d packets.\n", tries);
|
||||
break;
|
||||
}
|
||||
if (++tries >= 5) {
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR,
|
||||
"ad_lavc: initial decode failed\n");
|
||||
uninit(sh_audio);
|
||||
if (tries >= 50) {
|
||||
MP_ERR(da, "initial decode failed\n");
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sh_audio->i_bps = lavc_context->bit_rate / 8;
|
||||
da->i_bps = lavc_context->bit_rate / 8;
|
||||
if (sh_audio->wf && sh_audio->wf->nAvgBytesPerSec)
|
||||
sh_audio->i_bps = sh_audio->wf->nAvgBytesPerSec;
|
||||
da->i_bps = sh_audio->wf->nAvgBytesPerSec;
|
||||
|
||||
int af_sample_fmt =
|
||||
af_from_avformat(av_get_packed_sample_fmt(lavc_context->sample_fmt));
|
||||
if (af_sample_fmt == AF_FORMAT_UNKNOWN) {
|
||||
uninit(sh_audio);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void uninit(sh_audio_t *sh)
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
struct priv *ctx = sh->context;
|
||||
struct priv *ctx = da->priv;
|
||||
if (!ctx)
|
||||
return;
|
||||
AVCodecContext *lavc_context = ctx->avctx;
|
||||
|
||||
if (lavc_context) {
|
||||
if (avcodec_close(lavc_context) < 0)
|
||||
mp_tmsg(MSGT_DECVIDEO, MSGL_ERR, "Could not close codec.\n");
|
||||
MP_ERR(da, "Could not close codec.\n");
|
||||
av_freep(&lavc_context->extradata);
|
||||
av_freep(&lavc_context);
|
||||
}
|
||||
avcodec_free_frame(&ctx->avframe);
|
||||
talloc_free(ctx);
|
||||
sh->context = NULL;
|
||||
}
|
||||
|
||||
static int control(sh_audio_t *sh, int cmd, void *arg)
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct priv *ctx = sh->context;
|
||||
struct priv *ctx = da->priv;
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESYNC_STREAM:
|
||||
case ADCTRL_RESET:
|
||||
avcodec_flush_buffers(ctx->avctx);
|
||||
ctx->output_left = 0;
|
||||
ctx->frame.samples = 0;
|
||||
talloc_free(ctx->packet);
|
||||
ctx->packet = NULL;
|
||||
return CONTROL_TRUE;
|
||||
@@ -347,121 +316,92 @@ static int control(sh_audio_t *sh, int cmd, void *arg)
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static av_always_inline void deplanarize(struct sh_audio *sh)
|
||||
static int decode_new_packet(struct dec_audio *da)
|
||||
{
|
||||
struct priv *priv = sh->context;
|
||||
|
||||
uint8_t **planes = priv->avframe->extended_data;
|
||||
size_t bps = av_get_bytes_per_sample(priv->avctx->sample_fmt);
|
||||
size_t nb_samples = priv->avframe->nb_samples;
|
||||
size_t channels = priv->avctx->channels;
|
||||
size_t size = bps * nb_samples * channels;
|
||||
|
||||
if (talloc_get_size(priv->output_packed) != size)
|
||||
priv->output_packed =
|
||||
talloc_realloc_size(priv, priv->output_packed, size);
|
||||
|
||||
reorder_to_packed(priv->output_packed, planes, bps, channels, nb_samples);
|
||||
|
||||
priv->output = priv->output_packed;
|
||||
}
|
||||
|
||||
static int decode_new_packet(struct sh_audio *sh)
|
||||
{
|
||||
struct priv *priv = sh->context;
|
||||
struct priv *priv = da->priv;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
|
||||
priv->frame.samples = 0;
|
||||
|
||||
struct demux_packet *mpkt = priv->packet;
|
||||
if (!mpkt)
|
||||
mpkt = demux_read_packet(sh->gsh);
|
||||
if (!mpkt)
|
||||
return -1; // error or EOF
|
||||
mpkt = demux_read_packet(da->header);
|
||||
|
||||
priv->packet = talloc_steal(priv, mpkt);
|
||||
|
||||
int in_len = mpkt->len;
|
||||
int in_len = mpkt ? mpkt->len : 0;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt);
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
|
||||
if (mpkt->pts != MP_NOPTS_VALUE) {
|
||||
sh->pts = mpkt->pts;
|
||||
sh->pts_bytes = 0;
|
||||
// If we don't have a PTS yet, use the first packet PTS we can get.
|
||||
if (da->pts == MP_NOPTS_VALUE && mpkt && mpkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = mpkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
int got_frame = 0;
|
||||
int ret = avcodec_decode_audio4(avctx, priv->avframe, &got_frame, &pkt);
|
||||
// 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
|
||||
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
|
||||
}
|
||||
if (mpkt->len == 0 || ret < 0) {
|
||||
talloc_free(mpkt);
|
||||
priv->packet = NULL;
|
||||
}
|
||||
// LATM may need many packets to find mux info
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
return 0;
|
||||
}
|
||||
if (mpkt->len == 0 || ret < 0) {
|
||||
talloc_free(mpkt);
|
||||
priv->packet = NULL;
|
||||
}
|
||||
// LATM may need many packets to find mux info
|
||||
if (ret == AVERROR(EAGAIN))
|
||||
return 0;
|
||||
if (ret < 0) {
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "lavc_audio: error\n");
|
||||
MP_VERBOSE(da, "lavc_audio: error\n");
|
||||
return -1;
|
||||
}
|
||||
if (!got_frame)
|
||||
return 0;
|
||||
uint64_t unitsize = (uint64_t)av_get_bytes_per_sample(avctx->sample_fmt) *
|
||||
avctx->channels;
|
||||
if (unitsize > 100000)
|
||||
abort();
|
||||
priv->unitsize = unitsize;
|
||||
uint64_t output_left = unitsize * priv->avframe->nb_samples;
|
||||
if (output_left > 500000000)
|
||||
abort();
|
||||
priv->output_left = output_left;
|
||||
if (av_sample_fmt_is_planar(avctx->sample_fmt) && avctx->channels > 1) {
|
||||
deplanarize(sh);
|
||||
} else {
|
||||
priv->output = priv->avframe->data[0];
|
||||
return mpkt ? 0 : -1; // -1: eof
|
||||
|
||||
if (setup_format(da) < 0)
|
||||
return -1;
|
||||
|
||||
priv->frame.samples = priv->avframe->nb_samples;
|
||||
mp_audio_copy_config(&priv->frame, &da->decoded);
|
||||
for (int n = 0; n < priv->frame.num_planes; n++)
|
||||
priv->frame.planes[n] = priv->avframe->data[n];
|
||||
|
||||
double out_pts = mp_pts_from_av(priv->avframe->pkt_pts, NULL);
|
||||
if (out_pts != MP_NOPTS_VALUE) {
|
||||
da->pts = out_pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
mp_dbg(MSGT_DECAUDIO, MSGL_DBG2, "Decoded %d -> %d \n", in_len,
|
||||
priv->output_left);
|
||||
|
||||
MP_DBG(da, "Decoded %d -> %d samples\n", in_len,
|
||||
priv->frame.samples);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int decode_audio(sh_audio_t *sh_audio, unsigned char *buf, int minlen,
|
||||
int maxlen)
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
struct priv *priv = sh_audio->context;
|
||||
AVCodecContext *avctx = priv->avctx;
|
||||
struct priv *priv = da->priv;
|
||||
|
||||
int len = -1;
|
||||
while (len < minlen) {
|
||||
if (!priv->output_left) {
|
||||
if (decode_new_packet(sh_audio) < 0)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
if (setup_format(sh_audio, avctx))
|
||||
return len;
|
||||
int size = (minlen - len + priv->unitsize - 1);
|
||||
size -= size % priv->unitsize;
|
||||
size = FFMIN(size, priv->output_left);
|
||||
if (size > maxlen)
|
||||
abort();
|
||||
memcpy(buf, priv->output, size);
|
||||
priv->output += size;
|
||||
priv->output_left -= size;
|
||||
if (len < 0)
|
||||
len = size;
|
||||
else
|
||||
len += size;
|
||||
buf += size;
|
||||
maxlen -= size;
|
||||
sh_audio->pts_bytes += size;
|
||||
if (!priv->frame.samples) {
|
||||
if (decode_new_packet(da) < 0)
|
||||
return -1;
|
||||
}
|
||||
return len;
|
||||
|
||||
if (!mp_audio_config_equals(buffer, &priv->frame))
|
||||
return 0;
|
||||
|
||||
buffer->samples = MPMIN(priv->frame.samples, maxlen);
|
||||
mp_audio_copy(buffer, 0, &priv->frame, 0, buffer->samples);
|
||||
mp_audio_skip_samples(&priv->frame, buffer->samples);
|
||||
da->pts_offset += buffer->samples;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
@@ -474,7 +414,6 @@ static void add_decoders(struct mp_decoder_list *list)
|
||||
const struct ad_functions ad_lavc = {
|
||||
.name = "lavc",
|
||||
.add_decoders = add_decoders,
|
||||
.preinit = preinit,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
+220
-355
@@ -25,43 +25,40 @@
|
||||
#include "config.h"
|
||||
|
||||
#include "ad.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
/* Reducing the ifdeffery to two main variants:
|
||||
* 1. most compatible to any libmpg123 version
|
||||
* 2. fastest variant with recent libmpg123 (>=1.14)
|
||||
* Running variant 2 on older libmpg123 versions may work in
|
||||
* principle, but is not supported.
|
||||
* So, please leave the check for MPG123_API_VERSION there, m-kay?
|
||||
*/
|
||||
#include <mpg123.h>
|
||||
|
||||
/* Enable faster mode of operation with newer libmpg123, avoiding
|
||||
* unnecessary memcpy() calls. */
|
||||
#if (defined MPG123_API_VERSION) && (MPG123_API_VERSION >= 33)
|
||||
#define AD_MPG123_FRAMEWISE
|
||||
#if (defined MPG123_API_VERSION) && (MPG123_API_VERSION < 33)
|
||||
#error "This should not happen"
|
||||
#endif
|
||||
|
||||
/* Switch for updating bitrate info of VBR files. Not essential. */
|
||||
#define AD_MPG123_MEAN_BITRATE
|
||||
|
||||
struct ad_mpg123_context {
|
||||
mpg123_handle *handle;
|
||||
char new_format;
|
||||
#ifdef AD_MPG123_MEAN_BITRATE
|
||||
bool new_format;
|
||||
int sample_size;
|
||||
bool need_data;
|
||||
/* Running mean for bit rate, stream length estimation. */
|
||||
float mean_rate;
|
||||
unsigned int mean_count;
|
||||
/* Time delay for updates. */
|
||||
short delay;
|
||||
#endif
|
||||
/* If the stream is actually VBR. */
|
||||
char vbr;
|
||||
};
|
||||
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
|
||||
mpg123_close(con->handle);
|
||||
mpg123_delete(con->handle);
|
||||
mpg123_exit();
|
||||
}
|
||||
|
||||
/* This initializes libmpg123 and prepares the handle, including funky
|
||||
* parameters. */
|
||||
static int preinit(sh_audio_t *sh)
|
||||
static int preinit(struct dec_audio *da)
|
||||
{
|
||||
int err;
|
||||
struct ad_mpg123_context *con;
|
||||
@@ -72,8 +69,8 @@ static int preinit(sh_audio_t *sh)
|
||||
if (mpg123_init() != MPG123_OK)
|
||||
return 0;
|
||||
|
||||
sh->context = malloc(sizeof(struct ad_mpg123_context));
|
||||
con = sh->context;
|
||||
da->priv = talloc_zero(NULL, struct ad_mpg123_context);
|
||||
con = da->priv;
|
||||
/* Auto-choice of optimized decoder (first argument NULL). */
|
||||
con->handle = mpg123_new(NULL, &err);
|
||||
if (!con->handle)
|
||||
@@ -104,33 +101,147 @@ static int preinit(sh_audio_t *sh)
|
||||
/* Example for RVA choice (available since libmpg123 1.0.0):
|
||||
mpg123_param(con->handle, MPG123_RVA, MPG123_RVA_MIX, 0.0) */
|
||||
|
||||
#ifdef AD_MPG123_FRAMEWISE
|
||||
/* Prevent funky automatic resampling.
|
||||
* This way, we can be sure that one frame will never produce
|
||||
* more than 1152 stereo samples. */
|
||||
* more than 1152 stereo samples.
|
||||
* Background:
|
||||
* Going to decode directly to the output buffer. It is important to have
|
||||
* MPG123_AUTO_RESAMPLE disabled for the buffer size being an all-time
|
||||
* limit.
|
||||
* We need at least 1152 samples. dec_audio.c normally guarantees this. */
|
||||
mpg123_param(con->handle, MPG123_REMOVE_FLAGS, MPG123_AUTO_RESAMPLE, 0.);
|
||||
#else
|
||||
/* Older mpg123 is vulnerable to concatenated streams when gapless cutting
|
||||
* is enabled (will only play the jingle of a badly constructed radio
|
||||
* stream). The versions using framewise decoding are fine with that. */
|
||||
mpg123_param(con->handle, MPG123_REMOVE_FLAGS, MPG123_GAPLESS, 0.);
|
||||
#endif
|
||||
|
||||
return 1;
|
||||
|
||||
bad_end:
|
||||
if (!con->handle)
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 preinit error: %s\n",
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n",
|
||||
mpg123_plain_strerror(err));
|
||||
else
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 preinit error: %s\n",
|
||||
MP_ERR(da, "mpg123 preinit error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
|
||||
if (con->handle)
|
||||
mpg123_delete(con->handle);
|
||||
mpg123_exit();
|
||||
free(sh->context);
|
||||
sh->context = NULL;
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int mpg123_format_to_af(int mpg123_encoding)
|
||||
{
|
||||
/* Without external force, mpg123 will always choose signed encoding,
|
||||
* and non-16-bit only on builds that don't support it.
|
||||
* Be reminded that it doesn't matter to the MPEG file what encoding
|
||||
* is produced from it. */
|
||||
switch (mpg123_encoding) {
|
||||
case MPG123_ENC_SIGNED_8: return AF_FORMAT_S8;
|
||||
case MPG123_ENC_SIGNED_16: return AF_FORMAT_S16;
|
||||
case MPG123_ENC_SIGNED_32: return AF_FORMAT_S32;
|
||||
case MPG123_ENC_FLOAT_32: return AF_FORMAT_FLOAT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* libmpg123 has a new format ready; query and store, return return value
|
||||
of mpg123_getformat() */
|
||||
static int set_format(struct dec_audio *da)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
long rate;
|
||||
int channels;
|
||||
int encoding;
|
||||
ret = mpg123_getformat(con->handle, &rate, &channels, &encoding);
|
||||
if (ret == MPG123_OK) {
|
||||
mp_audio_set_num_channels(&da->decoded, channels);
|
||||
da->decoded.rate = rate;
|
||||
int af = mpg123_format_to_af(encoding);
|
||||
if (!af) {
|
||||
/* This means we got a funny custom build of libmpg123 that only supports an unknown format. */
|
||||
MP_ERR(da, "Bad encoding from mpg123: %i.\n", encoding);
|
||||
return MPG123_ERR;
|
||||
}
|
||||
mp_audio_set_format(&da->decoded, af);
|
||||
con->sample_size = channels * (af_fmt2bits(af) / 8);
|
||||
con->new_format = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int feed_new_packet(struct dec_audio *da)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
|
||||
struct demux_packet *pkt = demux_read_packet(da->header);
|
||||
if (!pkt)
|
||||
return -1; /* EOF. */
|
||||
|
||||
/* Next bytes from that presentation time. */
|
||||
if (pkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = pkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
|
||||
/* Have to use mpg123_feed() to avoid decoding here. */
|
||||
ret = mpg123_feed(con->handle, pkt->buffer, pkt->len);
|
||||
talloc_free(pkt);
|
||||
|
||||
if (ret == MPG123_ERR)
|
||||
return -1;
|
||||
|
||||
if (ret == MPG123_NEW_FORMAT)
|
||||
con->new_format = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Now we really start accessing some data and determining file format.
|
||||
* Format now is allowed to change on-the-fly. Here is the only point
|
||||
* that has MPlayer react to errors. We have to pray that exceptional
|
||||
* erros in other places simply cannot occur. */
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
if (!preinit(da))
|
||||
return 0;
|
||||
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
int ret;
|
||||
|
||||
ret = mpg123_open_feed(con->handle);
|
||||
if (ret != MPG123_OK)
|
||||
goto fail;
|
||||
|
||||
for (int n = 0; ; n++) {
|
||||
if (feed_new_packet(da) < 0) {
|
||||
ret = MPG123_NEED_MORE;
|
||||
goto fail;
|
||||
}
|
||||
size_t got_now = 0;
|
||||
ret = mpg123_decode_frame(con->handle, NULL, NULL, &got_now);
|
||||
if (ret == MPG123_OK || ret == MPG123_NEW_FORMAT) {
|
||||
ret = set_format(da);
|
||||
if (ret == MPG123_OK)
|
||||
break;
|
||||
}
|
||||
if (ret != MPG123_NEED_MORE)
|
||||
goto fail;
|
||||
// max. 16 retries (randomly chosen number)
|
||||
if (n > 16) {
|
||||
ret = MPG123_NEED_MORE;
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
if (ret == MPG123_NEED_MORE) {
|
||||
MP_ERR(da, "Could not find mp3 stream.\n");
|
||||
} else {
|
||||
MP_ERR(da, "mpg123 init error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
}
|
||||
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -147,348 +258,103 @@ static int compute_bitrate(struct mpg123_frameinfo *i)
|
||||
samples_per_frame[i->version][i->layer] + 0.5);
|
||||
}
|
||||
|
||||
/* Opted against the header printout from old mp3lib, too much
|
||||
* irrelevant info. This is modelled after the mpg123 app's
|
||||
* standard output line.
|
||||
* If more verbosity is demanded, one can add more detail and
|
||||
* also throw in ID3v2 info which libmpg123 collects anyway. */
|
||||
static void print_header_compact(struct mpg123_frameinfo *i)
|
||||
{
|
||||
static const char *smodes[5] = {
|
||||
"stereo", "joint-stereo", "dual-channel", "mono", "invalid"
|
||||
};
|
||||
static const char *layers[4] = {
|
||||
"Unknown", "I", "II", "III"
|
||||
};
|
||||
static const char *versions[4] = {
|
||||
"1.0", "2.0", "2.5", "x.x"
|
||||
};
|
||||
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "MPEG %s layer %s, ",
|
||||
versions[i->version], layers[i->layer]);
|
||||
switch (i->vbr) {
|
||||
case MPG123_CBR:
|
||||
if (i->bitrate)
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "%d kbit/s", i->bitrate);
|
||||
else
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "%d kbit/s (free format)",
|
||||
compute_bitrate(i));
|
||||
break;
|
||||
case MPG123_VBR:
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "VBR");
|
||||
break;
|
||||
case MPG123_ABR:
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "%d kbit/s ABR", i->abr_rate);
|
||||
break;
|
||||
default:
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "???");
|
||||
}
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, ", %ld Hz %s\n", i->rate,
|
||||
smodes[i->mode]);
|
||||
}
|
||||
|
||||
/* libmpg123 has a new format ready; query and store, return return value
|
||||
of mpg123_getformat() */
|
||||
static int set_format(sh_audio_t *sh, struct ad_mpg123_context *con)
|
||||
{
|
||||
int ret;
|
||||
long rate;
|
||||
int channels;
|
||||
int encoding;
|
||||
ret = mpg123_getformat(con->handle, &rate, &channels, &encoding);
|
||||
if(ret == MPG123_OK) {
|
||||
mp_chmap_from_channels(&sh->channels, channels);
|
||||
sh->samplerate = rate;
|
||||
/* Without external force, mpg123 will always choose signed encoding,
|
||||
* and non-16-bit only on builds that don't support it.
|
||||
* Be reminded that it doesn't matter to the MPEG file what encoding
|
||||
* is produced from it. */
|
||||
switch (encoding) {
|
||||
case MPG123_ENC_SIGNED_8:
|
||||
sh->sample_format = AF_FORMAT_S8;
|
||||
sh->samplesize = 1;
|
||||
break;
|
||||
case MPG123_ENC_SIGNED_16:
|
||||
sh->sample_format = AF_FORMAT_S16_NE;
|
||||
sh->samplesize = 2;
|
||||
break;
|
||||
/* To stay compatible with the oldest libmpg123 headers, do not rely
|
||||
* on float and 32 bit encoding symbols being defined.
|
||||
* Those formats came later */
|
||||
case 0x1180: /* MPG123_ENC_SIGNED_32 */
|
||||
sh->sample_format = AF_FORMAT_S32_NE;
|
||||
sh->samplesize = 4;
|
||||
break;
|
||||
case 0x200: /* MPG123_ENC_FLOAT_32 */
|
||||
sh->sample_format = AF_FORMAT_FLOAT_NE;
|
||||
sh->samplesize = 4;
|
||||
break;
|
||||
default:
|
||||
/* This means we got a funny custom build of libmpg123 that only supports an unknown format. */
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR,
|
||||
"Bad encoding from mpg123: %i.\n", encoding);
|
||||
return MPG123_ERR;
|
||||
}
|
||||
#ifdef AD_MPG123_FRAMEWISE
|
||||
/* Going to decode directly to MPlayer's memory. It is important
|
||||
* to have MPG123_AUTO_RESAMPLE disabled for the buffer size
|
||||
* being an all-time limit. */
|
||||
sh->audio_out_minsize = 1152 * 2 * sh->samplesize;
|
||||
#endif
|
||||
con->new_format = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* This tries to extract a requested amount of decoded data.
|
||||
* Even when you request 0 bytes, it will feed enough input so that
|
||||
* the decoder _could_ have delivered something.
|
||||
* Returns byte count >= 0, -1 on error.
|
||||
*
|
||||
* Thoughts on exact pts keeping:
|
||||
* We have to assume that MPEG frames are cut in pieces by packet boundaries.
|
||||
* Also, it might be possible that the first packet does not contain enough
|
||||
* data to ensure initial stream sync... or re-sync on erroneous streams.
|
||||
* So we need something robust to relate the decoded byte count to the correct
|
||||
* time stamp. This is tricky, though. From the outside, you cannot tell if,
|
||||
* after having fed two packets until the first output arrives, one should
|
||||
* start counting from the first packet's pts or the second packet's.
|
||||
* So, let's just count from the last fed package's pts. If the packets are
|
||||
* exactly cut to MPEG frames, this will cause one frame mismatch in the
|
||||
* beginning (when mpg123 peeks ahead for the following header), but will
|
||||
* be corrected with the third frame already. One might add special code to
|
||||
* not increment the base pts past the first packet's after a resync before
|
||||
* the first decoded bytes arrived. */
|
||||
static int decode_a_bit(sh_audio_t *sh, unsigned char *buf, int count)
|
||||
{
|
||||
int ret = MPG123_OK;
|
||||
int got = 0;
|
||||
struct ad_mpg123_context *con = sh->context;
|
||||
|
||||
/* There will be one MPG123_NEW_FORMAT message on first open.
|
||||
* This will be handled in init(). */
|
||||
do {
|
||||
size_t got_now = 0;
|
||||
/* Fetch new format now, after old data has been used. */
|
||||
if(con->new_format)
|
||||
ret = set_format(sh, con);
|
||||
|
||||
/* Feed the decoder. This will only fire from the second round on. */
|
||||
if (ret == MPG123_NEED_MORE) {
|
||||
/* Feed more input data. */
|
||||
struct demux_packet *pkt = demux_read_packet(sh->gsh);
|
||||
if (!pkt)
|
||||
break; /* Apparently that's it. EOF. */
|
||||
|
||||
/* Next bytes from that presentation time. */
|
||||
if (pkt->pts != MP_NOPTS_VALUE) {
|
||||
sh->pts = pkt->pts;
|
||||
sh->pts_bytes = 0;
|
||||
}
|
||||
|
||||
#ifdef AD_MPG123_FRAMEWISE
|
||||
/* Have to use mpg123_feed() to avoid decoding here. */
|
||||
ret = mpg123_feed(con->handle, pkt->buffer, pkt->len);
|
||||
#else
|
||||
/* Do not use mpg123_feed(), added in later libmpg123 versions. */
|
||||
ret = mpg123_decode(con->handle, pkt->buffer, pkt->len, NULL, 0, NULL);
|
||||
#endif
|
||||
talloc_free(pkt);
|
||||
if (ret == MPG123_ERR)
|
||||
break;
|
||||
|
||||
/* Indication of format change is possible here (from mpg123_decode()). */
|
||||
if(ret == MPG123_NEW_FORMAT) {
|
||||
con->new_format = 1;
|
||||
if(got)
|
||||
break; /* Do not switch format during a chunk. */
|
||||
|
||||
ret = set_format(sh, con);
|
||||
}
|
||||
}
|
||||
/* Theoretically, mpg123 could return MPG123_DONE, so be prepared.
|
||||
* Should not happen in our usage, but it is a valid return code. */
|
||||
else if (ret == MPG123_ERR || ret == MPG123_DONE)
|
||||
break;
|
||||
|
||||
/* Try to decode a bit. This is the return value that counts
|
||||
* for the loop condition. */
|
||||
#ifdef AD_MPG123_FRAMEWISE
|
||||
if (!buf) { /* fake call just for feeding to get format */
|
||||
ret = set_format(sh, con);
|
||||
} else { /* This is the decoding. One frame at a time. */
|
||||
ret = mpg123_replace_buffer(con->handle, buf, count);
|
||||
if (ret == MPG123_OK)
|
||||
ret = mpg123_decode_frame(con->handle, NULL, NULL, &got_now);
|
||||
}
|
||||
#else
|
||||
ret = mpg123_decode(con->handle, NULL, 0, buf + got, count - got,
|
||||
&got_now);
|
||||
#endif
|
||||
|
||||
got += got_now;
|
||||
sh->pts_bytes += got_now;
|
||||
|
||||
/* Indication of format change should happen here. */
|
||||
if(ret == MPG123_NEW_FORMAT) {
|
||||
con->new_format = 1;
|
||||
if(got)
|
||||
break; /* Do not switch format during a chunk. */
|
||||
|
||||
ret = set_format(sh, con);
|
||||
}
|
||||
|
||||
#ifdef AD_MPG123_FRAMEWISE
|
||||
} while (ret == MPG123_NEED_MORE || (got == 0 && count != 0));
|
||||
#else
|
||||
} while (ret == MPG123_NEED_MORE || got < count);
|
||||
#endif
|
||||
|
||||
if (ret == MPG123_ERR) {
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 decoding failed: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
}
|
||||
|
||||
return got;
|
||||
}
|
||||
|
||||
/* Close, reopen stream. Feed data until we know the format of the stream.
|
||||
* 1 on success, 0 on error */
|
||||
static int reopen_stream(sh_audio_t *sh)
|
||||
{
|
||||
struct ad_mpg123_context *con = (struct ad_mpg123_context*) sh->context;
|
||||
|
||||
mpg123_close(con->handle);
|
||||
/* No resetting of the context:
|
||||
* We do not want to loose the mean bitrate data. */
|
||||
|
||||
/* Open and make sure we have fed enough data to get stream properties. */
|
||||
if (MPG123_OK == mpg123_open_feed(con->handle) &&
|
||||
/* Feed data until mpg123 is ready (has found stream beginning). */
|
||||
!decode_a_bit(sh, NULL, 0) &&
|
||||
set_format(sh, con) == MPG123_OK) { /* format setting again just for return value */
|
||||
return 1;
|
||||
} else {
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR,
|
||||
"mpg123 failed to reopen stream: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now we really start accessing some data and determining file format.
|
||||
* Format now is allowed to change on-the-fly. Here is the only point
|
||||
* that has MPlayer react to errors. We have to pray that exceptional
|
||||
* erros in other places simply cannot occur. */
|
||||
static int init(sh_audio_t *sh, const char *decoder)
|
||||
{
|
||||
mpg123_id3v2 *v2;
|
||||
struct mpg123_frameinfo finfo;
|
||||
struct ad_mpg123_context *con = sh->context;
|
||||
|
||||
con->new_format = 0;
|
||||
if (reopen_stream(sh) &&
|
||||
/* Get MPEG header info. */
|
||||
MPG123_OK == mpg123_info(con->handle, &finfo) &&
|
||||
/* Since we queried format, mpg123 should have read past ID3v2 tags.
|
||||
* We need to decide if printing of UTF-8 encoded text info is wanted. */
|
||||
MPG123_OK == mpg123_id3(con->handle, NULL, &v2)) {
|
||||
/* If we are here, we passed all hurdles. Yay! Extract the info. */
|
||||
print_header_compact(&finfo);
|
||||
/* Do we want to print out the UTF-8 Id3v2 info?
|
||||
if (v2)
|
||||
print_id3v2(v2); */
|
||||
|
||||
/* Have kb/s, want B/s
|
||||
* For VBR, the first frame will be a bad estimate. */
|
||||
sh->i_bps = (finfo.bitrate ? finfo.bitrate : compute_bitrate(&finfo))
|
||||
* 1000 / 8;
|
||||
#ifdef AD_MPG123_MEAN_BITRATE
|
||||
con->delay = 1;
|
||||
con->mean_rate = 0.;
|
||||
con->mean_count = 0;
|
||||
#endif
|
||||
con->vbr = (finfo.vbr != MPG123_CBR);
|
||||
|
||||
return 1;
|
||||
} else {
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR, "mpg123 init error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void uninit(sh_audio_t *sh)
|
||||
{
|
||||
struct ad_mpg123_context *con = (struct ad_mpg123_context*) sh->context;
|
||||
|
||||
mpg123_close(con->handle);
|
||||
mpg123_delete(con->handle);
|
||||
free(sh->context);
|
||||
sh->context = NULL;
|
||||
mpg123_exit();
|
||||
}
|
||||
|
||||
#ifdef AD_MPG123_MEAN_BITRATE
|
||||
/* Update mean bitrate. This could be dropped if accurate time display
|
||||
* on audio file playback is not desired. */
|
||||
static void update_info(sh_audio_t *sh)
|
||||
static void update_info(struct dec_audio *da)
|
||||
{
|
||||
struct ad_mpg123_context *con = sh->context;
|
||||
if (con->vbr && --con->delay < 1) {
|
||||
struct mpg123_frameinfo finfo;
|
||||
if (MPG123_OK == mpg123_info(con->handle, &finfo)) {
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
struct mpg123_frameinfo finfo;
|
||||
if (mpg123_info(con->handle, &finfo) != MPG123_OK)
|
||||
return;
|
||||
|
||||
if (finfo.vbr != MPG123_CBR) {
|
||||
if (--con->delay < 1) {
|
||||
if (++con->mean_count > ((unsigned int) -1) / 2)
|
||||
con->mean_count = ((unsigned int) -1) / 4;
|
||||
|
||||
/* Might not be numerically optimal, but works fine enough. */
|
||||
con->mean_rate = ((con->mean_count - 1) * con->mean_rate +
|
||||
finfo.bitrate) / con->mean_count;
|
||||
sh->i_bps = (int) (con->mean_rate * 1000 / 8);
|
||||
da->i_bps = (int) (con->mean_rate * 1000 / 8);
|
||||
|
||||
con->delay = 10;
|
||||
}
|
||||
} else {
|
||||
da->i_bps = (finfo.bitrate ? finfo.bitrate : compute_bitrate(&finfo))
|
||||
* 1000 / 8;
|
||||
con->delay = 1;
|
||||
con->mean_rate = 0.;
|
||||
con->mean_count = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int decode_audio(sh_audio_t *sh, unsigned char *buf, int minlen,
|
||||
int maxlen)
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
int bytes;
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
void *buf = buffer->planes[0];
|
||||
int ret;
|
||||
|
||||
bytes = decode_a_bit(sh, buf, maxlen);
|
||||
/* This EOF is ignored, apparently, until input data is exhausted. */
|
||||
if (bytes == 0)
|
||||
return -1; /* EOF */
|
||||
if (con->new_format) {
|
||||
ret = set_format(da);
|
||||
if (ret == MPG123_OK) {
|
||||
return 0; // let caller handle format change
|
||||
} else if (ret == MPG123_NEED_MORE) {
|
||||
con->need_data = true;
|
||||
} else {
|
||||
goto mpg123_fail;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef AD_MPG123_MEAN_BITRATE
|
||||
update_info(sh);
|
||||
#endif
|
||||
return bytes;
|
||||
if (con->need_data) {
|
||||
if (feed_new_packet(da) < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mp_audio_config_equals(&da->decoded, buffer))
|
||||
return 0;
|
||||
|
||||
size_t got_now = 0;
|
||||
ret = mpg123_replace_buffer(con->handle, buf, maxlen * con->sample_size);
|
||||
if (ret != MPG123_OK)
|
||||
goto mpg123_fail;
|
||||
|
||||
ret = mpg123_decode_frame(con->handle, NULL, NULL, &got_now);
|
||||
|
||||
int got_samples = got_now / con->sample_size;
|
||||
buffer->samples += got_samples;
|
||||
da->pts_offset += got_samples;
|
||||
|
||||
if (ret == MPG123_NEW_FORMAT) {
|
||||
con->new_format = true;
|
||||
} else if (ret == MPG123_NEED_MORE) {
|
||||
con->need_data = true;
|
||||
} else if (ret != MPG123_OK && ret != MPG123_DONE) {
|
||||
goto mpg123_fail;
|
||||
}
|
||||
|
||||
update_info(da);
|
||||
return 0;
|
||||
|
||||
mpg123_fail:
|
||||
MP_ERR(da, "mpg123 decoding error: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int control(sh_audio_t *sh, int cmd, void *arg)
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
struct ad_mpg123_context *con = da->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case ADCTRL_RESYNC_STREAM:
|
||||
/* Close/reopen the stream for mpg123 to make sure it doesn't
|
||||
* think that it still knows the exact stream position.
|
||||
* Otherwise, we would have funny effects from the gapless code.
|
||||
* Oh, and it helps to minimize artifacts from jumping in the stream. */
|
||||
if (reopen_stream(sh)) {
|
||||
#ifdef AD_MPG123_MEAN_BITRATE
|
||||
update_info(sh);
|
||||
#endif
|
||||
return CONTROL_TRUE;
|
||||
} else {
|
||||
/* MPlayer ignores this case! It just keeps on decoding.
|
||||
* So we have to make sure resync never fails ... */
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR,
|
||||
"mpg123 cannot reopen stream for resync.\n");
|
||||
case ADCTRL_RESET:
|
||||
mpg123_close(con->handle);
|
||||
|
||||
if (mpg123_open_feed(con->handle) != MPG123_OK) {
|
||||
MP_ERR(da, "mpg123 failed to reopen stream: %s\n",
|
||||
mpg123_strerror(con->handle));
|
||||
return CONTROL_FALSE;
|
||||
}
|
||||
break;
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
@@ -502,7 +368,6 @@ static void add_decoders(struct mp_decoder_list *list)
|
||||
const struct ad_functions ad_mpg123 = {
|
||||
.name = "mpg123",
|
||||
.add_decoders = add_decoders,
|
||||
.preinit = preinit,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
+178
-200
@@ -19,60 +19,214 @@
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "mpvcore/av_common.h"
|
||||
#include "mpvcore/options.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/av_common.h"
|
||||
#include "options/options.h"
|
||||
#include "ad.h"
|
||||
|
||||
#define FILENAME_SPDIFENC "spdif"
|
||||
#define OUTBUF_SIZE 65536
|
||||
|
||||
struct spdifContext {
|
||||
struct mp_log *log;
|
||||
AVFormatContext *lavf_ctx;
|
||||
int iec61937_packet_size;
|
||||
int out_buffer_len;
|
||||
int out_buffer_size;
|
||||
uint8_t *out_buffer;
|
||||
uint8_t pb_buffer[OUTBUF_SIZE];
|
||||
bool need_close;
|
||||
};
|
||||
|
||||
static void uninit(sh_audio_t *sh);
|
||||
|
||||
static int read_packet(void *p, uint8_t *buf, int buf_size)
|
||||
{
|
||||
// spdifenc does not use read callback.
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_packet(void *p, uint8_t *buf, int buf_size)
|
||||
{
|
||||
int len;
|
||||
struct spdifContext *ctx = p;
|
||||
|
||||
len = FFMIN(buf_size, ctx->out_buffer_size -ctx->out_buffer_len);
|
||||
memcpy(&ctx->out_buffer[ctx->out_buffer_len], buf, len);
|
||||
ctx->out_buffer_len += len;
|
||||
return len;
|
||||
int buffer_left = ctx->out_buffer_size - ctx->out_buffer_len;
|
||||
if (buf_size > buffer_left) {
|
||||
MP_ERR(ctx, "spdif packet too large.\n");
|
||||
buf_size = buffer_left;
|
||||
}
|
||||
|
||||
memcpy(&ctx->out_buffer[ctx->out_buffer_len], buf, buf_size);
|
||||
ctx->out_buffer_len += buf_size;
|
||||
return buf_size;
|
||||
}
|
||||
|
||||
static int64_t seek(void *p, int64_t offset, int whence)
|
||||
static void uninit(struct dec_audio *da)
|
||||
{
|
||||
// spdifenc does not use seek callback.
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
|
||||
if (lavf_ctx) {
|
||||
if (spdif_ctx->need_close)
|
||||
av_write_trailer(lavf_ctx);
|
||||
if (lavf_ctx->pb)
|
||||
av_freep(&lavf_ctx->pb->buffer);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
avformat_free_context(lavf_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static int init(struct dec_audio *da, const char *decoder)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = talloc_zero(NULL, struct spdifContext);
|
||||
da->priv = spdif_ctx;
|
||||
spdif_ctx->log = da->log;
|
||||
|
||||
AVFormatContext *lavf_ctx = avformat_alloc_context();
|
||||
if (!lavf_ctx)
|
||||
goto fail;
|
||||
|
||||
lavf_ctx->oformat = av_guess_format("spdif", NULL, NULL);
|
||||
if (!lavf_ctx->oformat)
|
||||
goto fail;
|
||||
|
||||
spdif_ctx->lavf_ctx = lavf_ctx;
|
||||
|
||||
void *buffer = av_mallocz(OUTBUF_SIZE);
|
||||
if (!buffer)
|
||||
abort();
|
||||
lavf_ctx->pb = avio_alloc_context(buffer, OUTBUF_SIZE, 1, spdif_ctx, NULL,
|
||||
write_packet, NULL);
|
||||
if (!lavf_ctx->pb) {
|
||||
av_free(buffer);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// Request minimal buffering (not available on Libav)
|
||||
#if LIBAVFORMAT_VERSION_MICRO >= 100
|
||||
lavf_ctx->pb->direct = 1;
|
||||
#endif
|
||||
|
||||
AVStream *stream = avformat_new_stream(lavf_ctx, 0);
|
||||
if (!stream)
|
||||
goto fail;
|
||||
|
||||
stream->codec->codec_id = mp_codec_to_av_codec_id(decoder);
|
||||
|
||||
AVDictionary *format_opts = NULL;
|
||||
|
||||
int num_channels = 0;
|
||||
int sample_format = 0;
|
||||
int samplerate = 0;
|
||||
switch (stream->codec->codec_id) {
|
||||
case AV_CODEC_ID_AAC:
|
||||
spdif_ctx->iec61937_packet_size = 16384;
|
||||
sample_format = AF_FORMAT_IEC61937_LE;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_AC3:
|
||||
spdif_ctx->iec61937_packet_size = 6144;
|
||||
sample_format = AF_FORMAT_AC3_LE;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_DTS:
|
||||
if (da->opts->dtshd) {
|
||||
av_dict_set(&format_opts, "dtshd_rate", "768000", 0); // 4*192000
|
||||
spdif_ctx->iec61937_packet_size = 32768;
|
||||
sample_format = AF_FORMAT_IEC61937_LE;
|
||||
samplerate = 192000;
|
||||
num_channels = 2*4;
|
||||
} else {
|
||||
spdif_ctx->iec61937_packet_size = 32768;
|
||||
sample_format = AF_FORMAT_AC3_LE;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
}
|
||||
break;
|
||||
case AV_CODEC_ID_EAC3:
|
||||
spdif_ctx->iec61937_packet_size = 24576;
|
||||
sample_format = AF_FORMAT_IEC61937_LE;
|
||||
samplerate = 192000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_MP3:
|
||||
spdif_ctx->iec61937_packet_size = 4608;
|
||||
sample_format = AF_FORMAT_MPEG2;
|
||||
samplerate = 48000;
|
||||
num_channels = 2;
|
||||
break;
|
||||
case AV_CODEC_ID_TRUEHD:
|
||||
spdif_ctx->iec61937_packet_size = 61440;
|
||||
sample_format = AF_FORMAT_IEC61937_LE;
|
||||
samplerate = 192000;
|
||||
num_channels = 8;
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
mp_audio_set_num_channels(&da->decoded, num_channels);
|
||||
mp_audio_set_format(&da->decoded, sample_format);
|
||||
da->decoded.rate = samplerate;
|
||||
|
||||
if (avformat_write_header(lavf_ctx, &format_opts) < 0) {
|
||||
MP_FATAL(da, "libavformat spdif initialization failed.\n");
|
||||
av_dict_free(&format_opts);
|
||||
goto fail;
|
||||
}
|
||||
av_dict_free(&format_opts);
|
||||
|
||||
spdif_ctx->need_close = true;
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
uninit(da);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int preinit(sh_audio_t *sh)
|
||||
static int decode_audio(struct dec_audio *da, struct mp_audio *buffer, int maxlen)
|
||||
{
|
||||
sh->samplesize = 2;
|
||||
return 1;
|
||||
struct spdifContext *spdif_ctx = da->priv;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
|
||||
int sstride = 2 * da->decoded.channels.num;
|
||||
assert(sstride == buffer->sstride);
|
||||
|
||||
if (maxlen * sstride < spdif_ctx->iec61937_packet_size)
|
||||
return 0;
|
||||
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
spdif_ctx->out_buffer_size = maxlen * sstride;
|
||||
spdif_ctx->out_buffer = buffer->planes[0];
|
||||
|
||||
struct demux_packet *mpkt = demux_read_packet(da->header);
|
||||
if (!mpkt)
|
||||
return -1;
|
||||
|
||||
AVPacket pkt;
|
||||
mp_set_av_packet(&pkt, mpkt, NULL);
|
||||
pkt.pts = pkt.dts = 0;
|
||||
MP_VERBOSE(da, "spdif packet, size=%d\n", pkt.size);
|
||||
if (mpkt->pts != MP_NOPTS_VALUE) {
|
||||
da->pts = mpkt->pts;
|
||||
da->pts_offset = 0;
|
||||
}
|
||||
int ret = av_write_frame(lavf_ctx, &pkt);
|
||||
avio_flush(lavf_ctx->pb);
|
||||
buffer->samples = spdif_ctx->out_buffer_len / sstride;
|
||||
da->pts_offset += buffer->samples;
|
||||
talloc_free(mpkt);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int codecs[] = {
|
||||
static int control(struct dec_audio *da, int cmd, void *arg)
|
||||
{
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static const int codecs[] = {
|
||||
AV_CODEC_ID_AAC,
|
||||
AV_CODEC_ID_AC3,
|
||||
AV_CODEC_ID_DTS,
|
||||
@@ -82,181 +236,6 @@ static int codecs[] = {
|
||||
AV_CODEC_ID_NONE
|
||||
};
|
||||
|
||||
static int init(sh_audio_t *sh, const char *decoder)
|
||||
{
|
||||
int srate, bps, *dtshd_rate;
|
||||
AVFormatContext *lavf_ctx = NULL;
|
||||
AVStream *stream = NULL;
|
||||
const AVOption *opt = NULL;
|
||||
struct spdifContext *spdif_ctx = NULL;
|
||||
|
||||
spdif_ctx = av_mallocz(sizeof(*spdif_ctx));
|
||||
if (!spdif_ctx)
|
||||
goto fail;
|
||||
spdif_ctx->lavf_ctx = avformat_alloc_context();
|
||||
if (!spdif_ctx->lavf_ctx)
|
||||
goto fail;
|
||||
|
||||
sh->context = spdif_ctx;
|
||||
lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
|
||||
lavf_ctx->oformat = av_guess_format(FILENAME_SPDIFENC, NULL, NULL);
|
||||
if (!lavf_ctx->oformat)
|
||||
goto fail;
|
||||
lavf_ctx->priv_data = av_mallocz(lavf_ctx->oformat->priv_data_size);
|
||||
if (!lavf_ctx->priv_data)
|
||||
goto fail;
|
||||
lavf_ctx->pb = avio_alloc_context(spdif_ctx->pb_buffer, OUTBUF_SIZE, 1, spdif_ctx,
|
||||
read_packet, write_packet, seek);
|
||||
if (!lavf_ctx->pb)
|
||||
goto fail;
|
||||
stream = avformat_new_stream(lavf_ctx, 0);
|
||||
if (!stream)
|
||||
goto fail;
|
||||
lavf_ctx->duration = AV_NOPTS_VALUE;
|
||||
lavf_ctx->start_time = AV_NOPTS_VALUE;
|
||||
lavf_ctx->streams[0]->codec->codec_id = mp_codec_to_av_codec_id(decoder);
|
||||
lavf_ctx->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE;
|
||||
if (AVERROR_PATCHWELCOME == lavf_ctx->oformat->write_header(lavf_ctx)) {
|
||||
mp_msg(MSGT_DECAUDIO,MSGL_INFO,
|
||||
"This codec is not supported by spdifenc.\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
srate = 48000; //fake value
|
||||
bps = 768000/8; //fake value
|
||||
|
||||
int num_channels = 0;
|
||||
switch (lavf_ctx->streams[0]->codec->codec_id) {
|
||||
case AV_CODEC_ID_AAC:
|
||||
spdif_ctx->iec61937_packet_size = 16384;
|
||||
sh->sample_format = AF_FORMAT_IEC61937_LE;
|
||||
sh->samplerate = srate;
|
||||
num_channels = 2;
|
||||
sh->i_bps = bps;
|
||||
break;
|
||||
case AV_CODEC_ID_AC3:
|
||||
spdif_ctx->iec61937_packet_size = 6144;
|
||||
sh->sample_format = AF_FORMAT_AC3_LE;
|
||||
sh->samplerate = srate;
|
||||
num_channels = 2;
|
||||
sh->i_bps = bps;
|
||||
break;
|
||||
case AV_CODEC_ID_DTS:
|
||||
if(sh->opts->dtshd) {
|
||||
opt = av_opt_find(&lavf_ctx->oformat->priv_class,
|
||||
"dtshd_rate", NULL, 0, 0);
|
||||
if (!opt)
|
||||
goto fail;
|
||||
dtshd_rate = (int*)(((uint8_t*)lavf_ctx->priv_data) +
|
||||
opt->offset);
|
||||
*dtshd_rate = 192000*4;
|
||||
spdif_ctx->iec61937_packet_size = 32768;
|
||||
sh->sample_format = AF_FORMAT_IEC61937_LE;
|
||||
sh->samplerate = 192000; // DTS core require 48000
|
||||
num_channels = 2*4;
|
||||
sh->i_bps = bps;
|
||||
} else {
|
||||
spdif_ctx->iec61937_packet_size = 32768;
|
||||
sh->sample_format = AF_FORMAT_AC3_LE;
|
||||
sh->samplerate = srate;
|
||||
num_channels = 2;
|
||||
sh->i_bps = bps;
|
||||
}
|
||||
break;
|
||||
case AV_CODEC_ID_EAC3:
|
||||
spdif_ctx->iec61937_packet_size = 24576;
|
||||
sh->sample_format = AF_FORMAT_IEC61937_LE;
|
||||
sh->samplerate = 192000;
|
||||
num_channels = 2;
|
||||
sh->i_bps = bps;
|
||||
break;
|
||||
case AV_CODEC_ID_MP3:
|
||||
spdif_ctx->iec61937_packet_size = 4608;
|
||||
sh->sample_format = AF_FORMAT_MPEG2;
|
||||
sh->samplerate = srate;
|
||||
num_channels = 2;
|
||||
sh->i_bps = bps;
|
||||
break;
|
||||
case AV_CODEC_ID_TRUEHD:
|
||||
spdif_ctx->iec61937_packet_size = 61440;
|
||||
sh->sample_format = AF_FORMAT_IEC61937_LE;
|
||||
sh->samplerate = 192000;
|
||||
num_channels = 8;
|
||||
sh->i_bps = bps;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (num_channels)
|
||||
mp_chmap_from_channels(&sh->channels, num_channels);
|
||||
|
||||
return 1;
|
||||
|
||||
fail:
|
||||
uninit(sh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int decode_audio(sh_audio_t *sh, unsigned char *buf,
|
||||
int minlen, int maxlen)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = sh->context;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
AVPacket pkt;
|
||||
|
||||
spdif_ctx->out_buffer_len = 0;
|
||||
spdif_ctx->out_buffer_size = maxlen;
|
||||
spdif_ctx->out_buffer = buf;
|
||||
while (spdif_ctx->out_buffer_len + spdif_ctx->iec61937_packet_size < maxlen
|
||||
&& spdif_ctx->out_buffer_len < minlen) {
|
||||
struct demux_packet *mpkt = demux_read_packet(sh->gsh);
|
||||
if (!mpkt)
|
||||
break;
|
||||
mp_set_av_packet(&pkt, mpkt);
|
||||
mp_msg(MSGT_DECAUDIO,MSGL_V, "pkt.data[%p] pkt.size[%d]\n",
|
||||
pkt.data, pkt.size);
|
||||
if (mpkt->pts != MP_NOPTS_VALUE) {
|
||||
sh->pts = mpkt->pts;
|
||||
sh->pts_bytes = 0;
|
||||
}
|
||||
int out_len = spdif_ctx->out_buffer_len;
|
||||
int ret = lavf_ctx->oformat->write_packet(lavf_ctx, &pkt);
|
||||
avio_flush(lavf_ctx->pb);
|
||||
sh->pts_bytes += spdif_ctx->out_buffer_len - out_len;
|
||||
talloc_free(mpkt);
|
||||
if (ret < 0)
|
||||
break;
|
||||
}
|
||||
return spdif_ctx->out_buffer_len;
|
||||
}
|
||||
|
||||
static int control(sh_audio_t *sh, int cmd, void *arg)
|
||||
{
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static void uninit(sh_audio_t *sh)
|
||||
{
|
||||
struct spdifContext *spdif_ctx = sh->context;
|
||||
AVFormatContext *lavf_ctx = spdif_ctx->lavf_ctx;
|
||||
|
||||
if (lavf_ctx) {
|
||||
if (lavf_ctx->oformat)
|
||||
lavf_ctx->oformat->write_trailer(lavf_ctx);
|
||||
av_freep(&lavf_ctx->pb);
|
||||
if (lavf_ctx->streams) {
|
||||
av_freep(&lavf_ctx->streams[0]->codec);
|
||||
av_freep(&lavf_ctx->streams[0]->info);
|
||||
av_freep(&lavf_ctx->streams[0]);
|
||||
}
|
||||
av_freep(&lavf_ctx->streams);
|
||||
av_freep(&lavf_ctx->priv_data);
|
||||
}
|
||||
av_freep(&lavf_ctx);
|
||||
av_freep(&spdif_ctx);
|
||||
}
|
||||
|
||||
static void add_decoders(struct mp_decoder_list *list)
|
||||
{
|
||||
for (int n = 0; codecs[n] != AV_CODEC_ID_NONE; n++) {
|
||||
@@ -271,7 +250,6 @@ static void add_decoders(struct mp_decoder_list *list)
|
||||
const struct ad_functions ad_spdif = {
|
||||
.name = "spdif",
|
||||
.add_decoders = add_decoders,
|
||||
.preinit = preinit,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
+176
-184
@@ -26,9 +26,9 @@
|
||||
#include "demux/codec_tags.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "mpvcore/codecs.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "mpvcore/bstr.h"
|
||||
#include "common/codecs.h"
|
||||
#include "common/msg.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
#include "stream/stream.h"
|
||||
#include "demux/demux.h"
|
||||
@@ -38,6 +38,8 @@
|
||||
#include "dec_audio.h"
|
||||
#include "ad.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/audio.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
@@ -46,7 +48,7 @@ extern const struct ad_functions ad_lavc;
|
||||
extern const struct ad_functions ad_spdif;
|
||||
|
||||
static const struct ad_functions * const ad_drivers[] = {
|
||||
#ifdef CONFIG_MPG123
|
||||
#if HAVE_MPG123
|
||||
&ad_mpg123,
|
||||
#endif
|
||||
&ad_lavc,
|
||||
@@ -54,51 +56,57 @@ static const struct ad_functions * const ad_drivers[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static int init_audio_codec(sh_audio_t *sh_audio, const char *decoder)
|
||||
// ad_mpg123 needs to be able to decode 1152 samples at once
|
||||
// ad_spdif needs up to 8192
|
||||
#define DECODE_MAX_UNIT MPMAX(8192, 1152)
|
||||
|
||||
// At least 8192 samples, plus hack for ad_mpg123 and ad_spdif
|
||||
#define DECODE_BUFFER_SAMPLES (8192 + DECODE_MAX_UNIT)
|
||||
|
||||
// Drop audio buffer and reinit it (after format change)
|
||||
// Returns whether the format was valid at all.
|
||||
static bool reinit_audio_buffer(struct dec_audio *da)
|
||||
{
|
||||
assert(!sh_audio->initialized);
|
||||
resync_audio_stream(sh_audio);
|
||||
sh_audio->samplesize = 4;
|
||||
sh_audio->sample_format = AF_FORMAT_FLOAT_NE;
|
||||
sh_audio->audio_out_minsize = 8192; // default, preinit() may change it
|
||||
if (!sh_audio->ad_driver->preinit(sh_audio)) {
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, "Audio decoder preinit failed.\n");
|
||||
if (!mp_audio_config_valid(&da->decoded)) {
|
||||
MP_ERR(da, "Audio decoder did not specify audio "
|
||||
"format, or requested an unsupported configuration!\n");
|
||||
return false;
|
||||
}
|
||||
mp_audio_buffer_reinit(da->decode_buffer, &da->decoded);
|
||||
mp_audio_buffer_preallocate_min(da->decode_buffer, DECODE_BUFFER_SAMPLES);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void uninit_decoder(struct dec_audio *d_audio)
|
||||
{
|
||||
if (d_audio->ad_driver) {
|
||||
MP_VERBOSE(d_audio, "Uninit audio decoder.\n");
|
||||
d_audio->ad_driver->uninit(d_audio);
|
||||
}
|
||||
d_audio->ad_driver = NULL;
|
||||
talloc_free(d_audio->priv);
|
||||
d_audio->priv = NULL;
|
||||
}
|
||||
|
||||
static int init_audio_codec(struct dec_audio *d_audio, const char *decoder)
|
||||
{
|
||||
if (!d_audio->ad_driver->init(d_audio, decoder)) {
|
||||
MP_VERBOSE(d_audio, "Audio decoder init failed.\n");
|
||||
d_audio->ad_driver = NULL;
|
||||
uninit_decoder(d_audio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int base_size = 65536;
|
||||
// At least 64 KiB plus rounding up to next decodable unit size
|
||||
sh_audio->a_buffer_size = base_size + sh_audio->audio_out_minsize;
|
||||
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_V,
|
||||
"dec_audio: Allocating %d + %d = %d bytes for output buffer.\n",
|
||||
sh_audio->audio_out_minsize, base_size,
|
||||
sh_audio->a_buffer_size);
|
||||
|
||||
sh_audio->a_buffer = av_mallocz(sh_audio->a_buffer_size);
|
||||
if (!sh_audio->a_buffer)
|
||||
abort();
|
||||
sh_audio->a_buffer_len = 0;
|
||||
|
||||
if (!sh_audio->ad_driver->init(sh_audio, decoder)) {
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_V, "Audio decoder init failed.\n");
|
||||
uninit_audio(sh_audio); // free buffers
|
||||
return 0;
|
||||
}
|
||||
|
||||
sh_audio->initialized = 1;
|
||||
|
||||
if (mp_chmap_is_empty(&sh_audio->channels) || !sh_audio->samplerate) {
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_ERR, "Audio decoder did not specify "
|
||||
"audio format!\n");
|
||||
uninit_audio(sh_audio); // free buffers
|
||||
d_audio->decode_buffer = mp_audio_buffer_create(NULL);
|
||||
if (!reinit_audio_buffer(d_audio)) {
|
||||
uninit_decoder(d_audio);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct mp_decoder_list *mp_audio_decoder_list(void)
|
||||
struct mp_decoder_list *audio_decoder_list(void)
|
||||
{
|
||||
struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list);
|
||||
for (int i = 0; ad_drivers[i] != NULL; i++)
|
||||
@@ -106,10 +114,10 @@ struct mp_decoder_list *mp_audio_decoder_list(void)
|
||||
return list;
|
||||
}
|
||||
|
||||
static struct mp_decoder_list *mp_select_audio_decoders(const char *codec,
|
||||
char *selection)
|
||||
static struct mp_decoder_list *audio_select_decoders(const char *codec,
|
||||
char *selection)
|
||||
{
|
||||
struct mp_decoder_list *list = mp_audio_decoder_list();
|
||||
struct mp_decoder_list *list = audio_decoder_list();
|
||||
struct mp_decoder_list *new = mp_select_decoders(list, codec, selection);
|
||||
talloc_free(list);
|
||||
return new;
|
||||
@@ -124,86 +132,81 @@ static const struct ad_functions *find_driver(const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int init_best_audio_codec(sh_audio_t *sh_audio, char *audio_decoders)
|
||||
int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders)
|
||||
{
|
||||
assert(!sh_audio->initialized);
|
||||
assert(!d_audio->ad_driver);
|
||||
audio_reset_decoding(d_audio);
|
||||
|
||||
struct mp_decoder_entry *decoder = NULL;
|
||||
struct mp_decoder_list *list =
|
||||
mp_select_audio_decoders(sh_audio->gsh->codec, audio_decoders);
|
||||
audio_select_decoders(d_audio->header->codec, audio_decoders);
|
||||
|
||||
mp_print_decoders(MSGT_DECAUDIO, MSGL_V, "Codec list:", list);
|
||||
mp_print_decoders(d_audio->log, MSGL_V, "Codec list:", list);
|
||||
|
||||
for (int n = 0; n < list->num_entries; n++) {
|
||||
struct mp_decoder_entry *sel = &list->entries[n];
|
||||
const struct ad_functions *driver = find_driver(sel->family);
|
||||
if (!driver)
|
||||
continue;
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_V, "Opening audio decoder %s:%s\n",
|
||||
sel->family, sel->decoder);
|
||||
sh_audio->ad_driver = driver;
|
||||
if (init_audio_codec(sh_audio, sel->decoder)) {
|
||||
MP_VERBOSE(d_audio, "Opening audio decoder %s:%s\n",
|
||||
sel->family, sel->decoder);
|
||||
d_audio->ad_driver = driver;
|
||||
if (init_audio_codec(d_audio, sel->decoder)) {
|
||||
decoder = sel;
|
||||
break;
|
||||
}
|
||||
sh_audio->ad_driver = NULL;
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_WARN, "Audio decoder init failed for "
|
||||
MP_WARN(d_audio, "Audio decoder init failed for "
|
||||
"%s:%s\n", sel->family, sel->decoder);
|
||||
}
|
||||
|
||||
if (sh_audio->initialized) {
|
||||
sh_audio->gsh->decoder_desc =
|
||||
talloc_asprintf(NULL, "%s [%s:%s]", decoder->desc, decoder->family,
|
||||
if (d_audio->ad_driver) {
|
||||
d_audio->decoder_desc =
|
||||
talloc_asprintf(d_audio, "%s [%s:%s]", decoder->desc, decoder->family,
|
||||
decoder->decoder);
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_INFO, "Selected audio codec: %s\n",
|
||||
sh_audio->gsh->decoder_desc);
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V,
|
||||
"AUDIO: %d Hz, %d ch, %s\n",
|
||||
sh_audio->samplerate, sh_audio->channels.num,
|
||||
af_fmt2str_short(sh_audio->sample_format));
|
||||
mp_msg(MSGT_IDENTIFY, MSGL_INFO,
|
||||
"ID_AUDIO_BITRATE=%d\nID_AUDIO_RATE=%d\n" "ID_AUDIO_NCH=%d\n",
|
||||
sh_audio->i_bps * 8, sh_audio->samplerate, sh_audio->channels.num);
|
||||
MP_INFO(d_audio, "Selected audio codec: %s\n",
|
||||
d_audio->decoder_desc);
|
||||
MP_VERBOSE(d_audio, "AUDIO: %d Hz, %d ch, %s\n",
|
||||
d_audio->decoded.rate, d_audio->decoded.channels.num,
|
||||
af_fmt_to_str(d_audio->decoded.format));
|
||||
MP_SMODE(d_audio, "ID_AUDIO_BITRATE=%d\nID_AUDIO_RATE=%d\n" "ID_AUDIO_NCH=%d\n",
|
||||
d_audio->i_bps * 8, d_audio->decoded.rate,
|
||||
d_audio->decoded.channels.num);
|
||||
} else {
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_ERR,
|
||||
"Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
sh_audio->gsh->codec ? sh_audio->gsh->codec : "<unknown>");
|
||||
MP_ERR(d_audio, "Failed to initialize an audio decoder for codec '%s'.\n",
|
||||
d_audio->header->codec ? d_audio->header->codec : "<unknown>");
|
||||
}
|
||||
|
||||
talloc_free(list);
|
||||
return sh_audio->initialized;
|
||||
return !!d_audio->ad_driver;
|
||||
}
|
||||
|
||||
void uninit_audio(sh_audio_t *sh_audio)
|
||||
void audio_uninit(struct dec_audio *d_audio)
|
||||
{
|
||||
if (sh_audio->afilter) {
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "Uninit audio filters...\n");
|
||||
af_destroy(sh_audio->afilter);
|
||||
sh_audio->afilter = NULL;
|
||||
if (!d_audio)
|
||||
return;
|
||||
if (d_audio->afilter) {
|
||||
MP_VERBOSE(d_audio, "Uninit audio filters...\n");
|
||||
af_destroy(d_audio->afilter);
|
||||
d_audio->afilter = NULL;
|
||||
}
|
||||
if (sh_audio->initialized) {
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_V, "Uninit audio.\n");
|
||||
sh_audio->ad_driver->uninit(sh_audio);
|
||||
sh_audio->initialized = 0;
|
||||
}
|
||||
talloc_free(sh_audio->gsh->decoder_desc);
|
||||
sh_audio->gsh->decoder_desc = NULL;
|
||||
av_freep(&sh_audio->a_buffer);
|
||||
uninit_decoder(d_audio);
|
||||
talloc_free(d_audio->decode_buffer);
|
||||
talloc_free(d_audio);
|
||||
}
|
||||
|
||||
|
||||
int init_audio_filters(sh_audio_t *sh_audio, int in_samplerate,
|
||||
int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
|
||||
int *out_samplerate, struct mp_chmap *out_channels,
|
||||
int *out_format)
|
||||
{
|
||||
if (!sh_audio->afilter)
|
||||
sh_audio->afilter = af_new(sh_audio->opts);
|
||||
struct af_stream *afs = sh_audio->afilter;
|
||||
if (!d_audio->afilter)
|
||||
d_audio->afilter = af_new(d_audio->global);
|
||||
struct af_stream *afs = d_audio->afilter;
|
||||
|
||||
// input format: same as codec's output format:
|
||||
mp_audio_buffer_get_format(d_audio->decode_buffer, &afs->input);
|
||||
// Sample rate can be different when adjusting playback speed
|
||||
afs->input.rate = in_samplerate;
|
||||
mp_audio_set_channels(&afs->input, &sh_audio->channels);
|
||||
mp_audio_set_format(&afs->input, sh_audio->sample_format);
|
||||
|
||||
// output format: same as ao driver's input format (if missing, fallback to input)
|
||||
afs->output.rate = *out_samplerate;
|
||||
@@ -212,15 +215,14 @@ int init_audio_filters(sh_audio_t *sh_audio, int in_samplerate,
|
||||
|
||||
char *s_from = mp_audio_config_to_str(&afs->input);
|
||||
char *s_to = mp_audio_config_to_str(&afs->output);
|
||||
mp_tmsg(MSGT_DECAUDIO, MSGL_V,
|
||||
"Building audio filter chain for %s -> %s...\n", s_from, s_to);
|
||||
MP_VERBOSE(d_audio, "Building audio filter chain for %s -> %s...\n", s_from, s_to);
|
||||
talloc_free(s_from);
|
||||
talloc_free(s_to);
|
||||
|
||||
// let's autoprobe it!
|
||||
if (af_init(afs) != 0) {
|
||||
af_destroy(afs);
|
||||
sh_audio->afilter = NULL;
|
||||
d_audio->afilter = NULL;
|
||||
return 0; // failed :(
|
||||
}
|
||||
|
||||
@@ -231,137 +233,127 @@ int init_audio_filters(sh_audio_t *sh_audio, int in_samplerate,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void set_min_out_buffer_size(struct bstr *outbuf, int len)
|
||||
// Filter len bytes of input, put result into outbuf.
|
||||
static int filter_n_bytes(struct dec_audio *da, struct mp_audio_buffer *outbuf,
|
||||
int len)
|
||||
{
|
||||
size_t oldlen = talloc_get_size(outbuf->start);
|
||||
if (oldlen < len) {
|
||||
assert(outbuf->start); // talloc context should be already set
|
||||
mp_msg(MSGT_DECAUDIO, MSGL_V, "Increasing filtered audio buffer size "
|
||||
"from %zd to %d\n", oldlen, len);
|
||||
outbuf->start = talloc_realloc_size(NULL, outbuf->start, len);
|
||||
}
|
||||
}
|
||||
|
||||
static int filter_n_bytes(sh_audio_t *sh, struct bstr *outbuf, int len)
|
||||
{
|
||||
assert(len - 1 + sh->audio_out_minsize <= sh->a_buffer_size);
|
||||
|
||||
int error = 0;
|
||||
|
||||
// Decode more bytes if needed
|
||||
int old_samplerate = sh->samplerate;
|
||||
struct mp_chmap old_channels = sh->channels;
|
||||
int old_sample_format = sh->sample_format;
|
||||
while (sh->a_buffer_len < len) {
|
||||
unsigned char *buf = sh->a_buffer + sh->a_buffer_len;
|
||||
int minlen = len - sh->a_buffer_len;
|
||||
int maxlen = sh->a_buffer_size - sh->a_buffer_len;
|
||||
int ret = sh->ad_driver->decode_audio(sh, buf, minlen, maxlen);
|
||||
int format_change = sh->samplerate != old_samplerate
|
||||
|| !mp_chmap_equals(&sh->channels, &old_channels)
|
||||
|| sh->sample_format != old_sample_format;
|
||||
if (ret <= 0 || format_change) {
|
||||
error = format_change ? -2 : -1;
|
||||
// samples from format-changing call get discarded too
|
||||
len = sh->a_buffer_len;
|
||||
struct mp_audio config;
|
||||
mp_audio_buffer_get_format(da->decode_buffer, &config);
|
||||
|
||||
while (mp_audio_buffer_samples(da->decode_buffer) < len) {
|
||||
int maxlen = mp_audio_buffer_get_write_available(da->decode_buffer);
|
||||
if (maxlen < DECODE_MAX_UNIT)
|
||||
break;
|
||||
struct mp_audio buffer;
|
||||
mp_audio_buffer_get_write_buffer(da->decode_buffer, maxlen, &buffer);
|
||||
buffer.samples = 0;
|
||||
error = da->ad_driver->decode_audio(da, &buffer, maxlen);
|
||||
if (error < 0)
|
||||
break;
|
||||
// Commit the data just read as valid data
|
||||
mp_audio_buffer_finish_write(da->decode_buffer, buffer.samples);
|
||||
// Format change
|
||||
if (!mp_audio_config_equals(&da->decoded, &config)) {
|
||||
// If there are still samples left in the buffer, let them drain
|
||||
// first, and don't signal a format change to the caller yet.
|
||||
if (mp_audio_buffer_samples(da->decode_buffer) > 0)
|
||||
break;
|
||||
error = -2;
|
||||
break;
|
||||
}
|
||||
sh->a_buffer_len += ret;
|
||||
}
|
||||
|
||||
// Filter
|
||||
struct mp_audio filter_input = {
|
||||
.audio = sh->a_buffer,
|
||||
.len = len,
|
||||
.rate = sh->samplerate,
|
||||
};
|
||||
mp_audio_set_format(&filter_input, sh->sample_format);
|
||||
mp_audio_set_channels(&filter_input, &sh->channels);
|
||||
struct mp_audio filter_data;
|
||||
mp_audio_buffer_peek(da->decode_buffer, &filter_data);
|
||||
filter_data.rate = da->afilter->input.rate; // due to playback speed change
|
||||
len = MPMIN(filter_data.samples, len);
|
||||
filter_data.samples = len;
|
||||
bool eof = filter_data.samples == 0 && error < 0;
|
||||
|
||||
struct mp_audio *filter_output = af_play(sh->afilter, &filter_input);
|
||||
if (!filter_output)
|
||||
if (af_filter(da->afilter, &filter_data, eof ? AF_FILTER_FLAG_EOF : 0) < 0)
|
||||
return -1;
|
||||
set_min_out_buffer_size(outbuf, outbuf->len + filter_output->len);
|
||||
memcpy(outbuf->start + outbuf->len, filter_output->audio,
|
||||
filter_output->len);
|
||||
outbuf->len += filter_output->len;
|
||||
|
||||
mp_audio_buffer_append(outbuf, &filter_data);
|
||||
if (eof && filter_data.samples > 0)
|
||||
error = 0; // don't end playback yet
|
||||
|
||||
// remove processed data from decoder buffer:
|
||||
sh->a_buffer_len -= len;
|
||||
memmove(sh->a_buffer, sh->a_buffer + len, sh->a_buffer_len);
|
||||
mp_audio_buffer_skip(da->decode_buffer, len);
|
||||
|
||||
// Assume the filter chain is drained from old data at this point.
|
||||
// (If not, the remaining old data is discarded.)
|
||||
if (error == -2) {
|
||||
if (!reinit_audio_buffer(da))
|
||||
error = -1; // switch to invalid format
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/* Try to get at least minlen decoded+filtered bytes in outbuf
|
||||
/* Try to get at least minsamples decoded+filtered samples in outbuf
|
||||
* (total length including possible existing data).
|
||||
* Return 0 on success, -1 on error/EOF (not distinguished).
|
||||
* In the former case outbuf->len is always >= minlen on return.
|
||||
* In case of EOF/error it might or might not be.
|
||||
* Outbuf.start must be talloc-allocated, and will be reallocated
|
||||
* if needed to fit all filter output. */
|
||||
int decode_audio(sh_audio_t *sh_audio, struct bstr *outbuf, int minlen)
|
||||
* Return 0 on success, -1 on error/EOF (not distinguidaed).
|
||||
* In the former case outbuf has at least minsamples buffered on return.
|
||||
* In case of EOF/error it might or might not be. */
|
||||
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
|
||||
int minsamples)
|
||||
{
|
||||
// Indicates that a filter seems to be buffering large amounts of data
|
||||
int huge_filter_buffer = 0;
|
||||
// Decoded audio must be cut at boundaries of this many bytes
|
||||
int unitsize = sh_audio->channels.num * sh_audio->samplesize * 16;
|
||||
// Decoded audio must be cut at boundaries of this many samples
|
||||
// (Note: the reason for this is unknown, possibly a refactoring artifact)
|
||||
int unitsize = 16;
|
||||
|
||||
/* Filter output size will be about filter_multiplier times input size.
|
||||
* If some filter buffers audio in big blocks this might only hold
|
||||
* as average over time. */
|
||||
double filter_multiplier = af_calc_filter_multiplier(sh_audio->afilter);
|
||||
double filter_multiplier = af_calc_filter_multiplier(d_audio->afilter);
|
||||
|
||||
/* If the decoder set audio_out_minsize then it can do the equivalent of
|
||||
* "while (output_len < target_len) output_len += audio_out_minsize;",
|
||||
* so we must guarantee there is at least audio_out_minsize-1 bytes
|
||||
* more space in the output buffer than the minimum length we try to
|
||||
* decode. */
|
||||
int max_decode_len = sh_audio->a_buffer_size - sh_audio->audio_out_minsize;
|
||||
if (!unitsize)
|
||||
return -1;
|
||||
max_decode_len -= max_decode_len % unitsize;
|
||||
int prev_buffered = -1;
|
||||
while (minsamples >= 0) {
|
||||
int buffered = mp_audio_buffer_samples(outbuf);
|
||||
if (minsamples < buffered || buffered == prev_buffered)
|
||||
break;
|
||||
prev_buffered = buffered;
|
||||
|
||||
while (minlen >= 0 && outbuf->len < minlen) {
|
||||
int decsamples = (minsamples - buffered) / filter_multiplier;
|
||||
// + some extra for possible filter buffering
|
||||
int declen = (minlen - outbuf->len) / filter_multiplier + (unitsize << 5);
|
||||
if (huge_filter_buffer)
|
||||
decsamples += unitsize << 5;
|
||||
|
||||
if (huge_filter_buffer) {
|
||||
/* Some filter must be doing significant buffering if the estimated
|
||||
* input length didn't produce enough output from filters.
|
||||
* Feed the filters 2k bytes at a time until we have enough output.
|
||||
* Very small amounts could make filtering inefficient while large
|
||||
* amounts can make MPlayer demux the file unnecessarily far ahead
|
||||
* Feed the filters 250 samples at a time until we have enough
|
||||
* output. Very small amounts could make filtering inefficient while
|
||||
* large amounts can make mpv demux the file unnecessarily far ahead
|
||||
* to get audio data and buffer video frames in memory while doing
|
||||
* so. However the performance impact of either is probably not too
|
||||
* significant as long as the value is not completely insane. */
|
||||
declen = 2000;
|
||||
declen -= declen % unitsize;
|
||||
if (declen > max_decode_len)
|
||||
declen = max_decode_len;
|
||||
else
|
||||
/* if this iteration does not fill buffer, we must have lots
|
||||
* of buffering in filters */
|
||||
huge_filter_buffer = 1;
|
||||
int res = filter_n_bytes(sh_audio, outbuf, declen);
|
||||
decsamples = 250;
|
||||
}
|
||||
|
||||
/* if this iteration does not fill buffer, we must have lots
|
||||
* of buffering in filters */
|
||||
huge_filter_buffer = 1;
|
||||
|
||||
int res = filter_n_bytes(d_audio, outbuf, decsamples);
|
||||
if (res < 0)
|
||||
return res;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void decode_audio_prepend_bytes(struct bstr *outbuf, int count, int byte)
|
||||
void audio_reset_decoding(struct dec_audio *d_audio)
|
||||
{
|
||||
set_min_out_buffer_size(outbuf, outbuf->len + count);
|
||||
memmove(outbuf->start + count, outbuf->start, outbuf->len);
|
||||
memset(outbuf->start, byte, count);
|
||||
outbuf->len += count;
|
||||
}
|
||||
|
||||
|
||||
void resync_audio_stream(sh_audio_t *sh_audio)
|
||||
{
|
||||
sh_audio->pts = MP_NOPTS_VALUE;
|
||||
if (!sh_audio->initialized)
|
||||
return;
|
||||
sh_audio->ad_driver->control(sh_audio, ADCTRL_RESYNC_STREAM, NULL);
|
||||
if (d_audio->ad_driver)
|
||||
d_audio->ad_driver->control(d_audio, ADCTRL_RESET, NULL);
|
||||
if (d_audio->afilter)
|
||||
af_control_all(d_audio->afilter, AF_CONTROL_RESET, NULL);
|
||||
d_audio->pts = MP_NOPTS_VALUE;
|
||||
d_audio->pts_offset = 0;
|
||||
if (d_audio->decode_buffer)
|
||||
mp_audio_buffer_clear(d_audio->decode_buffer);
|
||||
}
|
||||
@@ -20,20 +20,42 @@
|
||||
#define MPLAYER_DEC_AUDIO_H
|
||||
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/audio.h"
|
||||
#include "demux/stheader.h"
|
||||
|
||||
struct bstr;
|
||||
struct mp_audio_buffer;
|
||||
struct mp_decoder_list;
|
||||
|
||||
struct mp_decoder_list *mp_audio_decoder_list(void);
|
||||
int init_best_audio_codec(sh_audio_t *sh_audio, char *audio_decoders);
|
||||
int decode_audio(sh_audio_t *sh_audio, struct bstr *outbuf, int minlen);
|
||||
void decode_audio_prepend_bytes(struct bstr *outbuf, int count, int byte);
|
||||
void resync_audio_stream(sh_audio_t *sh_audio);
|
||||
void skip_audio_frame(sh_audio_t *sh_audio);
|
||||
void uninit_audio(sh_audio_t *sh_audio);
|
||||
struct dec_audio {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct mpv_global *global;
|
||||
const struct ad_functions *ad_driver;
|
||||
struct sh_stream *header;
|
||||
struct mp_audio_buffer *decode_buffer;
|
||||
struct af_stream *afilter;
|
||||
char *decoder_desc;
|
||||
// set by decoder
|
||||
struct mp_audio decoded; // format of decoded audio (no data, temporarily
|
||||
// different from decode_buffer during format
|
||||
// changes)
|
||||
int i_bps; // input bitrate, can change with VBR sources
|
||||
// last known pts value in output from decoder
|
||||
double pts;
|
||||
// number of samples output by decoder after last known pts
|
||||
int pts_offset;
|
||||
// For free use by the ad_driver
|
||||
void *priv;
|
||||
};
|
||||
|
||||
int init_audio_filters(sh_audio_t *sh_audio, int in_samplerate,
|
||||
struct mp_decoder_list *audio_decoder_list(void);
|
||||
int audio_init_best_codec(struct dec_audio *d_audio, char *audio_decoders);
|
||||
int audio_decode(struct dec_audio *d_audio, struct mp_audio_buffer *outbuf,
|
||||
int minsamples);
|
||||
void audio_reset_decoding(struct dec_audio *d_audio);
|
||||
void audio_uninit(struct dec_audio *d_audio);
|
||||
|
||||
int audio_init_filters(struct dec_audio *d_audio, int in_samplerate,
|
||||
int *out_samplerate, struct mp_chmap *out_channels,
|
||||
int *out_format);
|
||||
|
||||
|
||||
+172
-123
@@ -22,8 +22,11 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "mpvcore/m_config.h"
|
||||
#include "common/common.h"
|
||||
#include "common/global.h"
|
||||
|
||||
#include "options/m_option.h"
|
||||
#include "options/m_config.h"
|
||||
|
||||
#include "af.h"
|
||||
|
||||
@@ -52,18 +55,20 @@ extern struct af_info af_info_karaoke;
|
||||
extern struct af_info af_info_scaletempo;
|
||||
extern struct af_info af_info_bs2b;
|
||||
extern struct af_info af_info_lavfi;
|
||||
extern struct af_info af_info_convert24;
|
||||
extern struct af_info af_info_convertsignendian;
|
||||
|
||||
static struct af_info* filter_list[] = {
|
||||
&af_info_dummy,
|
||||
&af_info_delay,
|
||||
&af_info_channels,
|
||||
&af_info_force,
|
||||
&af_info_format,
|
||||
&af_info_volume,
|
||||
&af_info_equalizer,
|
||||
&af_info_pan,
|
||||
&af_info_surround,
|
||||
&af_info_sub,
|
||||
#ifdef HAVE_SYS_MMAN_H
|
||||
#if HAVE_SYS_MMAN_H
|
||||
&af_info_export,
|
||||
#endif
|
||||
&af_info_drc,
|
||||
@@ -72,21 +77,22 @@ static struct af_info* filter_list[] = {
|
||||
&af_info_lavrresample,
|
||||
&af_info_sweep,
|
||||
&af_info_hrtf,
|
||||
#ifdef CONFIG_LADSPA
|
||||
#if HAVE_LADSPA
|
||||
&af_info_ladspa,
|
||||
#endif
|
||||
&af_info_center,
|
||||
&af_info_sinesuppress,
|
||||
&af_info_karaoke,
|
||||
&af_info_scaletempo,
|
||||
#ifdef CONFIG_LIBBS2B
|
||||
#if HAVE_LIBBS2B
|
||||
&af_info_bs2b,
|
||||
#endif
|
||||
#ifdef CONFIG_AF_LAVFI
|
||||
#if HAVE_AF_LAVFI
|
||||
&af_info_lavfi,
|
||||
#endif
|
||||
// Must come last, because it's the fallback format conversion filter
|
||||
&af_info_format,
|
||||
// Must come last, because they're fallback format conversion filter
|
||||
&af_info_convert24,
|
||||
&af_info_convertsignendian,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -109,7 +115,10 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
const struct m_obj_list af_obj_list = {
|
||||
.get_desc = get_desc,
|
||||
.description = "audio filters",
|
||||
.legacy_hacks = true, // many filters have custom option parsing
|
||||
.aliases = {
|
||||
{"force", "format"},
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
static bool af_config_equals(struct mp_audio *a, struct mp_audio *b)
|
||||
@@ -133,7 +142,7 @@ static int input_control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
assert(arg == &((struct af_stream *)af->setup)->input);
|
||||
assert(arg == &((struct af_stream *)af->priv)->input);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
@@ -141,7 +150,7 @@ static int input_control(struct af_instance* af, int cmd, void* arg)
|
||||
|
||||
static int output_control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
struct af_stream *s = af->setup;
|
||||
struct af_stream *s = af->priv;
|
||||
struct mp_audio *output = &s->output;
|
||||
struct mp_audio *filter_output = &s->filter_output;
|
||||
|
||||
@@ -159,9 +168,9 @@ static int output_control(struct af_instance* af, int cmd, void* arg)
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static struct mp_audio *dummy_play(struct af_instance* af, struct mp_audio* data)
|
||||
static int dummy_filter(struct af_instance* af, struct mp_audio* data, int f)
|
||||
{
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Function for creating a new filter of type name.The name may
|
||||
@@ -171,8 +180,7 @@ static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
{
|
||||
struct m_obj_desc desc;
|
||||
if (!m_obj_list_find(&desc, &af_obj_list, bstr0(name))) {
|
||||
mp_tmsg(MSGT_VFILTER, MSGL_ERR,
|
||||
"Couldn't find audio filter '%s'.\n", name);
|
||||
MP_ERR(s, "Couldn't find audio filter '%s'.\n", name);
|
||||
return NULL;
|
||||
}
|
||||
const struct af_info *info = desc.p;
|
||||
@@ -181,40 +189,37 @@ static struct af_instance *af_create(struct af_stream *s, char *name,
|
||||
if (info->flags & AF_FLAGS_NOT_REENTRANT) {
|
||||
for (struct af_instance *cur = s->first; cur; cur = cur->next) {
|
||||
if (cur->info == info) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] There can only be one "
|
||||
MP_ERR(s, "There can only be one "
|
||||
"instance of the filter '%s' in each stream\n", name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Adding filter %s \n", name);
|
||||
MP_VERBOSE(s, "Adding filter %s \n", name);
|
||||
|
||||
struct af_instance *af = talloc_zero(NULL, struct af_instance);
|
||||
*af = (struct af_instance) {
|
||||
.info = info,
|
||||
.mul = 1,
|
||||
.data = talloc_zero(af, struct mp_audio),
|
||||
.log = mp_log_new(af, s->log, name),
|
||||
};
|
||||
struct m_config *config = m_config_from_obj_desc(af, &desc);
|
||||
if (m_config_initialize_obj(config, &desc, &af->priv, &args) < 0)
|
||||
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)
|
||||
goto error;
|
||||
af->priv = config->optstruct;
|
||||
|
||||
// Initialize the new filter
|
||||
if (af->info->open(af) != AF_OK)
|
||||
goto error;
|
||||
if (args && af->control) {
|
||||
// Single option string for old filters
|
||||
char *opts = (char *)args; // m_config_initialize_obj did this
|
||||
assert(!af->priv);
|
||||
if (af->control(af, AF_CONTROL_COMMAND_LINE, opts) <= AF_ERROR)
|
||||
goto error;
|
||||
}
|
||||
|
||||
return af;
|
||||
|
||||
error:
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR,
|
||||
"[libaf] Couldn't create or open audio filter '%s'\n", name);
|
||||
MP_ERR(s, "Couldn't create or open audio filter '%s'\n", name);
|
||||
talloc_free(af);
|
||||
return NULL;
|
||||
}
|
||||
@@ -275,17 +280,14 @@ static void af_remove(struct af_stream *s, struct af_instance *af)
|
||||
return;
|
||||
|
||||
// Print friendly message
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Removing filter %s \n",
|
||||
af->info->name);
|
||||
|
||||
// Notify filter before changing anything
|
||||
af->control(af, AF_CONTROL_PRE_DESTROY, 0);
|
||||
MP_VERBOSE(s, "Removing filter %s \n", af->info->name);
|
||||
|
||||
// Detach pointers
|
||||
af->prev->next = af->next;
|
||||
af->next->prev = af->prev;
|
||||
|
||||
af->uninit(af);
|
||||
if (af->uninit)
|
||||
af->uninit(af);
|
||||
talloc_free(af);
|
||||
}
|
||||
|
||||
@@ -303,26 +305,26 @@ repeat:
|
||||
static void af_print_filter_chain(struct af_stream *s, struct af_instance *at,
|
||||
int msg_level)
|
||||
{
|
||||
mp_msg(MSGT_AFILTER, msg_level, "Audio filter chain:\n");
|
||||
MP_MSG(s, msg_level, "Audio filter chain:\n");
|
||||
|
||||
struct af_instance *af = s->first;
|
||||
while (af) {
|
||||
mp_msg(MSGT_AFILTER, msg_level, " [%s] ", af->info->name);
|
||||
MP_MSG(s, msg_level, " [%s] ", af->info->name);
|
||||
if (af->data) {
|
||||
char *info = mp_audio_config_to_str(af->data);
|
||||
mp_msg(MSGT_AFILTER, msg_level, "%s", info);
|
||||
MP_MSG(s, msg_level, "%s", info);
|
||||
talloc_free(info);
|
||||
}
|
||||
if (af == at)
|
||||
mp_msg(MSGT_AFILTER, msg_level, " <-");
|
||||
mp_msg(MSGT_AFILTER, msg_level, "\n");
|
||||
MP_MSG(s, msg_level, " <-");
|
||||
MP_MSG(s, msg_level, "\n");
|
||||
|
||||
af = af->next;
|
||||
}
|
||||
|
||||
mp_msg(MSGT_AFILTER, msg_level, " [ao] ");
|
||||
MP_MSG(s, msg_level, " [ao] ");
|
||||
char *info = mp_audio_config_to_str(&s->output);
|
||||
mp_msg(MSGT_AFILTER, msg_level, "%s\n", info);
|
||||
MP_MSG(s, msg_level, "%s\n", info);
|
||||
talloc_free(info);
|
||||
}
|
||||
|
||||
@@ -334,14 +336,76 @@ static int af_count_filters(struct af_stream *s)
|
||||
return count;
|
||||
}
|
||||
|
||||
static char *af_find_conversion_filter(int srcfmt, int dstfmt)
|
||||
// Finds the first conversion filter on the way from srcfmt to dstfmt.
|
||||
// Conversions form a DAG: each node is a format/filter pair, and possible
|
||||
// conversions are edges. We search the DAG for the shortest path.
|
||||
// Some cases visit the same filter multiple times, but with different formats
|
||||
// (like u24le->s8), so one node per format or filter separate is not enough.
|
||||
// Returns the filter and dest. format for the first conversion step.
|
||||
// (So we know what conversion filter with what format to insert next.)
|
||||
static char *af_find_conversion_filter(int srcfmt, int *dstfmt)
|
||||
{
|
||||
for (int n = 0; filter_list[n]; n++) {
|
||||
struct af_info *af = filter_list[n];
|
||||
if (af->test_conversion && af->test_conversion(srcfmt, dstfmt))
|
||||
return (char *)af->name;
|
||||
#define MAX_NODES (64 * 32)
|
||||
int num_fmt = 0, num_filt = 0;
|
||||
for (int n = 0; filter_list[n]; n++)
|
||||
num_filt = n + 1;
|
||||
for (int n = 0; af_fmtstr_table[n].format; n++)
|
||||
num_fmt = n + 1;
|
||||
|
||||
int num_nodes = num_fmt * num_filt;
|
||||
assert(num_nodes < MAX_NODES);
|
||||
|
||||
bool visited[MAX_NODES] = {0};
|
||||
unsigned char distance[MAX_NODES];
|
||||
short previous[MAX_NODES] = {0};
|
||||
for (int n = 0; n < num_nodes; n++) {
|
||||
distance[n] = 255;
|
||||
if (af_fmtstr_table[n % num_fmt].format == srcfmt)
|
||||
distance[n] = 0;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
while (1) {
|
||||
int next = -1;
|
||||
for (int n = 0; n < num_nodes; n++) {
|
||||
if (!visited[n] && (next < 0 || (distance[n] < distance[next])))
|
||||
next = n;
|
||||
}
|
||||
if (next < 0 || distance[next] == 255)
|
||||
return NULL;
|
||||
visited[next] = true;
|
||||
|
||||
int fmt = next % num_fmt;
|
||||
if (af_fmtstr_table[fmt].format == *dstfmt) {
|
||||
// Best match found
|
||||
for (int cur = next; cur >= 0; cur = previous[cur] - 1) {
|
||||
if (distance[cur] == 1) {
|
||||
*dstfmt = af_fmtstr_table[cur % num_fmt].format;
|
||||
return (char *)filter_list[cur / num_fmt]->name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (int n = 0; filter_list[n]; n++) {
|
||||
struct af_info *af = filter_list[n];
|
||||
if (!af->test_conversion)
|
||||
continue;
|
||||
for (int i = 0; af_fmtstr_table[i].format; i++) {
|
||||
if (i != fmt && af->test_conversion(af_fmtstr_table[fmt].format,
|
||||
af_fmtstr_table[i].format))
|
||||
{
|
||||
int other = n * num_fmt + i;
|
||||
int ndist = distance[next] + 1;
|
||||
if (ndist < distance[other]) {
|
||||
distance[other] = ndist;
|
||||
previous[other] = next + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
assert(0);
|
||||
#undef MAX_NODES
|
||||
}
|
||||
|
||||
static bool af_is_conversion_filter(struct af_instance *af)
|
||||
@@ -352,7 +416,7 @@ static bool af_is_conversion_filter(struct af_instance *af)
|
||||
// in is what af can take as input - insert a conversion filter if the actual
|
||||
// input format doesn't match what af expects.
|
||||
// Returns:
|
||||
// AF_OK: must call af_reinit() or equivalent, format matches
|
||||
// AF_OK: must call af_reinit() or equivalent, format matches (or is closer)
|
||||
// AF_FALSE: nothing was changed, format matches
|
||||
// else: error
|
||||
static int af_fix_format_conversion(struct af_stream *s,
|
||||
@@ -365,18 +429,21 @@ static int af_fix_format_conversion(struct af_stream *s,
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (actual.format == in.format)
|
||||
return AF_FALSE;
|
||||
if (prev->control(prev, AF_CONTROL_FORMAT_FMT, &in.format) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
char *filter = af_find_conversion_filter(actual.format, in.format);
|
||||
int dstfmt = in.format;
|
||||
char *filter = af_find_conversion_filter(actual.format, &dstfmt);
|
||||
if (!filter)
|
||||
return AF_ERROR;
|
||||
if (strcmp(filter, prev->info->name) == 0) {
|
||||
if (prev->control(prev, AF_CONTROL_SET_FORMAT, &dstfmt) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
struct af_instance *new = af_prepend(s, af, filter, NULL);
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_FORMAT_FMT, &in.format)))
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_FORMAT, &dstfmt)))
|
||||
return rv;
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
@@ -392,7 +459,7 @@ static int af_fix_channels(struct af_stream *s, struct af_instance **p_af,
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (mp_chmap_equals(&actual.channels, &in.channels))
|
||||
return AF_FALSE;
|
||||
if (prev->control(prev, AF_CONTROL_CHANNELS, &in.channels) == AF_OK) {
|
||||
if (prev->control(prev, AF_CONTROL_SET_CHANNELS, &in.channels) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
@@ -401,7 +468,7 @@ static int af_fix_channels(struct af_stream *s, struct af_instance **p_af,
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_CHANNELS, &in.channels)))
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_CHANNELS, &in.channels)))
|
||||
return rv;
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
@@ -416,7 +483,7 @@ static int af_fix_rate(struct af_stream *s, struct af_instance **p_af,
|
||||
struct mp_audio actual = *prev->data;
|
||||
if (actual.rate == in.rate)
|
||||
return AF_FALSE;
|
||||
if (prev->control(prev, AF_CONTROL_RESAMPLE_RATE, &in.rate) == AF_OK) {
|
||||
if (prev->control(prev, AF_CONTROL_SET_RESAMPLE_RATE, &in.rate) == AF_OK) {
|
||||
*p_af = prev;
|
||||
return AF_OK;
|
||||
}
|
||||
@@ -425,7 +492,7 @@ static int af_fix_rate(struct af_stream *s, struct af_instance **p_af,
|
||||
if (new == NULL)
|
||||
return AF_ERROR;
|
||||
new->auto_inserted = true;
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_RESAMPLE_RATE, &in.rate)))
|
||||
if (AF_OK != (rv = new->control(new, AF_CONTROL_SET_RESAMPLE_RATE, &in.rate)))
|
||||
return rv;
|
||||
*p_af = new;
|
||||
return AF_OK;
|
||||
@@ -439,10 +506,12 @@ static int af_fix_rate(struct af_stream *s, struct af_instance **p_af,
|
||||
// In that case, you should always rebuild the filter chain, or abort.
|
||||
static int af_reinit(struct af_stream *s)
|
||||
{
|
||||
remove_auto_inserted_filters(s);
|
||||
// Start with the second filter, as the first filter is the special input
|
||||
// filter which needs no initialization.
|
||||
struct af_instance *af = s->first->next;
|
||||
int max_retry = af_count_filters(s) * 4; // up to 4 retries per filter
|
||||
// Up to 7 retries per filter (channel, rate, 5x format conversions)
|
||||
int max_retry = af_count_filters(s) * 7;
|
||||
int retry = 0;
|
||||
while (af) {
|
||||
if (retry >= max_retry)
|
||||
@@ -451,10 +520,11 @@ static int af_reinit(struct af_stream *s)
|
||||
// Check if this is the first filter
|
||||
struct mp_audio in = *af->prev->data;
|
||||
// Reset just in case...
|
||||
in.audio = NULL;
|
||||
in.len = 0;
|
||||
mp_audio_set_null_data(&in);
|
||||
|
||||
int rv = af->control(af, AF_CONTROL_REINIT, &in);
|
||||
if (rv == AF_OK && !mp_audio_config_equals(&in, af->prev->data))
|
||||
rv = AF_FALSE; // conversion filter needed
|
||||
switch (rv) {
|
||||
case AF_OK:
|
||||
af = af->next;
|
||||
@@ -484,8 +554,8 @@ static int af_reinit(struct af_stream *s)
|
||||
break;
|
||||
}
|
||||
default:
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] Reinitialization did not "
|
||||
"work, audio filter '%s' returned error code %i\n",
|
||||
MP_ERR(s, "Reinitialization did not work, "
|
||||
"audio filter '%s' returned error code %i\n",
|
||||
af->info->name, rv);
|
||||
af_print_filter_chain(s, af, MSGL_ERR);
|
||||
return AF_ERROR;
|
||||
@@ -502,8 +572,7 @@ static int af_reinit(struct af_stream *s)
|
||||
return af_config_equals(&s->output, &s->filter_output) ? AF_OK : AF_ERROR;
|
||||
|
||||
negotiate_error:
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[libaf] Unable to correct audio format. "
|
||||
"This error should never occur, please send a bug report.\n");
|
||||
MP_ERR(s, "Unable to convert audio input format to output format.\n");
|
||||
af_print_filter_chain(s, af, MSGL_ERR);
|
||||
return AF_ERROR;
|
||||
}
|
||||
@@ -515,7 +584,7 @@ void af_uninit(struct af_stream *s)
|
||||
af_remove(s, s->first->next);
|
||||
}
|
||||
|
||||
struct af_stream *af_new(struct MPOpts *opts)
|
||||
struct af_stream *af_new(struct mpv_global *global)
|
||||
{
|
||||
struct af_stream *s = talloc_zero(NULL, struct af_stream);
|
||||
static struct af_info in = { .name = "in" };
|
||||
@@ -523,8 +592,8 @@ struct af_stream *af_new(struct MPOpts *opts)
|
||||
*s->first = (struct af_instance) {
|
||||
.info = &in,
|
||||
.control = input_control,
|
||||
.play = dummy_play,
|
||||
.setup = s,
|
||||
.filter = dummy_filter,
|
||||
.priv = s,
|
||||
.data = &s->input,
|
||||
.mul = 1.0,
|
||||
};
|
||||
@@ -533,14 +602,15 @@ struct af_stream *af_new(struct MPOpts *opts)
|
||||
*s->last = (struct af_instance) {
|
||||
.info = &out,
|
||||
.control = output_control,
|
||||
.play = dummy_play,
|
||||
.setup = s,
|
||||
.filter = dummy_filter,
|
||||
.priv = s,
|
||||
.data = &s->filter_output,
|
||||
.mul = 1.0,
|
||||
};
|
||||
s->first->next = s->last;
|
||||
s->last->prev = s->first;
|
||||
s->opts = opts;
|
||||
s->opts = global->opts;
|
||||
s->log = mp_log_new(s, global->log, "!af");
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -553,7 +623,7 @@ void af_destroy(struct af_stream *s)
|
||||
/* Initialize the stream "s". This function creates a new filter list
|
||||
if necessary according to the values set in input and output. Input
|
||||
and output should contain the format of the current movie and the
|
||||
formate of the preferred output respectively. The function is
|
||||
format of the preferred output respectively. The function is
|
||||
reentrant i.e. if called with an already initialized stream the
|
||||
stream will be reinitialized.
|
||||
If one of the prefered output parameters is 0 the one that needs
|
||||
@@ -566,8 +636,8 @@ int af_init(struct af_stream *s)
|
||||
return -1;
|
||||
|
||||
// Precaution in case caller is misbehaving
|
||||
s->input.audio = s->output.audio = NULL;
|
||||
s->input.len = s->output.len = 0;
|
||||
mp_audio_set_null_data(&s->input);
|
||||
mp_audio_set_null_data(&s->output);
|
||||
|
||||
// Check if this is the first call
|
||||
if (s->first->next == s->last) {
|
||||
@@ -581,11 +651,9 @@ int af_init(struct af_stream *s)
|
||||
}
|
||||
}
|
||||
|
||||
remove_auto_inserted_filters(s);
|
||||
|
||||
if (af_reinit(s) != AF_OK) {
|
||||
// Something is stuffed audio out will not work
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "Could not create audio filter chain.\n");
|
||||
MP_ERR(s, "Could not create audio filter chain.\n");
|
||||
af_uninit(s);
|
||||
return -1;
|
||||
}
|
||||
@@ -619,36 +687,31 @@ struct af_instance *af_add(struct af_stream *s, char *name, char **args)
|
||||
return new;
|
||||
}
|
||||
|
||||
// Filter data chunk through the filters in the list
|
||||
struct mp_audio *af_play(struct af_stream *s, struct mp_audio *data)
|
||||
/* Filter data chunk through the filters in the list.
|
||||
* On success, *data is set to the filtered data/format.
|
||||
* Warning: input audio data will be overwritten.
|
||||
*/
|
||||
int af_filter(struct af_stream *s, struct mp_audio *data, int flags)
|
||||
{
|
||||
struct af_instance *af = s->first;
|
||||
assert(mp_audio_config_equals(af->data, data));
|
||||
// Iterate through all filters
|
||||
do {
|
||||
if (data->len <= 0)
|
||||
break;
|
||||
data = af->play(af, data);
|
||||
while (af) {
|
||||
int r = af->filter(af, data, flags);
|
||||
if (r < 0)
|
||||
return r;
|
||||
assert(mp_audio_config_equals(af->data, data));
|
||||
af = af->next;
|
||||
} while (af && data);
|
||||
return data;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Calculate the minimum output buffer size for given input data d
|
||||
* when using the RESIZE_LOCAL_BUFFER macro. The +t+1 part ensures the
|
||||
* value is >= len*mul rounded upwards to whole samples even if the
|
||||
* double 'mul' is inexact. */
|
||||
int af_lencalc(double mul, struct mp_audio *d)
|
||||
{
|
||||
int t = d->bps * d->nch;
|
||||
return d->len * mul + t + 1;
|
||||
}
|
||||
|
||||
// Calculate average ratio of filter output size to input size
|
||||
// Calculate average ratio of filter output samples to input samples.
|
||||
// e.g: num_output_samples = mul * num_input_samples
|
||||
double af_calc_filter_multiplier(struct af_stream *s)
|
||||
{
|
||||
struct af_instance *af = s->first;
|
||||
double mul = 1;
|
||||
// Iterate through all filters and calculate total multiplication factor
|
||||
do {
|
||||
mul *= af->mul;
|
||||
af = af->next;
|
||||
@@ -657,41 +720,20 @@ double af_calc_filter_multiplier(struct af_stream *s)
|
||||
return mul;
|
||||
}
|
||||
|
||||
/* Calculate the total delay [bytes output] caused by the filters */
|
||||
/* Calculate the total delay [seconds of output] caused by the filters */
|
||||
double af_calc_delay(struct af_stream *s)
|
||||
{
|
||||
struct af_instance *af = s->first;
|
||||
register double delay = 0.0;
|
||||
// Iterate through all filters
|
||||
double delay = 0.0;
|
||||
while (af) {
|
||||
delay += af->delay;
|
||||
delay *= af->mul;
|
||||
af = af->next;
|
||||
}
|
||||
return delay;
|
||||
}
|
||||
|
||||
/* Helper function called by the macro with the same name this
|
||||
function should not be called directly */
|
||||
int af_resize_local_buffer(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
// Calculate new length
|
||||
register int len = af_lencalc(af->mul, data);
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Reallocating memory in module %s, "
|
||||
"old len = %i, new len = %i\n", af->info->name, af->data->len, len);
|
||||
// If there is a buffer free it
|
||||
free(af->data->audio);
|
||||
// Create new buffer and check that it is OK
|
||||
af->data->audio = malloc(len);
|
||||
if (!af->data->audio) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[libaf] Could not allocate memory \n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
af->data->len = len;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// documentation in af.h
|
||||
/* Send control to all filters, starting with the last until one accepts the
|
||||
* command with AF_OK. Return the accepting filter. */
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
int res = AF_UNKNOWN;
|
||||
@@ -704,3 +746,10 @@ struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg)
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Send control to all filters. Never stop, even if a filter returns AF_OK. */
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg)
|
||||
{
|
||||
for (struct af_instance *af = s->first; af; af = af->next)
|
||||
af->control(af, cmd, arg);
|
||||
}
|
||||
+46
-189
@@ -21,17 +21,16 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "mpvcore/options.h"
|
||||
#include "options/options.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/audio.h"
|
||||
#include "control.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
struct af_instance;
|
||||
struct mpv_global;
|
||||
|
||||
// Number of channels
|
||||
#define AF_NCH MP_NUM_CHANNELS
|
||||
@@ -40,13 +39,14 @@ struct af_instance;
|
||||
#define AF_FLAGS_REENTRANT 0x00000000
|
||||
#define AF_FLAGS_NOT_REENTRANT 0x00000001
|
||||
|
||||
// Flags for af->filter()
|
||||
#define AF_FILTER_FLAG_EOF 1
|
||||
|
||||
/* Audio filter information not specific for current instance, but for
|
||||
a specific filter */
|
||||
struct af_info {
|
||||
const char *info;
|
||||
const char *name;
|
||||
const char *author;
|
||||
const char *comment;
|
||||
const int flags;
|
||||
int (*open)(struct af_instance *vf);
|
||||
bool (*test_conversion)(int src_format, int dst_format);
|
||||
@@ -58,18 +58,22 @@ struct af_info {
|
||||
// Linked list of audio filters
|
||||
struct af_instance {
|
||||
const struct af_info *info;
|
||||
struct mp_log *log;
|
||||
int (*control)(struct af_instance *af, int cmd, void *arg);
|
||||
void (*uninit)(struct af_instance *af);
|
||||
struct mp_audio * (*play)(struct af_instance *af, struct mp_audio *data);
|
||||
void *setup; // old field for priv structs
|
||||
/* flags is a bit mask of AF_FILTER_FLAG_* values
|
||||
* returns 0 on success, negative value on error
|
||||
*/
|
||||
int (*filter)(struct af_instance *af, struct mp_audio *data, int flags);
|
||||
void *priv;
|
||||
struct mp_audio *data; // configuration for outgoing data stream
|
||||
struct mp_audio *data; // configuration and buffer for outgoing data stream
|
||||
struct af_instance *next;
|
||||
struct af_instance *prev;
|
||||
double delay; /* Delay caused by the filter, in units of bytes read without
|
||||
* corresponding output */
|
||||
double delay; /* Delay caused by the filter, in seconds of audio consumed
|
||||
* without corresponding output */
|
||||
double mul; /* length multiplier: how much does this instance change
|
||||
the length of the buffer. */
|
||||
* the number of samples passed though. (Ratio of input
|
||||
* and output, e.g. mul=4 => 1 sample becomes 4 samples) .*/
|
||||
bool auto_inserted; // inserted by af.c, such as conversion filters
|
||||
};
|
||||
|
||||
@@ -80,18 +84,15 @@ struct af_stream {
|
||||
struct af_instance *last;
|
||||
// The user sets the input format (what the decoder outputs), and sets some
|
||||
// or all fields in output to the output format the AO accepts.
|
||||
// See fixup_output_format().
|
||||
struct mp_audio input;
|
||||
struct mp_audio output;
|
||||
struct mp_audio filter_output;
|
||||
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
};
|
||||
|
||||
/*********************************************
|
||||
// Return values
|
||||
*/
|
||||
|
||||
// Return values
|
||||
#define AF_DETACH 2
|
||||
#define AF_OK 1
|
||||
#define AF_TRUE 1
|
||||
@@ -100,190 +101,46 @@ struct af_stream {
|
||||
#define AF_ERROR -2
|
||||
#define AF_FATAL -3
|
||||
|
||||
// Parameters for af_control_*
|
||||
enum af_control {
|
||||
AF_CONTROL_REINIT = 1,
|
||||
AF_CONTROL_RESET,
|
||||
AF_CONTROL_SET_RESAMPLE_RATE,
|
||||
AF_CONTROL_SET_FORMAT,
|
||||
AF_CONTROL_SET_CHANNELS,
|
||||
AF_CONTROL_SET_VOLUME,
|
||||
AF_CONTROL_GET_VOLUME,
|
||||
AF_CONTROL_SET_PAN_LEVEL,
|
||||
AF_CONTROL_SET_PAN_NOUT,
|
||||
AF_CONTROL_SET_PAN_BALANCE,
|
||||
AF_CONTROL_GET_PAN_BALANCE,
|
||||
AF_CONTROL_SET_PLAYBACK_SPEED,
|
||||
};
|
||||
|
||||
// Argument for AF_CONTROL_SET_PAN_LEVEL
|
||||
typedef struct af_control_ext_s {
|
||||
void* arg; // Argument
|
||||
int ch; // Chanel number
|
||||
} af_control_ext_t;
|
||||
|
||||
/*********************************************
|
||||
// Export functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* \defgroup af_chain Audio filter chain functions
|
||||
* \{
|
||||
* \param s filter chain
|
||||
*/
|
||||
|
||||
struct af_stream *af_new(struct MPOpts *opts);
|
||||
struct af_stream *af_new(struct mpv_global *global);
|
||||
void af_destroy(struct af_stream *s);
|
||||
|
||||
/**
|
||||
* \brief Initialize the stream "s".
|
||||
* \return 0 on success, -1 on failure
|
||||
*
|
||||
* This function creates a new filter list if necessary, according
|
||||
* to the values set in input and output. Input and output should contain
|
||||
* the format of the current movie and the format of the preferred output
|
||||
* respectively.
|
||||
* Filters to convert to the preferred output format are inserted
|
||||
* automatically, except when they are set to 0.
|
||||
* The function is reentrant i.e. if called with an already initialized
|
||||
* stream the stream will be reinitialized.
|
||||
*/
|
||||
int af_init(struct af_stream *s);
|
||||
|
||||
/**
|
||||
* \brief Uninit and remove all filters from audio filter chain
|
||||
*/
|
||||
void af_uninit(struct af_stream *s);
|
||||
|
||||
/**
|
||||
* \brief This function adds the filter "name" to the stream s.
|
||||
* \param name name of filter to add
|
||||
* \return pointer to the new filter, NULL if insert failed
|
||||
*
|
||||
* The filter will be inserted somewhere nice in the
|
||||
* list of filters (i.e. at the beginning unless the
|
||||
* first filter is the format filter (why??).
|
||||
*/
|
||||
struct af_instance *af_add(struct af_stream *s, char *name, char **args);
|
||||
|
||||
/**
|
||||
* \brief filter data chunk through the filters in the list
|
||||
* \param data data to play
|
||||
* \return resulting data
|
||||
* \ingroup af_chain
|
||||
*/
|
||||
struct mp_audio *af_play(struct af_stream *s, struct mp_audio *data);
|
||||
|
||||
/**
|
||||
* \brief send control to all filters, starting with the last until
|
||||
* one accepts the command with AF_OK.
|
||||
* \param cmd filter control command
|
||||
* \param arg argument for filter command
|
||||
* \return the accepting filter or NULL if none was found
|
||||
*/
|
||||
int af_filter(struct af_stream *s, struct mp_audio *data, int flags);
|
||||
struct af_instance *af_control_any_rev(struct af_stream *s, int cmd, void *arg);
|
||||
void af_control_all(struct af_stream *s, int cmd, void *arg);
|
||||
|
||||
/**
|
||||
* \brief calculate average ratio of filter output lenth to input length
|
||||
* \return the ratio
|
||||
*/
|
||||
double af_calc_filter_multiplier(struct af_stream *s);
|
||||
|
||||
/**
|
||||
* \brief Calculate the total delay caused by the filters
|
||||
* \return delay in bytes of "missing" output
|
||||
*/
|
||||
double af_calc_delay(struct af_stream *s);
|
||||
|
||||
/** \} */ // end of af_chain group
|
||||
|
||||
// Helper functions and macros used inside the audio filters
|
||||
|
||||
/**
|
||||
* \defgroup af_filter Audio filter helper functions
|
||||
* \{
|
||||
*/
|
||||
|
||||
/* Helper function called by the macro with the same name only to be
|
||||
called from inside filters */
|
||||
int af_resize_local_buffer(struct af_instance *af, struct mp_audio *data);
|
||||
|
||||
/* Helper function used to calculate the exact buffer length needed
|
||||
when buffers are resized. The returned length is >= than what is
|
||||
needed */
|
||||
int af_lencalc(double mul, struct mp_audio *data);
|
||||
|
||||
/**
|
||||
* \brief convert dB to gain value
|
||||
* \param n number of values to convert
|
||||
* \param in [in] values in dB, <= -200 will become 0 gain
|
||||
* \param out [out] gain values
|
||||
* \param k input values are divided by this
|
||||
* \param mi minimum dB value, input will be clamped to this
|
||||
* \param ma maximum dB value, input will be clamped to this
|
||||
* \return AF_ERROR on error, AF_OK otherwise
|
||||
*/
|
||||
int af_from_dB(int n, float *in, float *out, float k, float mi, float ma);
|
||||
|
||||
/**
|
||||
* \brief convert gain value to dB
|
||||
* \param n number of values to convert
|
||||
* \param in [in] gain values, 0 wil become -200 dB
|
||||
* \param out [out] values in dB
|
||||
* \param k output values will be multiplied by this
|
||||
* \return AF_ERROR on error, AF_OK otherwise
|
||||
*/
|
||||
int af_to_dB(int n, float *in, float *out, float k);
|
||||
|
||||
/**
|
||||
* \brief convert milliseconds to sample time
|
||||
* \param n number of values to convert
|
||||
* \param in [in] values in milliseconds
|
||||
* \param out [out] sample time values
|
||||
* \param rate sample rate
|
||||
* \param mi minimum ms value, input will be clamped to this
|
||||
* \param ma maximum ms value, input will be clamped to this
|
||||
* \return AF_ERROR on error, AF_OK otherwise
|
||||
*/
|
||||
int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
|
||||
|
||||
/**
|
||||
* \brief convert sample time to milliseconds
|
||||
* \param n number of values to convert
|
||||
* \param in [in] sample time values
|
||||
* \param out [out] values in milliseconds
|
||||
* \param rate sample rate
|
||||
* \return AF_ERROR on error, AF_OK otherwise
|
||||
*/
|
||||
int af_to_ms(int n, int *in, float *out, int rate);
|
||||
|
||||
/**
|
||||
* \brief test if output format matches
|
||||
* \param af audio filter
|
||||
* \param out needed format, will be overwritten by available
|
||||
* format if they do not match
|
||||
* \return AF_FALSE if formats do not match, AF_OK if they match
|
||||
*
|
||||
* compares the format, bps, rate and nch values of af->data with out
|
||||
*/
|
||||
int af_test_output(struct af_instance *af, struct mp_audio *out);
|
||||
|
||||
/**
|
||||
* \brief soft clipping function using sin()
|
||||
* \param a input value
|
||||
* \return clipped value
|
||||
*/
|
||||
int af_from_dB(int n, float *in, float *out, float k, float mi, float ma);
|
||||
int af_to_dB(int n, float *in, float *out, float k);
|
||||
int af_from_ms(int n, float *in, int *out, int rate, float mi, float ma);
|
||||
int af_to_ms(int n, int *in, float *out, int rate);
|
||||
float af_softclip(float a);
|
||||
|
||||
/** \} */ // end of af_filter group, but more functions of this group below
|
||||
|
||||
/** Memory reallocation macro: if a local buffer is used (i.e. if the
|
||||
filter doesn't operate on the incoming buffer this macro must be
|
||||
called to ensure the buffer is big enough.
|
||||
* \ingroup af_filter
|
||||
*/
|
||||
#define RESIZE_LOCAL_BUFFER(a, d) \
|
||||
((a->data->len < af_lencalc(a->mul, d)) ? af_resize_local_buffer(a, d) : AF_OK)
|
||||
|
||||
/* Some other useful macro definitions*/
|
||||
#ifndef min
|
||||
#define min(a, b)(((a) > (b)) ? (b) : (a))
|
||||
#endif
|
||||
|
||||
#ifndef max
|
||||
#define max(a, b)(((a) > (b)) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef clamp
|
||||
#define clamp(a, min, max) (((a) > (max)) ? (max) : (((a) < (min)) ? (min) : (a)))
|
||||
#endif
|
||||
|
||||
#ifndef sign
|
||||
#define sign(a) (((a) > 0) ? (1) : (-1))
|
||||
#endif
|
||||
|
||||
#ifndef lrnd
|
||||
#define lrnd(a, b) ((b)((a) >= 0.0 ? (a) + 0.5 : (a) - 0.5))
|
||||
#endif
|
||||
|
||||
#endif /* MPLAYER_AF_H */
|
||||
+46
-56
@@ -27,7 +27,7 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "af.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
/// Internal specific data of the filter
|
||||
struct af_bs2b {
|
||||
@@ -37,33 +37,33 @@ struct af_bs2b {
|
||||
t_bs2bdp filter; ///< instance of a library filter
|
||||
};
|
||||
|
||||
#define PLAY(name, type) \
|
||||
static struct mp_audio *play_##name(struct af_instance *af, struct mp_audio *data) \
|
||||
#define FILTER(name, type) \
|
||||
static int filter_##name(struct af_instance *af, struct mp_audio *data, int f) \
|
||||
{ \
|
||||
/* filter is called for all pairs of samples available in the buffer */ \
|
||||
bs2b_cross_feed_##name(((struct af_bs2b*)(af->priv))->filter, \
|
||||
(type*)(data->audio), data->len/data->bps/2); \
|
||||
(type*)(data->planes[0]), data->samples); \
|
||||
\
|
||||
return data; \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
PLAY(f, float)
|
||||
PLAY(fbe, float)
|
||||
PLAY(fle, float)
|
||||
PLAY(s32be, int32_t)
|
||||
PLAY(u32be, uint32_t)
|
||||
PLAY(s32le, int32_t)
|
||||
PLAY(u32le, uint32_t)
|
||||
PLAY(s24be, bs2b_int24_t)
|
||||
PLAY(u24be, bs2b_uint24_t)
|
||||
PLAY(s24le, bs2b_int24_t)
|
||||
PLAY(u24le, bs2b_uint24_t)
|
||||
PLAY(s16be, int16_t)
|
||||
PLAY(u16be, uint16_t)
|
||||
PLAY(s16le, int16_t)
|
||||
PLAY(u16le, uint16_t)
|
||||
PLAY(s8, int8_t)
|
||||
PLAY(u8, uint8_t)
|
||||
FILTER(f, float)
|
||||
FILTER(fbe, float)
|
||||
FILTER(fle, float)
|
||||
FILTER(s32be, int32_t)
|
||||
FILTER(u32be, uint32_t)
|
||||
FILTER(s32le, int32_t)
|
||||
FILTER(u32le, uint32_t)
|
||||
FILTER(s24be, bs2b_int24_t)
|
||||
FILTER(u24be, bs2b_uint24_t)
|
||||
FILTER(s24le, bs2b_int24_t)
|
||||
FILTER(u24le, bs2b_uint24_t)
|
||||
FILTER(s16be, int16_t)
|
||||
FILTER(u16be, uint16_t)
|
||||
FILTER(s16le, int16_t)
|
||||
FILTER(u16le, uint16_t)
|
||||
FILTER(s8, int8_t)
|
||||
FILTER(u8, uint8_t)
|
||||
|
||||
|
||||
/// Initialization and runtime control
|
||||
@@ -74,7 +74,6 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
int format;
|
||||
char buf[256];
|
||||
// Sanity check
|
||||
if (!arg) return AF_ERROR;
|
||||
|
||||
@@ -87,70 +86,69 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
and assign corresponding handlers */
|
||||
switch (format) {
|
||||
case AF_FORMAT_FLOAT_BE:
|
||||
af->play = play_fbe;
|
||||
af->filter = filter_fbe;
|
||||
break;
|
||||
case AF_FORMAT_FLOAT_LE:
|
||||
af->play = play_fle;
|
||||
af->filter = filter_fle;
|
||||
break;
|
||||
case AF_FORMAT_S32_BE:
|
||||
af->play = play_s32be;
|
||||
af->filter = filter_s32be;
|
||||
break;
|
||||
case AF_FORMAT_U32_BE:
|
||||
af->play = play_u32be;
|
||||
af->filter = filter_u32be;
|
||||
break;
|
||||
case AF_FORMAT_S32_LE:
|
||||
af->play = play_s32le;
|
||||
af->filter = filter_s32le;
|
||||
break;
|
||||
case AF_FORMAT_U32_LE:
|
||||
af->play = play_u32le;
|
||||
af->filter = filter_u32le;
|
||||
break;
|
||||
case AF_FORMAT_S24_BE:
|
||||
af->play = play_s24be;
|
||||
af->filter = filter_s24be;
|
||||
break;
|
||||
case AF_FORMAT_U24_BE:
|
||||
af->play = play_u24be;
|
||||
af->filter = filter_u24be;
|
||||
break;
|
||||
case AF_FORMAT_S24_LE:
|
||||
af->play = play_s24le;
|
||||
af->filter = filter_s24le;
|
||||
break;
|
||||
case AF_FORMAT_U24_LE:
|
||||
af->play = play_u24le;
|
||||
af->filter = filter_u24le;
|
||||
break;
|
||||
case AF_FORMAT_S16_BE:
|
||||
af->play = play_s16be;
|
||||
af->filter = filter_s16be;
|
||||
break;
|
||||
case AF_FORMAT_U16_BE:
|
||||
af->play = play_u16be;
|
||||
af->filter = filter_u16be;
|
||||
break;
|
||||
case AF_FORMAT_S16_LE:
|
||||
af->play = play_s16le;
|
||||
af->filter = filter_s16le;
|
||||
break;
|
||||
case AF_FORMAT_U16_LE:
|
||||
af->play = play_u16le;
|
||||
af->filter = filter_u16le;
|
||||
break;
|
||||
case AF_FORMAT_S8:
|
||||
af->play = play_s8;
|
||||
af->filter = filter_s8;
|
||||
break;
|
||||
case AF_FORMAT_U8:
|
||||
af->play = play_u8;
|
||||
af->filter = filter_u8;
|
||||
break;
|
||||
default:
|
||||
af->play = play_f;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
af->filter = filter_f;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
break;
|
||||
}
|
||||
|
||||
// bs2b have srate limits, try to resample if needed
|
||||
if (af->data->rate > BS2B_MAXSRATE || af->data->rate < BS2B_MINSRATE) {
|
||||
af->data->rate = BS2B_DEFAULT_SRATE;
|
||||
mp_msg(MSGT_AFILTER, MSGL_WARN,
|
||||
"[bs2b] Requested sample rate %d Hz is out of bounds [%d..%d] Hz.\n"
|
||||
MP_WARN(af, "[bs2b] Requested sample rate %d Hz is out of bounds [%d..%d] Hz.\n"
|
||||
"[bs2b] Trying to resample to %d Hz.\n",
|
||||
af->data->rate, BS2B_MINSRATE, BS2B_MAXSRATE, BS2B_DEFAULT_SRATE);
|
||||
}
|
||||
bs2b_set_srate(s->filter, (long)af->data->rate);
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[bs2b] using format %s\n",
|
||||
af_fmt2str(af->data->format,buf,256));
|
||||
MP_VERBOSE(af, "[bs2b] using format %s\n",
|
||||
af_fmt_to_str(af->data->format));
|
||||
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
@@ -162,7 +160,6 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
struct af_bs2b *s = af->priv;
|
||||
free(af->data);
|
||||
if (s->filter)
|
||||
bs2b_close(s->filter);
|
||||
}
|
||||
@@ -173,13 +170,9 @@ static int af_open(struct af_instance *af)
|
||||
struct af_bs2b *s = af->priv;
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->mul = 1;
|
||||
if (!(af->data = calloc(1, sizeof(struct mp_audio))))
|
||||
return AF_ERROR;
|
||||
|
||||
// NULL means failed initialization
|
||||
if (!(s->filter = bs2b_open())) {
|
||||
free(af->data);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
@@ -197,12 +190,9 @@ static int af_open(struct af_instance *af)
|
||||
|
||||
/// Description of this filter
|
||||
struct af_info af_info_bs2b = {
|
||||
"Bauer stereophonic-to-binaural audio filter",
|
||||
"bs2b",
|
||||
"Andrew Savchenko",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open,
|
||||
.info = "Bauer stereophonic-to-binaural audio filter",
|
||||
.name = "bs2b",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_bs2b),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("fcut", fcut, 0, BS2B_MINFCUT, BS2B_MAXFCUT),
|
||||
|
||||
+20
-49
@@ -29,6 +29,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
@@ -40,7 +41,7 @@ typedef struct af_center_s
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_center_t* s = af->setup;
|
||||
af_center_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
@@ -48,47 +49,23 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_channels_old(af->data, max(s->ch+1,((struct mp_audio*)arg)->nch));
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
mp_audio_set_channels_old(af->data, MPMAX(s->ch+1,((struct mp_audio*)arg)->nch));
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
int ch=1;
|
||||
sscanf(arg,"%i", &ch);
|
||||
return control(af,AF_CONTROL_CENTER_CH | AF_CONTROL_SET, &ch);
|
||||
}
|
||||
case AF_CONTROL_CENTER_CH | AF_CONTROL_SET: // Requires reinit
|
||||
// Sanity check
|
||||
if((*(int*)arg >= AF_NCH) || (*(int*)arg < 0)){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[sub] Center channel number must be between "
|
||||
" 0 and %i current value is %i\n", AF_NCH-1, *(int*)arg);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->ch = *(int*)arg;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_CENTER_CH | AF_CONTROL_GET:
|
||||
*(int*)arg = s->ch;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_center_t* s = af->setup; // Setup for this instance
|
||||
float* a = c->audio; // Audio data
|
||||
int len = c->len/4; // Number of samples in current audio block
|
||||
af_center_t* s = af->priv; // Setup for this instance
|
||||
float* a = c->planes[0]; // Audio data
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = c->samples*c->nch; // Number of samples in current audio block
|
||||
int ch = s->ch; // Channel in which to insert the center audio
|
||||
register int i;
|
||||
|
||||
@@ -98,31 +75,25 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
a[i+ch] = (a[i]/2) + (a[i+1]/2);
|
||||
}
|
||||
|
||||
return c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af_center_t* s;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=s=calloc(1,sizeof(af_center_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
// Set default values
|
||||
s->ch = 1; // Channel nr 2
|
||||
af->filter=filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_center_t
|
||||
struct af_info af_info_center = {
|
||||
"Audio filter for adding a center channel",
|
||||
"center",
|
||||
"Alex Beregszaszi",
|
||||
"",
|
||||
AF_FLAGS_NOT_REENTRANT,
|
||||
af_open
|
||||
.info = "Audio filter for adding a center channel",
|
||||
.name = "center",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_center_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("channel", ch, 0, 0, AF_NCH - 1, OPTDEF_INT(1)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+68
-88
@@ -3,6 +3,8 @@
|
||||
* command line parameter channels. It is stupid and can only add
|
||||
* silence or copy channels, not mix or filter.
|
||||
*
|
||||
* Original author: Anders
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
@@ -25,6 +27,7 @@
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define FR 0
|
||||
@@ -32,12 +35,14 @@
|
||||
|
||||
typedef struct af_channels_s{
|
||||
int route[AF_NCH][2];
|
||||
int nr;
|
||||
int nch, nr;
|
||||
int router;
|
||||
char *routes;
|
||||
}af_channels_t;
|
||||
|
||||
// Local function for copying data
|
||||
static void copy(void* in, void* out, int ins, int inos,int outs, int outos, int len, int bps)
|
||||
static void copy(struct af_instance *af, void* in, void* out,
|
||||
int ins, int inos,int outs, int outos, int len, int bps)
|
||||
{
|
||||
switch(bps){
|
||||
case 1:{
|
||||
@@ -108,24 +113,25 @@ static void copy(void* in, void* out, int ins, int inos,int outs, int outos, int
|
||||
break;
|
||||
}
|
||||
default:
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] Unsupported number of bytes/sample: %i"
|
||||
MP_ERR(af, "Unsupported number of bytes/sample: %i"
|
||||
" please report this error on the MPlayer mailing list. \n",bps);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the routes are sane
|
||||
static int check_routes(af_channels_t* s, int nin, int nout)
|
||||
static int check_routes(struct af_instance *af, int nin, int nout)
|
||||
{
|
||||
af_channels_t* s = af->priv;
|
||||
int i;
|
||||
if((s->nr < 1) || (s->nr > AF_NCH)){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] The number of routing pairs must be"
|
||||
MP_ERR(af, "[channels] The number of routing pairs must be"
|
||||
" between 1 and %i. Current value is %i\n",AF_NCH,s->nr);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
for(i=0;i<s->nr;i++){
|
||||
if((s->route[i][FR] >= nin) || (s->route[i][TO] >= nout)){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] Invalid routing in pair nr. %i.\n", i);
|
||||
MP_ERR(af, "[channels] Invalid routing in pair nr. %i.\n", i);
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -135,9 +141,13 @@ static int check_routes(af_channels_t* s, int nin, int nout)
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_channels_t* s = af->setup;
|
||||
af_channels_t* s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
case AF_CONTROL_REINIT: ;
|
||||
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_set_unknown(&chmap, s->nch);
|
||||
mp_audio_set_channels(af->data, &chmap);
|
||||
|
||||
// Set default channel assignment
|
||||
if(!s->router){
|
||||
@@ -148,14 +158,14 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
|
||||
// If mono: fake stereo
|
||||
if(((struct mp_audio*)arg)->nch == 1){
|
||||
s->nr = min(af->data->nch,2);
|
||||
s->nr = MPMIN(af->data->nch,2);
|
||||
for(i=0;i<s->nr;i++){
|
||||
s->route[i][FR] = 0;
|
||||
s->route[i][TO] = i;
|
||||
}
|
||||
}
|
||||
else{
|
||||
s->nr = min(af->data->nch, ((struct mp_audio*)arg)->nch);
|
||||
s->nr = MPMIN(af->data->nch, ((struct mp_audio*)arg)->nch);
|
||||
for(i=0;i<s->nr;i++){
|
||||
s->route[i][FR] = i;
|
||||
s->route[i][TO] = i;
|
||||
@@ -164,108 +174,78 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
}
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_force_interleaved_format((struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, ((struct mp_audio*)arg)->format);
|
||||
af->mul = (double)af->data->nch / ((struct mp_audio*)arg)->nch;
|
||||
return check_routes(s,((struct mp_audio*)arg)->nch,af->data->nch);
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
int nch = 0;
|
||||
int n = 0;
|
||||
// Check number of channels and number of routing pairs
|
||||
sscanf(arg, "%i:%i%n", &nch, &s->nr, &n);
|
||||
|
||||
// If router scan commandline for routing pairs
|
||||
if(s->nr){
|
||||
char* cp = &((char*)arg)[n];
|
||||
int ch = 0;
|
||||
// Sanity check
|
||||
if((s->nr < 1) || (s->nr > AF_NCH)){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[channels] The number of routing pairs must be"
|
||||
" between 1 and %i. Current value is %i\n",AF_NCH,s->nr);
|
||||
}
|
||||
s->router = 1;
|
||||
// Scan for pairs on commandline
|
||||
while((*cp == ':') && (ch < s->nr)){
|
||||
sscanf(cp, ":%i:%i%n" ,&s->route[ch][FR], &s->route[ch][TO], &n);
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[channels] Routing from channel %i to"
|
||||
" channel %i\n",s->route[ch][FR],s->route[ch][TO]);
|
||||
cp = &cp[n];
|
||||
ch++;
|
||||
}
|
||||
}
|
||||
|
||||
struct mp_chmap chmap;
|
||||
mp_chmap_from_channels(&chmap, nch);
|
||||
if (AF_OK != af->control(af, AF_CONTROL_CHANNELS | AF_CONTROL_SET, &chmap))
|
||||
return AF_ERROR;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_CHANNELS | AF_CONTROL_SET:
|
||||
// Reinit must be called after this function has been called
|
||||
|
||||
mp_audio_set_channels(af->data, (struct mp_chmap *)arg);
|
||||
if(!s->router)
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[channels] Changing number of channels"
|
||||
" to %i\n",af->data->nch);
|
||||
return AF_OK;
|
||||
return check_routes(af,((struct mp_audio*)arg)->nch,af->data->nch);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->setup);
|
||||
if (af->data)
|
||||
free(af->data->audio);
|
||||
free(af->data);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
struct mp_audio* l = af->data; // Local data
|
||||
af_channels_t* s = af->setup;
|
||||
af_channels_t* s = af->priv;
|
||||
int i;
|
||||
|
||||
if(AF_OK != RESIZE_LOCAL_BUFFER(af,data))
|
||||
return NULL;
|
||||
mp_audio_realloc_min(af->data, data->samples);
|
||||
|
||||
// Reset unused channels
|
||||
memset(l->audio,0,c->len / c->nch * l->nch);
|
||||
memset(l->planes[0],0,mp_audio_psize(c) / c->nch * l->nch);
|
||||
|
||||
if(AF_OK == check_routes(s,c->nch,l->nch))
|
||||
if(AF_OK == check_routes(af,c->nch,l->nch))
|
||||
for(i=0;i<s->nr;i++)
|
||||
copy(c->audio,l->audio,c->nch,s->route[i][FR],
|
||||
l->nch,s->route[i][TO],c->len,c->bps);
|
||||
copy(af, c->planes[0],l->planes[0],c->nch,s->route[i][FR],
|
||||
l->nch,s->route[i][TO],mp_audio_psize(c),c->bps);
|
||||
|
||||
// Set output data
|
||||
c->audio = l->audio;
|
||||
c->len = c->len / c->nch * l->nch;
|
||||
c->planes[0] = l->planes[0];
|
||||
mp_audio_set_channels(c, &l->channels);
|
||||
|
||||
return c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_channels_t));
|
||||
if((af->data == NULL) || (af->setup == NULL))
|
||||
return AF_ERROR;
|
||||
return AF_OK;
|
||||
af->control=control;
|
||||
af->filter=filter;
|
||||
af_channels_t *s = af->priv;
|
||||
|
||||
// If router scan commandline for routing pairs
|
||||
if(s->routes && s->routes[0]){
|
||||
char* cp = s->routes;
|
||||
int ch = 0;
|
||||
// Scan for pairs on commandline
|
||||
do {
|
||||
int n = 0;
|
||||
if (ch >= AF_NCH) {
|
||||
MP_FATAL(af, "[channels] Can't have more than %d routes.\n", AF_NCH);
|
||||
return AF_ERROR;
|
||||
}
|
||||
sscanf(cp, "%i-%i%n" ,&s->route[ch][FR], &s->route[ch][TO], &n);
|
||||
MP_VERBOSE(af, "[channels] Routing from channel %i to"
|
||||
" channel %i\n",s->route[ch][FR],s->route[ch][TO]);
|
||||
cp = &cp[n];
|
||||
ch++;
|
||||
} while(*cp == ',' && *(cp++));
|
||||
s->nr = ch;
|
||||
if (s->nr > 0)
|
||||
s->router = 1;
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_channels_t
|
||||
struct af_info af_info_channels = {
|
||||
"Insert or remove channels",
|
||||
"channels",
|
||||
"Anders",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "Insert or remove channels",
|
||||
.name = "channels",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_channels_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("nch", nch, 0, 1, AF_NCH, OPTDEF_INT(2)),
|
||||
OPT_STRING("routes", routes, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
|
||||
static bool test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
return (src_format == AF_FORMAT_U24 && dst_format == AF_FORMAT_U32) ||
|
||||
(src_format == AF_FORMAT_S24 && dst_format == AF_FORMAT_S32) ||
|
||||
(src_format == AF_FORMAT_U32 && dst_format == AF_FORMAT_U24) ||
|
||||
(src_format == AF_FORMAT_S32 && dst_format == AF_FORMAT_S24);
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (!test_conversion(in->format, out->format))
|
||||
return AF_DETACH;
|
||||
|
||||
if ((in->format & AF_FORMAT_BITS_MASK) == AF_FORMAT_24BIT) {
|
||||
mp_audio_set_format(out, af_fmt_change_bits(in->format, 32));
|
||||
} else if ((in->format & AF_FORMAT_BITS_MASK) == AF_FORMAT_32BIT) {
|
||||
mp_audio_set_format(out, af_fmt_change_bits(in->format, 24));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
|
||||
out->rate = in->rate;
|
||||
mp_audio_set_channels(out, &in->channels);
|
||||
|
||||
assert(test_conversion(in->format, out->format));
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_SET_FORMAT: {
|
||||
mp_audio_set_format(af->data, *(int*)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// The LSB is always ignored.
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
#define SHIFT(x) ((3-(x))*8)
|
||||
#else
|
||||
#define SHIFT(x) (((x)+1)*8)
|
||||
#endif
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
{
|
||||
mp_audio_realloc_min(af->data, data->samples);
|
||||
|
||||
struct mp_audio *out = af->data;
|
||||
size_t len = mp_audio_psize(data) / data->bps;
|
||||
|
||||
if (data->bps == 4) {
|
||||
for (int s = 0; s < len; s++) {
|
||||
uint32_t val = *((uint32_t *)data->planes[0] + s);
|
||||
uint8_t *ptr = (uint8_t *)out->planes[0] + s * 3;
|
||||
ptr[0] = val >> SHIFT(0);
|
||||
ptr[1] = val >> SHIFT(1);
|
||||
ptr[2] = val >> SHIFT(2);
|
||||
}
|
||||
mp_audio_set_format(data, af_fmt_change_bits(data->format, 24));
|
||||
} else {
|
||||
for (int s = 0; s < len; s++) {
|
||||
uint8_t *ptr = (uint8_t *)data->planes[0] + s * 3;
|
||||
uint32_t val = ptr[0] << SHIFT(0)
|
||||
| ptr[1] << SHIFT(1)
|
||||
| ptr[2] << SHIFT(2);
|
||||
*((uint32_t *)out->planes[0] + s) = val;
|
||||
}
|
||||
mp_audio_set_format(data, af_fmt_change_bits(data->format, 32));
|
||||
}
|
||||
|
||||
data->planes[0] = out->planes[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
struct af_info af_info_convert24 = {
|
||||
.info = "Convert between 24 and 32 bit sample format",
|
||||
.name = "convert24",
|
||||
.open = af_open,
|
||||
.test_conversion = test_conversion,
|
||||
};
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "af.h"
|
||||
#include "audio/format.h"
|
||||
#include "compat/mpbswap.h"
|
||||
|
||||
static bool test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
if ((src_format & AF_FORMAT_PLANAR) ||
|
||||
(dst_format & AF_FORMAT_PLANAR))
|
||||
return false;
|
||||
int src_noend = src_format & ~AF_FORMAT_END_MASK;
|
||||
int dst_noend = dst_format & ~AF_FORMAT_END_MASK;
|
||||
// We can swap endian for all formats, but sign only for integer formats.
|
||||
if (src_noend == dst_noend)
|
||||
return true;
|
||||
if (((src_noend & ~AF_FORMAT_SIGN_MASK) ==
|
||||
(dst_noend & ~AF_FORMAT_SIGN_MASK)) &&
|
||||
((src_noend & AF_FORMAT_POINT_MASK) == AF_FORMAT_I))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (!test_conversion(in->format, out->format))
|
||||
return AF_DETACH;
|
||||
|
||||
out->rate = in->rate;
|
||||
mp_audio_set_channels(out, &in->channels);
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_SET_FORMAT: {
|
||||
mp_audio_set_format(af->data, *(int*)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void endian(void *data, int len, int bps)
|
||||
{
|
||||
switch (bps) {
|
||||
case 2:
|
||||
for (int i = 0; i < len; i++) {
|
||||
((uint16_t*)data)[i] = bswap_16(((uint16_t *)data)[i]);
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
for(int i = 0; i < len; i++) {
|
||||
uint8_t s = ((uint8_t *)data)[3 * i];
|
||||
((uint8_t *)data)[3 * i] = ((uint8_t *)data)[3 * i + 2];
|
||||
((uint8_t *)data)[3 * i + 2] = s;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
for(int i = 0; i < len; i++) {
|
||||
((uint32_t*)data)[i] = bswap_32(((uint32_t *)data)[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void si2us(void *data, int len, int bps, bool le)
|
||||
{
|
||||
ptrdiff_t i = -(len * bps);
|
||||
uint8_t *p = &((uint8_t *)data)[len * bps];
|
||||
if (le && bps > 1)
|
||||
p += bps - 1;
|
||||
if (len <= 0)
|
||||
return;
|
||||
do {
|
||||
p[i] ^= 0x80;
|
||||
} while (i += bps);
|
||||
}
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
{
|
||||
int infmt = data->format;
|
||||
int outfmt = af->data->format;
|
||||
size_t len = data->samples * data->nch;
|
||||
|
||||
if ((infmt & AF_FORMAT_END_MASK) != (outfmt & AF_FORMAT_END_MASK))
|
||||
endian(data->planes[0], len, data->bps);
|
||||
|
||||
if ((infmt & AF_FORMAT_SIGN_MASK) != (outfmt & AF_FORMAT_SIGN_MASK))
|
||||
si2us(data->planes[0], len, data->bps,
|
||||
(outfmt & AF_FORMAT_END_MASK) == AF_FORMAT_LE);
|
||||
|
||||
mp_audio_set_format(data, outfmt);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->filter = filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
struct af_info af_info_convertsignendian = {
|
||||
.info = "Convert between sample format sign/endian",
|
||||
.name = "convertsignendian",
|
||||
.open = af_open,
|
||||
.test_conversion = test_conversion,
|
||||
};
|
||||
+54
-61
@@ -3,6 +3,8 @@
|
||||
* channels and can be used for simple position panning.
|
||||
* An extension for this filter would be a reverb.
|
||||
*
|
||||
* Original author: Anders
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
@@ -25,6 +27,7 @@
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define L 65536
|
||||
@@ -38,67 +41,48 @@ typedef struct af_delay_s
|
||||
int wi[AF_NCH]; // Write index
|
||||
int ri; // Read index
|
||||
float d[AF_NCH]; // Delay [ms]
|
||||
char *delaystr;
|
||||
}af_delay_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_delay_t* s = af->setup;
|
||||
af_delay_t* s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
int i;
|
||||
struct mp_audio *in = arg;
|
||||
|
||||
if (in->bps != 1 && in->bps != 2 && in->bps != 4) {
|
||||
MP_FATAL(af, "[delay] Sample format not supported\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
// Free prevous delay queues
|
||||
for(i=0;i<af->data->nch;i++)
|
||||
free(s->q[i]);
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_force_interleaved_format(in);
|
||||
mp_audio_copy_config(af->data, in);
|
||||
|
||||
// Allocate new delay queues
|
||||
for(i=0;i<af->data->nch;i++){
|
||||
s->q[i] = calloc(L,af->data->bps);
|
||||
if(NULL == s->q[i])
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[delay] Out of memory\n");
|
||||
MP_FATAL(af, "[delay] Out of memory\n");
|
||||
}
|
||||
|
||||
return control(af,AF_CONTROL_DELAY_LEN | AF_CONTROL_SET,s->d);
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
int n = 1;
|
||||
int i = 0;
|
||||
char* cl = arg;
|
||||
while(n && i < AF_NCH ){
|
||||
sscanf(cl,"%f:%n",&s->d[i],&n);
|
||||
if(n==0 || cl[n-1] == '\0')
|
||||
break;
|
||||
cl=&cl[n];
|
||||
i++;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_DELAY_LEN | AF_CONTROL_SET:{
|
||||
int i;
|
||||
if(AF_OK != af_from_ms(AF_NCH, arg, s->wi, af->data->rate, 0.0, 1000.0))
|
||||
if(AF_OK != af_from_ms(AF_NCH, s->d, s->wi, af->data->rate, 0.0, 1000.0))
|
||||
return AF_ERROR;
|
||||
s->ri = 0;
|
||||
for(i=0;i<AF_NCH;i++){
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG2, "[delay] Channel %i delayed by %0.3fms\n",
|
||||
i,clamp(s->d[i],0.0,1000.0));
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG3, "[delay] Channel %i delayed by %i samples\n",
|
||||
i,s->wi[i]);
|
||||
MP_DBG(af, "[delay] Channel %i delayed by %0.3fms\n",
|
||||
i,MPCLAMP(s->d[i],0.0,1000.0));
|
||||
MP_TRACE(af, "[delay] Channel %i delayed by %i samples\n",
|
||||
i,s->wi[i]);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_DELAY_LEN | AF_CONTROL_GET:{
|
||||
int i;
|
||||
for(i=0;i<AF_NCH;i++){
|
||||
if(s->ri > s->wi[i])
|
||||
s->wi[i] = L - (s->ri - s->wi[i]);
|
||||
else
|
||||
s->wi[i] = s->wi[i] - s->ri;
|
||||
}
|
||||
return af_to_ms(AF_NCH, s->wi, arg, af->data->rate);
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
@@ -108,25 +92,23 @@ static void uninit(struct af_instance* af)
|
||||
{
|
||||
int i;
|
||||
|
||||
free(af->data);
|
||||
for(i=0;i<AF_NCH;i++)
|
||||
free(((af_delay_t*)(af->setup))->q[i]);
|
||||
free(af->setup);
|
||||
free(((af_delay_t*)(af->priv))->q[i]);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_delay_t* s = af->setup; // Setup for this instance
|
||||
af_delay_t* s = af->priv; // Setup for this instance
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = c->len/c->bps; // Number of sample in data chunk
|
||||
int len = mp_audio_psize(c)/c->bps; // Number of sample in data chunk
|
||||
int ri = 0;
|
||||
int ch,i;
|
||||
for(ch=0;ch<nch;ch++){
|
||||
switch(c->bps){
|
||||
case 1:{
|
||||
int8_t* a = c->audio;
|
||||
int8_t* a = c->planes[0];
|
||||
int8_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
@@ -140,7 +122,7 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
break;
|
||||
}
|
||||
case 2:{
|
||||
int16_t* a = c->audio;
|
||||
int16_t* a = c->planes[0];
|
||||
int16_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
@@ -154,7 +136,7 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
break;
|
||||
}
|
||||
case 4:{
|
||||
int32_t* a = c->audio;
|
||||
int32_t* a = c->planes[0];
|
||||
int32_t* q = s->q[ch];
|
||||
int wi = s->wi[ch];
|
||||
ri = s->ri;
|
||||
@@ -170,28 +152,39 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
}
|
||||
}
|
||||
s->ri = ri;
|
||||
return c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_delay_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
return AF_OK;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->filter=filter;
|
||||
af_delay_t *s = af->priv;
|
||||
int n = 1;
|
||||
int i = 0;
|
||||
char* cl = s->delaystr;
|
||||
while(cl && n && i < AF_NCH ){
|
||||
sscanf(cl,"%f%n",&s->d[i],&n);
|
||||
if(n==0 || cl[n-1] == '\0')
|
||||
break;
|
||||
cl=&cl[n];
|
||||
if (*cl != ',')
|
||||
break;
|
||||
cl++;
|
||||
i++;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_delay_t
|
||||
struct af_info af_info_delay = {
|
||||
"Delay audio filter",
|
||||
"delay",
|
||||
"Anders",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "Delay audio filter",
|
||||
.name = "delay",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_delay_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("delays", delaystr, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+44
-65
@@ -26,6 +26,7 @@
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Methods:
|
||||
@@ -80,46 +81,27 @@ typedef struct af_volume_s
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_drc_t* s = (af_drc_t*)af->setup;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
mp_audio_force_interleaved_format((struct mp_audio*)arg);
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
|
||||
if(((struct mp_audio*)arg)->format != (AF_FORMAT_S16_NE)){
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
if(((struct mp_audio*)arg)->format != (AF_FORMAT_S16)){
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
int i = 0;
|
||||
float target = DEFAULT_TARGET;
|
||||
sscanf((char*)arg,"%d:%f", &i, &target);
|
||||
if (i != 1 && i != 2)
|
||||
return AF_ERROR;
|
||||
s->method = i-1;
|
||||
s->mid_s16 = ((float)SHRT_MAX) * target;
|
||||
s->mid_float = target;
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
static void method1_int16(af_drc_t *s, struct mp_audio *c)
|
||||
{
|
||||
register int i = 0;
|
||||
int16_t *data = (int16_t*)c->audio; // Audio data
|
||||
int len = c->len/2; // Number of samples
|
||||
int16_t *data = (int16_t*)c->planes[0]; // Audio data
|
||||
int len = c->samples*c->nch; // Number of samples
|
||||
float curavg = 0.0, newavg, neededmul;
|
||||
int tmp;
|
||||
|
||||
@@ -139,14 +121,14 @@ static void method1_int16(af_drc_t *s, struct mp_audio *c)
|
||||
s->mul = (1.0 - SMOOTH_MUL) * s->mul + SMOOTH_MUL * neededmul;
|
||||
|
||||
// clamp the mul coefficient
|
||||
s->mul = clamp(s->mul, MUL_MIN, MUL_MAX);
|
||||
s->mul = MPCLAMP(s->mul, MUL_MIN, MUL_MAX);
|
||||
}
|
||||
|
||||
// Scale & clamp the samples
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tmp = s->mul * data[i];
|
||||
tmp = clamp(tmp, SHRT_MIN, SHRT_MAX);
|
||||
tmp = MPCLAMP(tmp, SHRT_MIN, SHRT_MAX);
|
||||
data[i] = tmp;
|
||||
}
|
||||
|
||||
@@ -160,8 +142,8 @@ static void method1_int16(af_drc_t *s, struct mp_audio *c)
|
||||
static void method1_float(af_drc_t *s, struct mp_audio *c)
|
||||
{
|
||||
register int i = 0;
|
||||
float *data = (float*)c->audio; // Audio data
|
||||
int len = c->len/4; // Number of samples
|
||||
float *data = (float*)c->planes[0]; // Audio data
|
||||
int len = c->samples*c->nch; // Number of samples
|
||||
float curavg = 0.0, newavg, neededmul, tmp;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
@@ -180,7 +162,7 @@ static void method1_float(af_drc_t *s, struct mp_audio *c)
|
||||
s->mul = (1.0 - SMOOTH_MUL) * s->mul + SMOOTH_MUL * neededmul;
|
||||
|
||||
// clamp the mul coefficient
|
||||
s->mul = clamp(s->mul, MUL_MIN, MUL_MAX);
|
||||
s->mul = MPCLAMP(s->mul, MUL_MIN, MUL_MAX);
|
||||
}
|
||||
|
||||
// Scale & clamp the samples
|
||||
@@ -197,8 +179,8 @@ static void method1_float(af_drc_t *s, struct mp_audio *c)
|
||||
static void method2_int16(af_drc_t *s, struct mp_audio *c)
|
||||
{
|
||||
register int i = 0;
|
||||
int16_t *data = (int16_t*)c->audio; // Audio data
|
||||
int len = c->len/2; // Number of samples
|
||||
int16_t *data = (int16_t*)c->planes[0]; // Audio data
|
||||
int len = c->samples*c->nch; // Number of samples
|
||||
float curavg = 0.0, newavg, avg = 0.0;
|
||||
int tmp, totallen = 0;
|
||||
|
||||
@@ -223,7 +205,7 @@ static void method2_int16(af_drc_t *s, struct mp_audio *c)
|
||||
if (avg >= SIL_S16)
|
||||
{
|
||||
s->mul = s->mid_s16 / avg;
|
||||
s->mul = clamp(s->mul, MUL_MIN, MUL_MAX);
|
||||
s->mul = MPCLAMP(s->mul, MUL_MIN, MUL_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +213,7 @@ static void method2_int16(af_drc_t *s, struct mp_audio *c)
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
tmp = s->mul * data[i];
|
||||
tmp = clamp(tmp, SHRT_MIN, SHRT_MAX);
|
||||
tmp = MPCLAMP(tmp, SHRT_MIN, SHRT_MAX);
|
||||
data[i] = tmp;
|
||||
}
|
||||
|
||||
@@ -247,8 +229,8 @@ static void method2_int16(af_drc_t *s, struct mp_audio *c)
|
||||
static void method2_float(af_drc_t *s, struct mp_audio *c)
|
||||
{
|
||||
register int i = 0;
|
||||
float *data = (float*)c->audio; // Audio data
|
||||
int len = c->len/4; // Number of samples
|
||||
float *data = (float*)c->planes[0]; // Audio data
|
||||
int len = c->samples*c->nch; // Number of samples
|
||||
float curavg = 0.0, newavg, avg = 0.0, tmp;
|
||||
int totallen = 0;
|
||||
|
||||
@@ -273,7 +255,7 @@ static void method2_float(af_drc_t *s, struct mp_audio *c)
|
||||
if (avg >= SIL_FLOAT)
|
||||
{
|
||||
s->mul = s->mid_float / avg;
|
||||
s->mul = clamp(s->mul, MUL_MIN, MUL_MAX);
|
||||
s->mul = MPCLAMP(s->mul, MUL_MIN, MUL_MAX);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,59 +272,56 @@ static void method2_float(af_drc_t *s, struct mp_audio *c)
|
||||
s->idx = (s->idx + 1) % NSAMPLES;
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
af_drc_t *s = af->setup;
|
||||
af_drc_t *s = af->priv;
|
||||
|
||||
if(af->data->format == (AF_FORMAT_S16_NE))
|
||||
if(af->data->format == (AF_FORMAT_S16))
|
||||
{
|
||||
if (s->method)
|
||||
if (s->method == 2)
|
||||
method2_int16(s, data);
|
||||
else
|
||||
method1_int16(s, data);
|
||||
}
|
||||
else if(af->data->format == (AF_FORMAT_FLOAT_NE))
|
||||
else if(af->data->format == (AF_FORMAT_FLOAT))
|
||||
{
|
||||
if (s->method)
|
||||
if (s->method == 2)
|
||||
method2_float(s, data);
|
||||
else
|
||||
method1_float(s, data);
|
||||
}
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
int i = 0;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_drc_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
af->filter=filter;
|
||||
af_drc_t *priv = af->priv;
|
||||
|
||||
((af_drc_t*)af->setup)->mul = MUL_INIT;
|
||||
((af_drc_t*)af->setup)->lastavg = ((float)SHRT_MAX) * DEFAULT_TARGET;
|
||||
((af_drc_t*)af->setup)->idx = 0;
|
||||
((af_drc_t*)af->setup)->mid_s16 = ((float)SHRT_MAX) * DEFAULT_TARGET;
|
||||
((af_drc_t*)af->setup)->mid_float = DEFAULT_TARGET;
|
||||
priv->mul = MUL_INIT;
|
||||
priv->lastavg = ((float)SHRT_MAX) * DEFAULT_TARGET;
|
||||
priv->idx = 0;
|
||||
for (i = 0; i < NSAMPLES; i++)
|
||||
{
|
||||
((af_drc_t*)af->setup)->mem[i].len = 0;
|
||||
((af_drc_t*)af->setup)->mem[i].avg = 0;
|
||||
priv->mem[i].len = 0;
|
||||
priv->mem[i].avg = 0;
|
||||
}
|
||||
priv->mid_s16 = ((float)SHRT_MAX) * priv->mid_float;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_drc_t
|
||||
struct af_info af_info_drc = {
|
||||
"Dynamic range compression filter",
|
||||
"drc",
|
||||
"Alex Beregszaszi & Pierre Lombard",
|
||||
"",
|
||||
AF_FLAGS_NOT_REENTRANT,
|
||||
af_open
|
||||
.info = "Dynamic range compression filter",
|
||||
.name = "drc",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_drc_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("method", method, 0, 1, 2, OPTDEF_INT(1)),
|
||||
OPT_FLOAT("target", mid_float, 0, OPTDEF_FLOAT(DEFAULT_TARGET)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+10
-25
@@ -2,6 +2,8 @@
|
||||
* The name speaks for itself. This filter is a dummy and will
|
||||
* not blow up regardless of what you do with it.
|
||||
*
|
||||
* Original author: Anders
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
@@ -31,46 +33,29 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT: ;
|
||||
*af->data = *(struct mp_audio*)arg;
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[dummy] Was reinitialized: %iHz/%ich/%s\n",
|
||||
af->data->rate,af->data->nch,af_fmt2str_short(af->data->format));
|
||||
MP_VERBOSE(af, "[dummy] Was reinitialized: %iHz/%ich/%s\n",
|
||||
af->data->rate,af->data->nch,af_fmt_to_str(af->data->format));
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
// Do something necessary to get rid of annoying warning during compile
|
||||
if(!af)
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "EEEK: Argument af == NULL in af_dummy.c play().");
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=malloc(sizeof(struct mp_audio));
|
||||
if(af->data == NULL)
|
||||
return AF_ERROR;
|
||||
af->filter=filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
struct af_info af_info_dummy = {
|
||||
"dummy",
|
||||
"dummy",
|
||||
"Anders",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "dummy",
|
||||
.name = "dummy",
|
||||
.open = af_open,
|
||||
};
|
||||
+31
-44
@@ -29,6 +29,7 @@
|
||||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
#define L 2 // Storage for filter taps
|
||||
@@ -67,6 +68,7 @@ typedef struct af_equalizer_s
|
||||
int K; // Number of used eq bands
|
||||
int channels; // Number of channels
|
||||
float gain_factor; // applied at output to avoid clipping
|
||||
double p[KM];
|
||||
} af_equalizer_t;
|
||||
|
||||
// 2nd order Band-pass Filter design
|
||||
@@ -84,7 +86,7 @@ static void bp2(float* a, float* b, float fc, float q){
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_equalizer_t* s = (af_equalizer_t*)af->setup;
|
||||
af_equalizer_t* s = (af_equalizer_t*)af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
@@ -97,7 +99,7 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
// Calculate number of active filters
|
||||
s->K=KM;
|
||||
@@ -105,7 +107,7 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
s->K--;
|
||||
|
||||
if(s->K != KM)
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO, "[equalizer] Limiting the number of filters to"
|
||||
MP_INFO(af, "[equalizer] Limiting the number of filters to"
|
||||
" %i due to low sample rate.\n",s->K);
|
||||
|
||||
// Generate filter taps
|
||||
@@ -113,7 +115,7 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
bp2(s->a[k],s->b[k],F[k]/((float)af->data->rate),Q);
|
||||
|
||||
// Calculate how much this plugin adds to the overall time delay
|
||||
af->delay = 2 * af->data->nch * af->data->bps;
|
||||
af->delay = 2.0 / (double)af->data->rate;
|
||||
|
||||
// Calculate gain factor to prevent clipping at output
|
||||
for(k=0;k<AF_NCH;k++)
|
||||
@@ -135,43 +137,23 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
|
||||
return af_test_output(af,arg);
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
float g[10]={0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0};
|
||||
int i,j;
|
||||
sscanf((char*)arg,"%f:%f:%f:%f:%f:%f:%f:%f:%f:%f", &g[0], &g[1],
|
||||
&g[2], &g[3], &g[4], &g[5], &g[6], &g[7], &g[8] ,&g[9]);
|
||||
for(i=0;i<AF_NCH;i++){
|
||||
for(j=0;j<KM;j++){
|
||||
((af_equalizer_t*)af->setup)->g[i][j] =
|
||||
pow(10.0,clamp(g[j],G_MIN,G_MAX)/20.0)-1.0;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_equalizer_t* s = (af_equalizer_t*)af->setup; // Setup
|
||||
af_equalizer_t* s = (af_equalizer_t*)af->priv; // Setup
|
||||
uint32_t ci = af->data->nch; // Index for channels
|
||||
uint32_t nch = af->data->nch; // Number of channels
|
||||
|
||||
while(ci--){
|
||||
float* g = s->g[ci]; // Gain factor
|
||||
float* in = ((float*)c->audio)+ci;
|
||||
float* out = ((float*)c->audio)+ci;
|
||||
float* end = in + c->len/4; // Block loop end
|
||||
float* in = ((float*)c->planes[0])+ci;
|
||||
float* out = ((float*)c->planes[0])+ci;
|
||||
float* end = in + c->samples*c->nch; // Block loop end
|
||||
|
||||
while(in < end){
|
||||
register int k = 0; // Frequency band index
|
||||
@@ -195,28 +177,33 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
out+=nch;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_equalizer_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
af->filter=filter;
|
||||
af_equalizer_t *priv = af->priv;
|
||||
for(int i=0;i<AF_NCH;i++){
|
||||
for(int j=0;j<KM;j++){
|
||||
priv->g[i][j] = pow(10.0,MPCLAMP(priv->p[j],G_MIN,G_MAX)/20.0)-1.0;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_equalizer_t
|
||||
struct af_info af_info_equalizer = {
|
||||
"Equalizer audio filter",
|
||||
"equalizer",
|
||||
"Anders",
|
||||
"",
|
||||
AF_FLAGS_NOT_REENTRANT,
|
||||
af_open
|
||||
.info = "Equalizer audio filter",
|
||||
.name = "equalizer",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_equalizer_t),
|
||||
.options = (const struct m_option[]) {
|
||||
#define BAND(n) OPT_DOUBLE("e" #n, p[n], 0)
|
||||
BAND(0), BAND(1), BAND(2), BAND(3), BAND(4),
|
||||
BAND(5), BAND(6), BAND(7), BAND(8), BAND(9),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+36
-80
@@ -7,6 +7,8 @@
|
||||
* the area changes),
|
||||
* the rest is payload (non-interleaved).
|
||||
*
|
||||
* Authors: Anders; Gustavo Sverzut Barbieri <gustavo.barbieri@ic.unicamp.br>
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
@@ -37,9 +39,11 @@
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "osdep/io.h"
|
||||
|
||||
#include "talloc.h"
|
||||
#include "af.h"
|
||||
#include "mpvcore/path.h"
|
||||
#include "options/path.h"
|
||||
|
||||
#define DEF_SZ 512 // default buffer size (in samples)
|
||||
#define SHARED_FILE "mpv-af_export" /* default file name
|
||||
@@ -67,7 +71,7 @@ typedef struct af_export_s
|
||||
*/
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_export_t* s = af->setup;
|
||||
af_export_t* s = af->priv;
|
||||
switch (cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
int i=0;
|
||||
@@ -86,29 +90,25 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
|
||||
// Accept only int16_t as input format (which sucks)
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16_NE);
|
||||
|
||||
// If buffer length isn't set, set it to the default value
|
||||
if(s->sz == 0)
|
||||
s->sz = DEF_SZ;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
|
||||
// Allocate new buffers (as one continuous block)
|
||||
s->buf[0] = calloc(s->sz*af->data->nch, af->data->bps);
|
||||
if(NULL == s->buf[0])
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Out of memory\n");
|
||||
MP_FATAL(af, "[export] Out of memory\n");
|
||||
for(i = 1; i < af->data->nch; i++)
|
||||
s->buf[i] = (uint8_t *)s->buf[0] + i*s->sz*af->data->bps;
|
||||
|
||||
if (!s->filename) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] No filename set.\n");
|
||||
MP_FATAL(af, "[export] No filename set.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
// Init memory mapping
|
||||
s->fd = open(s->filename, O_RDWR | O_CREAT | O_TRUNC, 0640);
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO, "[export] Exporting to file: %s\n", s->filename);
|
||||
s->fd = open(s->filename, O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0640);
|
||||
MP_INFO(af, "[export] Exporting to file: %s\n", s->filename);
|
||||
if(s->fd < 0) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Could not open/create file: %s\n",
|
||||
MP_FATAL(af, "[export] Could not open/create file: %s\n",
|
||||
s->filename);
|
||||
return AF_ERROR;
|
||||
}
|
||||
@@ -125,8 +125,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
// mmap size
|
||||
s->mmap_area = mmap(0, mapsize, PROT_READ|PROT_WRITE,MAP_SHARED, s->fd, 0);
|
||||
if(s->mmap_area == NULL)
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[export] Could not mmap file %s\n", s->filename);
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO, "[export] Memory mapped to file: %s (%p)\n",
|
||||
MP_FATAL(af, "[export] Could not mmap file %s\n", s->filename);
|
||||
MP_INFO(af, "[export] Memory mapped to file: %s (%p)\n",
|
||||
s->filename, s->mmap_area);
|
||||
|
||||
// Initialize header
|
||||
@@ -137,41 +137,6 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
// Use test_output to return FALSE if necessary
|
||||
return af_test_output(af, (struct mp_audio*)arg);
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
int i=0;
|
||||
char *str = arg;
|
||||
|
||||
if (!str){
|
||||
talloc_free(s->filename);
|
||||
|
||||
s->filename = mp_find_user_config_file(SHARED_FILE);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
while((str[i]) && (str[i] != ':'))
|
||||
i++;
|
||||
|
||||
talloc_free(s->filename);
|
||||
|
||||
s->filename = talloc_array_size(NULL, 1, i + 1);
|
||||
memcpy(s->filename, str, i);
|
||||
s->filename[i] = 0;
|
||||
|
||||
sscanf(str + i + 1, "%d", &(s->sz));
|
||||
|
||||
return af->control(af, AF_CONTROL_EXPORT_SZ | AF_CONTROL_SET, &s->sz);
|
||||
}
|
||||
case AF_CONTROL_EXPORT_SZ | AF_CONTROL_SET:
|
||||
s->sz = * (int *) arg;
|
||||
if((s->sz <= 0) || (s->sz > 2048))
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[export] Buffer size must be between"
|
||||
" 1 and 2048\n" );
|
||||
|
||||
return AF_OK;
|
||||
case AF_CONTROL_EXPORT_SZ | AF_CONTROL_GET:
|
||||
*(int*) arg = s->sz;
|
||||
return AF_OK;
|
||||
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
@@ -181,11 +146,7 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
*/
|
||||
static void uninit( struct af_instance* af )
|
||||
{
|
||||
free(af->data);
|
||||
af->data = NULL;
|
||||
|
||||
if(af->setup){
|
||||
af_export_t* s = af->setup;
|
||||
af_export_t* s = af->priv;
|
||||
if (s->buf)
|
||||
free(s->buf[0]);
|
||||
|
||||
@@ -195,25 +156,19 @@ static void uninit( struct af_instance* af )
|
||||
|
||||
if(s->fd > -1)
|
||||
close(s->fd);
|
||||
|
||||
talloc_free(s->filename);
|
||||
|
||||
free(af->setup);
|
||||
af->setup = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Filter data through filter
|
||||
af audio filter instance
|
||||
data audio data
|
||||
*/
|
||||
static struct mp_audio* play( struct af_instance* af, struct mp_audio* data )
|
||||
static int filter( struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_export_t* s = af->setup; // Setup for this instance
|
||||
int16_t* a = c->audio; // Incomming sound
|
||||
af_export_t* s = af->priv; // Setup for this instance
|
||||
int16_t* a = c->planes[0]; // Incomming sound
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = c->len/c->bps; // Number of sample in data chunk
|
||||
int len = c->samples*c->nch; // Number of sample in data chunk
|
||||
int sz = s->sz; // buffer size (in samples)
|
||||
int flag = 0; // Set to 1 if buffer is filled
|
||||
|
||||
@@ -244,8 +199,7 @@ static struct mp_audio* play( struct af_instance* af, struct mp_audio* data )
|
||||
&(s->count), sizeof(s->count));
|
||||
}
|
||||
|
||||
// We don't modify data, just export it
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Allocate memory and set function pointers
|
||||
@@ -256,24 +210,26 @@ static int af_open( struct af_instance* af )
|
||||
{
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->play = play;
|
||||
af->mul=1;
|
||||
af->data = calloc(1, sizeof(struct mp_audio));
|
||||
af->setup = calloc(1, sizeof(af_export_t));
|
||||
if((af->data == NULL) || (af->setup == NULL))
|
||||
return AF_ERROR;
|
||||
af->filter = filter;
|
||||
af_export_t *priv = af->priv;
|
||||
|
||||
((af_export_t *)af->setup)->filename = mp_find_user_config_file(SHARED_FILE);
|
||||
if (!priv->filename || !priv->filename[0]) {
|
||||
MP_FATAL(af, "no export filename given");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_export_t
|
||||
struct af_info af_info_export = {
|
||||
"Sound export filter",
|
||||
"export",
|
||||
"Anders; Gustavo Sverzut Barbieri <gustavo.barbieri@ic.unicamp.br>",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "Sound export filter",
|
||||
.name = "export",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_export_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("filename", filename, 0),
|
||||
OPT_INTRANGE("buffersamples", sz, 0, 1, 2048, OPTDEF_INT(DEF_SZ)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
@@ -34,56 +35,42 @@ typedef struct af_extrastereo_s
|
||||
float mul;
|
||||
}af_extrastereo_t;
|
||||
|
||||
static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data);
|
||||
static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data);
|
||||
static int play_s16(struct af_instance* af, struct mp_audio* data, int f);
|
||||
static int play_float(struct af_instance* af, struct mp_audio* data, int f);
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_extrastereo_t* s = (af_extrastereo_t*)af->setup;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_force_interleaved_format(af->data);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
if (af->data->format == AF_FORMAT_FLOAT_NE)
|
||||
if (af->data->format == AF_FORMAT_FLOAT)
|
||||
{
|
||||
af->play = play_float;
|
||||
af->filter = play_float;
|
||||
}// else
|
||||
{
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16_NE);
|
||||
af->play = play_s16;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
af->filter = play_s16;
|
||||
}
|
||||
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
float f;
|
||||
sscanf((char*)arg,"%f", &f);
|
||||
s->mul = f;
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data)
|
||||
static int play_s16(struct af_instance* af, struct mp_audio* data, int f)
|
||||
{
|
||||
af_extrastereo_t *s = af->setup;
|
||||
af_extrastereo_t *s = af->priv;
|
||||
register int i = 0;
|
||||
int16_t *a = (int16_t*)data->audio; // Audio data
|
||||
int len = data->len/2; // Number of samples
|
||||
int16_t *a = (int16_t*)data->planes[0]; // Audio data
|
||||
int len = data->samples*data->nch; // Number of samples
|
||||
int avg, l, r;
|
||||
|
||||
for (i = 0; i < len; i+=2)
|
||||
@@ -93,19 +80,19 @@ static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data)
|
||||
l = avg + (int)(s->mul * (a[i] - avg));
|
||||
r = avg + (int)(s->mul * (a[i + 1] - avg));
|
||||
|
||||
a[i] = clamp(l, SHRT_MIN, SHRT_MAX);
|
||||
a[i + 1] = clamp(r, SHRT_MIN, SHRT_MAX);
|
||||
a[i] = MPCLAMP(l, SHRT_MIN, SHRT_MAX);
|
||||
a[i + 1] = MPCLAMP(r, SHRT_MIN, SHRT_MAX);
|
||||
}
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data)
|
||||
static int play_float(struct af_instance* af, struct mp_audio* data, int f)
|
||||
{
|
||||
af_extrastereo_t *s = af->setup;
|
||||
af_extrastereo_t *s = af->priv;
|
||||
register int i = 0;
|
||||
float *a = (float*)data->audio; // Audio data
|
||||
int len = data->len/4; // Number of samples
|
||||
float *a = (float*)data->planes[0]; // Audio data
|
||||
int len = data->samples * data->nch; // Number of samples
|
||||
float avg, l, r;
|
||||
|
||||
for (i = 0; i < len; i+=2)
|
||||
@@ -119,30 +106,26 @@ static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data
|
||||
a[i + 1] = af_softclip(r);
|
||||
}
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play_s16;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_extrastereo_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
af->filter=play_s16;
|
||||
|
||||
((af_extrastereo_t*)af->setup)->mul = 2.5;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_extrastereo_t
|
||||
struct af_info af_info_extrastereo = {
|
||||
"Increase difference between audio channels",
|
||||
"extrastereo",
|
||||
"Alex Beregszaszi & Pierre Lombard",
|
||||
"",
|
||||
AF_FLAGS_NOT_REENTRANT,
|
||||
af_open
|
||||
.info = "Increase difference between audio channels",
|
||||
.name = "extrastereo",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_extrastereo_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOAT("mul", mul, 0, OPTDEF_FLOAT(2.5)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* mpv is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "mpvcore/m_option.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
|
||||
struct priv {
|
||||
struct m_config *config;
|
||||
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct mp_chmap in_channels;
|
||||
int out_format;
|
||||
int out_srate;
|
||||
struct mp_chmap out_channels;
|
||||
|
||||
int fail;
|
||||
|
||||
struct mp_audio data;
|
||||
struct mp_audio temp;
|
||||
};
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
|
||||
if (priv->in_channels.num)
|
||||
mp_audio_set_channels(in, &priv->in_channels);
|
||||
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
|
||||
mp_audio_copy_config(out, in);
|
||||
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
|
||||
if (priv->out_channels.num)
|
||||
mp_audio_set_channels(out, &priv->out_channels);
|
||||
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
|
||||
if (in->nch != out->nch || in->bps != out->bps) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR,
|
||||
"[af_force] Forced input/output formats are incompatible.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (priv->fail) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[af_force] Failing on purpose.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static struct mp_audio *play(struct af_instance *af, struct mp_audio *data)
|
||||
{
|
||||
struct priv *priv = af->priv;
|
||||
struct mp_audio *r = &priv->temp;
|
||||
|
||||
*r = *af->data;
|
||||
r->audio = data->audio;
|
||||
r->len = data->len;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->play = play;
|
||||
af->mul = 1;
|
||||
struct priv *priv = af->priv;
|
||||
af->data = &priv->data;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
struct af_info af_info_force = {
|
||||
"Force audio format",
|
||||
"force",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.in_format = AF_FORMAT_UNKNOWN,
|
||||
.out_format = AF_FORMAT_UNKNOWN,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHMAP("channels", in_channels, CONF_MIN, .min = 0),
|
||||
OPT_AUDIOFORMAT("out-format", out_format, 0),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHMAP("out-channels", out_channels, CONF_MIN, .min = 0),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+95
-457
@@ -1,494 +1,132 @@
|
||||
/*
|
||||
* This audio filter changes the format of a data block. Valid
|
||||
* formats are: AFMT_U8, AFMT_S8, AFMT_S16_LE, AFMT_S16_BE
|
||||
* AFMT_U16_LE, AFMT_U16_BE, AFMT_S32_LE and AFMT_S32_BE.
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* 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.
|
||||
*
|
||||
* MPlayer is distributed in the hope that it will be useful,
|
||||
* 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
* with mpv. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "config.h"
|
||||
#include <libavutil/common.h>
|
||||
|
||||
#include "options/m_option.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "af.h"
|
||||
#include "compat/mpbswap.h"
|
||||
|
||||
/* Functions used by play to convert the input audio to the correct
|
||||
format */
|
||||
struct priv {
|
||||
struct m_config *config;
|
||||
|
||||
// Switch endianness
|
||||
static void endian(void* in, void* out, int len, int bps);
|
||||
// From signed to unsigned and the other way
|
||||
static void si2us(void* data, int len, int bps);
|
||||
// Change the number of bits per sample
|
||||
static void change_bps(void* in, void* out, int len, int inbps, int outbps);
|
||||
// From float to int signed
|
||||
static void float2int(float* in, void* out, int len, int bps);
|
||||
// From signed int to float
|
||||
static void int2float(void* in, float* out, int len, int bps);
|
||||
int in_format;
|
||||
int in_srate;
|
||||
struct mp_chmap in_channels;
|
||||
int out_format;
|
||||
int out_srate;
|
||||
struct mp_chmap out_channels;
|
||||
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data);
|
||||
static struct mp_audio* play_swapendian(struct af_instance* af, struct mp_audio* data);
|
||||
static struct mp_audio* play_float_s16(struct af_instance* af, struct mp_audio* data);
|
||||
static struct mp_audio* play_s16_float(struct af_instance* af, struct mp_audio* data);
|
||||
|
||||
// Helper functions to check sanity for input arguments
|
||||
|
||||
// Sanity check for bytes per sample
|
||||
static int check_bps(int bps)
|
||||
{
|
||||
if(bps != 4 && bps != 3 && bps != 2 && bps != 1){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[format] The number of bytes per sample"
|
||||
" must be 1, 2, 3 or 4. Current value is %i \n",bps);
|
||||
return AF_ERROR;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Check for unsupported formats
|
||||
static int check_format(int format)
|
||||
{
|
||||
char buf[256];
|
||||
if ((format & AF_FORMAT_SPECIAL_MASK) == 0)
|
||||
return AF_OK;
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[format] Sample format %s not yet supported \n",
|
||||
af_fmt2str(format,buf,256));
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
static bool test_conversion(int src_format, int dst_format)
|
||||
{
|
||||
// This is the fallback conversion filter, so this filter is always
|
||||
// inserted on format mismatches if no other filter can handle it.
|
||||
// Initializing the filter might still fail.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
char buf1[256];
|
||||
char buf2[256];
|
||||
struct mp_audio *data = arg;
|
||||
int supported_ac3 = 0;
|
||||
|
||||
// Make sure this filter isn't redundant
|
||||
if(af->data->format == data->format)
|
||||
return AF_DETACH;
|
||||
|
||||
// A bit complex because we can convert AC3
|
||||
// to generic iec61937 but not the other way
|
||||
// round.
|
||||
if (AF_FORMAT_IS_AC3(af->data->format))
|
||||
supported_ac3 = AF_FORMAT_IS_AC3(data->format);
|
||||
else if (AF_FORMAT_IS_IEC61937(af->data->format))
|
||||
supported_ac3 = AF_FORMAT_IS_IEC61937(data->format);
|
||||
|
||||
// Allow trivial AC3-endianness conversion
|
||||
if (!supported_ac3)
|
||||
// Check for errors in configuration
|
||||
if((AF_OK != check_bps(data->bps)) ||
|
||||
(AF_OK != check_format(data->format)) ||
|
||||
(AF_OK != check_bps(af->data->bps)) ||
|
||||
(AF_OK != check_format(af->data->format)))
|
||||
return AF_ERROR;
|
||||
|
||||
af_fmt2str(data->format,buf1,256);
|
||||
af_fmt2str(af->data->format,buf2,256);
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[format] Changing sample format from %s to %s\n",
|
||||
buf1, buf2);
|
||||
|
||||
af->data->rate = data->rate;
|
||||
mp_audio_set_channels(af->data, &data->channels);
|
||||
af->mul = (double)af->data->bps / data->bps;
|
||||
|
||||
af->play = play; // set default
|
||||
|
||||
// look whether only endianness differences are there
|
||||
if ((af->data->format & ~AF_FORMAT_END_MASK) ==
|
||||
(data->format & ~AF_FORMAT_END_MASK))
|
||||
{
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[format] Accelerated endianness conversion only\n");
|
||||
af->play = play_swapendian;
|
||||
}
|
||||
if ((data->format == AF_FORMAT_FLOAT_NE) &&
|
||||
(af->data->format == AF_FORMAT_S16_NE))
|
||||
{
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[format] Accelerated %s to %s conversion\n",
|
||||
buf1, buf2);
|
||||
af->play = play_float_s16;
|
||||
}
|
||||
if ((data->format == AF_FORMAT_S16_NE) &&
|
||||
(af->data->format == AF_FORMAT_FLOAT_NE))
|
||||
{
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[format] Accelerated %s to %s conversion\n",
|
||||
buf1, buf2);
|
||||
af->play = play_s16_float;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
int format = af_str2fmt_short(bstr0(arg));
|
||||
if (!format) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[format] %s is not a valid format\n", (char *)arg);
|
||||
return AF_ERROR;
|
||||
}
|
||||
if(AF_OK != af->control(af, AF_CONTROL_FORMAT_FMT | AF_CONTROL_SET,&format))
|
||||
return AF_ERROR;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_FORMAT_FMT | AF_CONTROL_SET:{
|
||||
// Check for errors in configuration
|
||||
if(!AF_FORMAT_IS_AC3(*(int*)arg) && AF_OK != check_format(*(int*)arg))
|
||||
return AF_ERROR;
|
||||
|
||||
mp_audio_set_format(af->data, *(int*)arg);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
if (af->data)
|
||||
free(af->data->audio);
|
||||
free(af->data);
|
||||
af->setup = 0;
|
||||
}
|
||||
|
||||
static struct mp_audio* play_swapendian(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
struct mp_audio* l = af->data; // Local data
|
||||
struct mp_audio* c = data; // Current working data
|
||||
int len = c->len/c->bps; // Length in samples of current audio block
|
||||
|
||||
if(AF_OK != RESIZE_LOCAL_BUFFER(af,data))
|
||||
return NULL;
|
||||
|
||||
endian(c->audio,l->audio,len,c->bps);
|
||||
|
||||
c->audio = l->audio;
|
||||
mp_audio_set_format(c, l->format);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static struct mp_audio* play_float_s16(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
struct mp_audio* l = af->data; // Local data
|
||||
struct mp_audio* c = data; // Current working data
|
||||
int len = c->len/4; // Length in samples of current audio block
|
||||
|
||||
if(AF_OK != RESIZE_LOCAL_BUFFER(af,data))
|
||||
return NULL;
|
||||
|
||||
float2int(c->audio, l->audio, len, 2);
|
||||
|
||||
c->audio = l->audio;
|
||||
mp_audio_set_format(c, l->format);
|
||||
c->len = len*2;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static struct mp_audio* play_s16_float(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
struct mp_audio* l = af->data; // Local data
|
||||
struct mp_audio* c = data; // Current working data
|
||||
int len = c->len/2; // Length in samples of current audio block
|
||||
|
||||
if(AF_OK != RESIZE_LOCAL_BUFFER(af,data))
|
||||
return NULL;
|
||||
|
||||
int2float(c->audio, l->audio, len, 2);
|
||||
|
||||
c->audio = l->audio;
|
||||
mp_audio_set_format(c, l->format);
|
||||
c->len = len*4;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
struct mp_audio* l = af->data; // Local data
|
||||
struct mp_audio* c = data; // Current working data
|
||||
int len = c->len/c->bps; // Length in samples of current audio block
|
||||
|
||||
if(AF_OK != RESIZE_LOCAL_BUFFER(af,data))
|
||||
return NULL;
|
||||
|
||||
// Change to cpu native endian format
|
||||
if((c->format&AF_FORMAT_END_MASK)!=AF_FORMAT_NE)
|
||||
endian(c->audio,c->audio,len,c->bps);
|
||||
|
||||
// Conversion table
|
||||
if((c->format & AF_FORMAT_POINT_MASK) == AF_FORMAT_F) {
|
||||
float2int(c->audio, l->audio, len, l->bps);
|
||||
if((l->format&AF_FORMAT_SIGN_MASK) == AF_FORMAT_US)
|
||||
si2us(l->audio,len,l->bps);
|
||||
} else {
|
||||
// Input must be int
|
||||
|
||||
// Change signed/unsigned
|
||||
if((c->format&AF_FORMAT_SIGN_MASK) != (l->format&AF_FORMAT_SIGN_MASK)){
|
||||
si2us(c->audio,len,c->bps);
|
||||
}
|
||||
// Convert to special formats
|
||||
switch(l->format&AF_FORMAT_POINT_MASK){
|
||||
case(AF_FORMAT_F):
|
||||
int2float(c->audio, l->audio, len, c->bps);
|
||||
break;
|
||||
default:
|
||||
// Change the number of bits
|
||||
if(c->bps != l->bps)
|
||||
change_bps(c->audio,l->audio,len,c->bps,l->bps);
|
||||
else
|
||||
memcpy(l->audio,c->audio,len*c->bps);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Switch from cpu native endian to the correct endianness
|
||||
if((l->format&AF_FORMAT_END_MASK)!=AF_FORMAT_NE)
|
||||
endian(l->audio,l->audio,len,l->bps);
|
||||
|
||||
// Set output data
|
||||
c->audio = l->audio;
|
||||
mp_audio_set_format(c, l->format);
|
||||
c->len = len*l->bps;
|
||||
return c;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
if(af->data == NULL)
|
||||
return AF_ERROR;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
struct af_info af_info_format = {
|
||||
"Sample format conversion",
|
||||
"format",
|
||||
"Anders",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open,
|
||||
.test_conversion = test_conversion,
|
||||
int fail;
|
||||
};
|
||||
|
||||
static inline uint32_t load24bit(void* data, int pos) {
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
return (((uint32_t)((uint8_t*)data)[3*pos])<<24) |
|
||||
(((uint32_t)((uint8_t*)data)[3*pos+1])<<16) |
|
||||
(((uint32_t)((uint8_t*)data)[3*pos+2])<<8);
|
||||
#else
|
||||
return (((uint32_t)((uint8_t*)data)[3*pos])<<8) |
|
||||
(((uint32_t)((uint8_t*)data)[3*pos+1])<<16) |
|
||||
(((uint32_t)((uint8_t*)data)[3*pos+2])<<24);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void store24bit(void* data, int pos, uint32_t expanded_value) {
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
((uint8_t*)data)[3*pos]=expanded_value>>24;
|
||||
((uint8_t*)data)[3*pos+1]=expanded_value>>16;
|
||||
((uint8_t*)data)[3*pos+2]=expanded_value>>8;
|
||||
#else
|
||||
((uint8_t*)data)[3*pos]=expanded_value>>8;
|
||||
((uint8_t*)data)[3*pos+1]=expanded_value>>16;
|
||||
((uint8_t*)data)[3*pos+2]=expanded_value>>24;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Function implementations used by play
|
||||
static void endian(void* in, void* out, int len, int bps)
|
||||
static void force_in_params(struct af_instance *af, struct mp_audio *in)
|
||||
{
|
||||
register int i;
|
||||
switch(bps){
|
||||
case(2):{
|
||||
for(i=0;i<len;i++){
|
||||
((uint16_t*)out)[i]=bswap_16(((uint16_t*)in)[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case(3):{
|
||||
register uint8_t s;
|
||||
for(i=0;i<len;i++){
|
||||
s=((uint8_t*)in)[3*i];
|
||||
((uint8_t*)out)[3*i]=((uint8_t*)in)[3*i+2];
|
||||
if (in != out)
|
||||
((uint8_t*)out)[3*i+1]=((uint8_t*)in)[3*i+1];
|
||||
((uint8_t*)out)[3*i+2]=s;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case(4):{
|
||||
for(i=0;i<len;i++){
|
||||
((uint32_t*)out)[i]=bswap_32(((uint32_t*)in)[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (priv->in_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(in, priv->in_format);
|
||||
|
||||
if (priv->in_channels.num)
|
||||
mp_audio_set_channels(in, &priv->in_channels);
|
||||
|
||||
if (priv->in_srate)
|
||||
in->rate = priv->in_srate;
|
||||
}
|
||||
|
||||
static void si2us(void* data, int len, int bps)
|
||||
static void force_out_params(struct af_instance *af, struct mp_audio *out)
|
||||
{
|
||||
register long i = -(len * bps);
|
||||
register uint8_t *p = &((uint8_t *)data)[len * bps];
|
||||
#if AF_FORMAT_NE == AF_FORMAT_LE
|
||||
p += bps - 1;
|
||||
#endif
|
||||
if (len <= 0) return;
|
||||
do {
|
||||
p[i] ^= 0x80;
|
||||
} while (i += bps);
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
if (priv->out_format != AF_FORMAT_UNKNOWN)
|
||||
mp_audio_set_format(out, priv->out_format);
|
||||
|
||||
if (priv->out_channels.num)
|
||||
mp_audio_set_channels(out, &priv->out_channels);
|
||||
|
||||
if (priv->out_srate)
|
||||
out->rate = priv->out_srate;
|
||||
}
|
||||
|
||||
static void change_bps(void* in, void* out, int len, int inbps, int outbps)
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
register int i;
|
||||
switch(inbps){
|
||||
case(1):
|
||||
switch(outbps){
|
||||
case(2):
|
||||
for(i=0;i<len;i++)
|
||||
((uint16_t*)out)[i]=((uint16_t)((uint8_t*)in)[i])<<8;
|
||||
break;
|
||||
case(3):
|
||||
for(i=0;i<len;i++)
|
||||
store24bit(out, i, ((uint32_t)((uint8_t*)in)[i])<<24);
|
||||
break;
|
||||
case(4):
|
||||
for(i=0;i<len;i++)
|
||||
((uint32_t*)out)[i]=((uint32_t)((uint8_t*)in)[i])<<24;
|
||||
break;
|
||||
struct priv *priv = af->priv;
|
||||
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
force_in_params(af, in);
|
||||
mp_audio_copy_config(out, in);
|
||||
force_out_params(af, out);
|
||||
|
||||
if (in->nch != out->nch || in->bps != out->bps) {
|
||||
MP_ERR(af, "[af_format] Forced input/output formats are incompatible.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (priv->fail) {
|
||||
MP_ERR(af, "[af_format] Failing on purpose.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
break;
|
||||
case(2):
|
||||
switch(outbps){
|
||||
case(1):
|
||||
for(i=0;i<len;i++)
|
||||
((uint8_t*)out)[i]=(uint8_t)((((uint16_t*)in)[i])>>8);
|
||||
break;
|
||||
case(3):
|
||||
for(i=0;i<len;i++)
|
||||
store24bit(out, i, ((uint32_t)((uint16_t*)in)[i])<<16);
|
||||
break;
|
||||
case(4):
|
||||
for(i=0;i<len;i++)
|
||||
((uint32_t*)out)[i]=((uint32_t)((uint16_t*)in)[i])<<16;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case(3):
|
||||
switch(outbps){
|
||||
case(1):
|
||||
for(i=0;i<len;i++)
|
||||
((uint8_t*)out)[i]=(uint8_t)(load24bit(in, i)>>24);
|
||||
break;
|
||||
case(2):
|
||||
for(i=0;i<len;i++)
|
||||
((uint16_t*)out)[i]=(uint16_t)(load24bit(in, i)>>16);
|
||||
break;
|
||||
case(4):
|
||||
for(i=0;i<len;i++)
|
||||
((uint32_t*)out)[i]=(uint32_t)load24bit(in, i);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case(4):
|
||||
switch(outbps){
|
||||
case(1):
|
||||
for(i=0;i<len;i++)
|
||||
((uint8_t*)out)[i]=(uint8_t)((((uint32_t*)in)[i])>>24);
|
||||
break;
|
||||
case(2):
|
||||
for(i=0;i<len;i++)
|
||||
((uint16_t*)out)[i]=(uint16_t)((((uint32_t*)in)[i])>>16);
|
||||
break;
|
||||
case(3):
|
||||
for(i=0;i<len;i++)
|
||||
store24bit(out, i, ((uint32_t*)in)[i]);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void float2int(float* in, void* out, int len, int bps)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
{
|
||||
register int i;
|
||||
switch(bps){
|
||||
case(1):
|
||||
for(i=0;i<len;i++)
|
||||
((int8_t*)out)[i] = lrintf(127.0 * clamp(in[i], -1.0f, +1.0f));
|
||||
break;
|
||||
case(2):
|
||||
for(i=0;i<len;i++)
|
||||
((int16_t*)out)[i] = lrintf(32767.0 * clamp(in[i], -1.0f, +1.0f));
|
||||
break;
|
||||
case(3):
|
||||
for(i=0;i<len;i++)
|
||||
store24bit(out, i, lrintf(2147483647.0 * clamp(in[i], -1.0f, +1.0f)));
|
||||
break;
|
||||
case(4):
|
||||
for(i=0;i<len;i++)
|
||||
((int32_t*)out)[i] = lrintf(2147483647.0 * clamp(in[i], -1.0f, +1.0f));
|
||||
break;
|
||||
}
|
||||
mp_audio_copy_config(data, af->data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void int2float(void* in, float* out, int len, int bps)
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
register int i;
|
||||
switch(bps){
|
||||
case(1):
|
||||
for(i=0;i<len;i++)
|
||||
out[i]=(1.0/128.0)*((int8_t*)in)[i];
|
||||
break;
|
||||
case(2):
|
||||
for(i=0;i<len;i++)
|
||||
out[i]=(1.0/32768.0)*((int16_t*)in)[i];
|
||||
break;
|
||||
case(3):
|
||||
for(i=0;i<len;i++)
|
||||
out[i]=(1.0/2147483648.0)*((int32_t)load24bit(in, i));
|
||||
break;
|
||||
case(4):
|
||||
for(i=0;i<len;i++)
|
||||
out[i]=(1.0/2147483648.0)*((int32_t*)in)[i];
|
||||
break;
|
||||
}
|
||||
af->control = control;
|
||||
af->filter = filter;
|
||||
|
||||
force_in_params(af, af->data);
|
||||
force_out_params(af, af->data);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
struct af_info af_info_format = {
|
||||
.info = "Force audio format",
|
||||
.name = "format",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_AUDIOFORMAT("format", in_format, 0),
|
||||
OPT_INTRANGE("srate", in_srate, 0, 1000, 8*48000),
|
||||
OPT_CHMAP("channels", in_channels, CONF_MIN, .min = 0),
|
||||
OPT_AUDIOFORMAT("out-format", out_format, 0),
|
||||
OPT_INTRANGE("out-srate", out_srate, 0, 1000, 8*48000),
|
||||
OPT_CHMAP("out-channels", out_channels, CONF_MIN, .min = 0),
|
||||
OPT_FLAG("fail", fail, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+46
-72
@@ -3,6 +3,8 @@
|
||||
* encoded rear channels into headphone signal using FIR filtering
|
||||
* with HRTF.
|
||||
*
|
||||
* Author: ylai
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
@@ -69,6 +71,7 @@ typedef struct af_hrtf_s {
|
||||
/* Cyclic position on the ring buffer */
|
||||
int cyc_pos;
|
||||
int print_flag;
|
||||
int mode;
|
||||
} af_hrtf_t;
|
||||
|
||||
/* Convolution on a ring buffer
|
||||
@@ -284,9 +287,8 @@ static inline void update_ch(af_hrtf_t *s, short *in, const int k)
|
||||
/* Initialization and runtime control */
|
||||
static int control(struct af_instance *af, int cmd, void* arg)
|
||||
{
|
||||
af_hrtf_t *s = af->setup;
|
||||
af_hrtf_t *s = af->priv;
|
||||
int test_output_res;
|
||||
char mode;
|
||||
|
||||
switch(cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
@@ -294,8 +296,7 @@ static int control(struct af_instance *af, int cmd, void* arg)
|
||||
if(af->data->rate != 48000) {
|
||||
// automatic samplerate adjustment in the filter chain
|
||||
// is not yet supported.
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR,
|
||||
"[hrtf] ERROR: Sampling rate is not 48000 Hz (%d)!\n",
|
||||
MP_ERR(af, "[hrtf] ERROR: Sampling rate is not 48000 Hz (%d)!\n",
|
||||
af->data->rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
@@ -309,35 +310,12 @@ static int control(struct af_instance *af, int cmd, void* arg)
|
||||
}
|
||||
else if (af->data->nch < 5)
|
||||
mp_audio_set_channels_old(af->data, 5);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16_NE);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
test_output_res = af_test_output(af, (struct mp_audio*)arg);
|
||||
af->mul = 2.0 / af->data->nch;
|
||||
// after testing input set the real output format
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
s->print_flag = 1;
|
||||
return test_output_res;
|
||||
case AF_CONTROL_COMMAND_LINE:
|
||||
sscanf((char*)arg, "%c", &mode);
|
||||
switch(mode) {
|
||||
case 'm':
|
||||
/* Use matrix rear decoding. */
|
||||
s->matrix_mode = 1;
|
||||
break;
|
||||
case 's':
|
||||
/* Input needs matrix decoding. */
|
||||
s->decode_mode = HRTF_MIX_MATRIX2CH;
|
||||
break;
|
||||
case '0':
|
||||
s->matrix_mode = 0;
|
||||
break;
|
||||
default:
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR,
|
||||
"[hrtf] Mode is neither 'm', 's', nor '0' (%c).\n",
|
||||
mode);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->print_flag = 1;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
return AF_UNKNOWN;
|
||||
@@ -346,8 +324,7 @@ static int control(struct af_instance *af, int cmd, void* arg)
|
||||
/* Deallocate memory */
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
if(af->setup) {
|
||||
af_hrtf_t *s = af->setup;
|
||||
af_hrtf_t *s = af->priv;
|
||||
|
||||
free(s->lf);
|
||||
free(s->rf);
|
||||
@@ -362,11 +339,6 @@ static void uninit(struct af_instance *af)
|
||||
free(s->fwrbuf_r);
|
||||
free(s->fwrbuf_lr);
|
||||
free(s->fwrbuf_rr);
|
||||
free(af->setup);
|
||||
}
|
||||
if(af->data)
|
||||
free(af->data->audio);
|
||||
free(af->data);
|
||||
}
|
||||
|
||||
/* Filter data through filter
|
||||
@@ -380,50 +352,44 @@ frequencies).
|
||||
2. A bass compensation is introduced to ensure that 0-200 Hz are not
|
||||
damped (without any real 3D acoustical image, however).
|
||||
*/
|
||||
static struct mp_audio* play(struct af_instance *af, struct mp_audio *data)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
{
|
||||
af_hrtf_t *s = af->setup;
|
||||
short *in = data->audio; // Input audio data
|
||||
af_hrtf_t *s = af->priv;
|
||||
short *in = data->planes[0]; // Input audio data
|
||||
short *out = NULL; // Output audio data
|
||||
short *end = in + data->len / sizeof(short); // Loop end
|
||||
short *end = in + data->samples * data->nch; // Loop end
|
||||
float common, left, right, diff, left_b, right_b;
|
||||
const int dblen = s->dlbuflen, hlen = s->hrflen, blen = s->basslen;
|
||||
|
||||
if(AF_OK != RESIZE_LOCAL_BUFFER(af, data))
|
||||
return NULL;
|
||||
mp_audio_realloc_min(af->data, data->samples);
|
||||
|
||||
if(s->print_flag) {
|
||||
s->print_flag = 0;
|
||||
switch (s->decode_mode) {
|
||||
case HRTF_MIX_51:
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO,
|
||||
"[hrtf] Using HRTF to mix %s discrete surround into "
|
||||
MP_INFO(af, "[hrtf] Using HRTF to mix %s discrete surround into "
|
||||
"L, R channels\n", s->matrix_mode ? "5+1" : "5");
|
||||
break;
|
||||
case HRTF_MIX_STEREO:
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO,
|
||||
"[hrtf] Using HRTF to mix stereo into "
|
||||
MP_INFO(af, "[hrtf] Using HRTF to mix stereo into "
|
||||
"L, R channels\n");
|
||||
break;
|
||||
case HRTF_MIX_MATRIX2CH:
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO,
|
||||
"[hrtf] Using active matrix to decode 2 channel "
|
||||
MP_INFO(af, "[hrtf] Using active matrix to decode 2 channel "
|
||||
"input, HRTF to mix %s matrix surround into "
|
||||
"L, R channels\n", "3/2");
|
||||
break;
|
||||
default:
|
||||
mp_msg(MSGT_AFILTER, MSGL_WARN,
|
||||
"[hrtf] bogus decode_mode: %d\n", s->decode_mode);
|
||||
MP_WARN(af, "[hrtf] bogus decode_mode: %d\n", s->decode_mode);
|
||||
break;
|
||||
}
|
||||
|
||||
if(s->matrix_mode)
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO,
|
||||
"[hrtf] Using active matrix to decode rear center "
|
||||
MP_INFO(af, "[hrtf] Using active matrix to decode rear center "
|
||||
"channel\n");
|
||||
}
|
||||
|
||||
out = af->data->audio;
|
||||
out = af->data->planes[0];
|
||||
|
||||
/* MPlayer's 5 channel layout (notation for the variable):
|
||||
*
|
||||
@@ -563,11 +529,10 @@ static struct mp_audio* play(struct af_instance *af, struct mp_audio *data)
|
||||
}
|
||||
|
||||
/* Set output data */
|
||||
data->audio = af->data->audio;
|
||||
data->len = data->len / data->nch * 2;
|
||||
data->planes[0] = af->data->planes[0];
|
||||
mp_audio_set_num_channels(data, 2);
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int allocate(af_hrtf_t *s)
|
||||
@@ -600,14 +565,9 @@ static int af_open(struct af_instance* af)
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->play = play;
|
||||
af->mul = 1;
|
||||
af->data = calloc(1, sizeof(struct mp_audio));
|
||||
af->setup = calloc(1, sizeof(af_hrtf_t));
|
||||
if((af->data == NULL) || (af->setup == NULL))
|
||||
return AF_ERROR;
|
||||
af->filter = filter;
|
||||
|
||||
s = af->setup;
|
||||
s = af->priv;
|
||||
|
||||
s->dlbuflen = DELAYBUFLEN;
|
||||
s->hrflen = HRTFFILTLEN;
|
||||
@@ -620,10 +580,22 @@ static int af_open(struct af_instance* af)
|
||||
s->matrix_mode = 0;
|
||||
s->decode_mode = HRTF_MIX_51;
|
||||
|
||||
switch (s->mode) {
|
||||
case 0: /* Use matrix rear decoding. */
|
||||
s->matrix_mode = 1;
|
||||
break;
|
||||
case 1: /* Input needs matrix decoding. */
|
||||
s->decode_mode = HRTF_MIX_MATRIX2CH;
|
||||
break;
|
||||
case 2:
|
||||
s->matrix_mode = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
s->print_flag = 1;
|
||||
|
||||
if (allocate(s) != 0) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[hrtf] Memory allocation error.\n");
|
||||
MP_ERR(af, "[hrtf] Memory allocation error.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
@@ -642,13 +614,13 @@ static int af_open(struct af_instance* af)
|
||||
s->cr_ir = cr_filt + (s->cr_o = pulse_detect(cr_filt));
|
||||
|
||||
if((s->ba_ir = malloc(s->basslen * sizeof(float))) == NULL) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[hrtf] Memory allocation error.\n");
|
||||
MP_ERR(af, "[hrtf] Memory allocation error.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
fc = 2.0 * BASSFILTFREQ / (float)af->data->rate;
|
||||
if(af_filter_design_fir(s->basslen, s->ba_ir, &fc, LP | KAISER, 4 * M_PI) ==
|
||||
-1) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[hrtf] Unable to design low-pass "
|
||||
MP_ERR(af, "[hrtf] Unable to design low-pass "
|
||||
"filter.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
@@ -658,12 +630,14 @@ static int af_open(struct af_instance* af)
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* Description of this filter */
|
||||
#define OPT_BASE_STRUCT af_hrtf_t
|
||||
struct af_info af_info_hrtf = {
|
||||
"HRTF Headphone",
|
||||
"hrtf",
|
||||
"ylai",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "HRTF Headphone",
|
||||
.name = "hrtf",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_hrtf_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_CHOICE("mode", mode, 0, ({"m", 0}, {"s", 1}, {"0", 2})),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+10
-25
@@ -35,25 +35,19 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int play(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
float* a = c->audio; // Audio data
|
||||
int len = c->len/4; // Number of samples in current audio block
|
||||
float* a = c->planes[0]; // Audio data
|
||||
int nch = c->nch; // Number of channels
|
||||
int len = c->samples*nch; // Number of samples in current audio block
|
||||
register int i;
|
||||
|
||||
/*
|
||||
@@ -68,29 +62,20 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
a[i+1]=a[i];
|
||||
}
|
||||
|
||||
return c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->play = play;
|
||||
af->mul = 1;
|
||||
af->data = calloc(1,sizeof(struct mp_audio));
|
||||
|
||||
if(af->data == NULL)
|
||||
return AF_ERROR;
|
||||
|
||||
af->filter = play;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
struct af_info af_info_karaoke = {
|
||||
"Simple karaoke/voice-removal audio filter",
|
||||
"karaoke",
|
||||
"Reynaldo H. Verdejo Pinochet",
|
||||
"",
|
||||
AF_FLAGS_NOT_REENTRANT,
|
||||
af_open
|
||||
.info = "Simple karaoke/voice-removal audio filter",
|
||||
.name = "karaoke",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
};
|
||||
+147
-220
@@ -42,6 +42,8 @@
|
||||
|
||||
#include "af.h"
|
||||
|
||||
#define _(x) (x)
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Filter specific data */
|
||||
@@ -63,6 +65,7 @@ typedef struct af_ladspa_s
|
||||
|
||||
char *file;
|
||||
char *label;
|
||||
char *controls;
|
||||
|
||||
char *myname; /**< It's easy to have a concatenation of file and label */
|
||||
|
||||
@@ -100,15 +103,18 @@ static int af_ladspa_malloc_failed(char*);
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Description */
|
||||
|
||||
#define OPT_BASE_STRUCT af_ladspa_t
|
||||
struct af_info af_info_ladspa = {
|
||||
"LADSPA plugin loader",
|
||||
"ladspa",
|
||||
"Ivo van Poorten",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "LADSPA plugin loader",
|
||||
.name = "ladspa",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_ladspa_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("file", file, 0),
|
||||
OPT_STRING("label", label, 0),
|
||||
OPT_STRING("controls", controls, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
@@ -131,7 +137,8 @@ struct af_info af_info_ladspa = {
|
||||
* configuration. Else, it returns AF_ERROR.
|
||||
*/
|
||||
|
||||
static int af_ladspa_parse_plugin(af_ladspa_t *setup) {
|
||||
static int af_ladspa_parse_plugin(struct af_instance *af) {
|
||||
af_ladspa_t *setup = af->priv;
|
||||
int p, i;
|
||||
const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
|
||||
LADSPA_PortDescriptor d;
|
||||
@@ -211,30 +218,30 @@ static int af_ladspa_parse_plugin(af_ladspa_t *setup) {
|
||||
}
|
||||
|
||||
if (setup->ninputs == 0) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_WARN, "%s: %s\n", setup->myname,
|
||||
MP_WARN(af, "%s: %s\n", setup->myname,
|
||||
_("WARNING! This LADSPA plugin has no audio inputs.\n The incoming audio signal will be lost."));
|
||||
} else if (setup->ninputs == 1) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: this is a mono effect\n", setup->myname);
|
||||
MP_VERBOSE(af, "%s: this is a mono effect\n", setup->myname);
|
||||
} else if (setup->ninputs == 2) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: this is a stereo effect\n", setup->myname);
|
||||
MP_VERBOSE(af, "%s: this is a stereo effect\n", setup->myname);
|
||||
} else {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: this is a %i-channel effect, "
|
||||
MP_VERBOSE(af, "%s: this is a %i-channel effect, "
|
||||
"support is experimental\n", setup->myname, setup->ninputs);
|
||||
}
|
||||
|
||||
if (setup->noutputs == 0) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname,
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("This LADSPA plugin has no audio outputs."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
if (setup->noutputs != setup->ninputs ) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname,
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("The number of audio inputs and audio outputs of the LADSPA plugin differ."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: this plugin has %d input control(s)\n",
|
||||
MP_VERBOSE(af, "%s: this plugin has %d input control(s)\n",
|
||||
setup->myname, setup->ninputcontrols);
|
||||
|
||||
/* Print list of controls and its range of values it accepts */
|
||||
@@ -242,18 +249,18 @@ static int af_ladspa_parse_plugin(af_ladspa_t *setup) {
|
||||
for (i=0; i<setup->ninputcontrols; i++) {
|
||||
p = setup->inputcontrolsmap[i];
|
||||
hint = pdes->PortRangeHints[p];
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, " --- %d %s [", i, pdes->PortNames[p]);
|
||||
MP_VERBOSE(af, " --- %d %s [", i, pdes->PortNames[p]);
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor)) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%0.2f , ", hint.LowerBound);
|
||||
MP_VERBOSE(af, "%0.2f , ", hint.LowerBound);
|
||||
} else {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "... , ");
|
||||
MP_VERBOSE(af, "... , ");
|
||||
}
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor)) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%0.2f]\n", hint.UpperBound);
|
||||
MP_VERBOSE(af, "%0.2f]\n", hint.UpperBound);
|
||||
} else {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "...]\n");
|
||||
MP_VERBOSE(af, "...]\n");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -299,9 +306,9 @@ static void* mydlopen(const char *filename, int flag) {
|
||||
/* For Windows there's only absolute path support.
|
||||
* If you have a Windows machine, feel free to fix this.
|
||||
* (path separator, shared objects extension, et cetera). */
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "\ton windows, only absolute pathnames "
|
||||
MP_VERBOSE(af, "\ton windows, only absolute pathnames "
|
||||
"are supported\n");
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "\ttrying %s\n", filename);
|
||||
MP_VERBOSE(af, "\ttrying %s\n", filename);
|
||||
return dlopen(filename, flag);
|
||||
#endif
|
||||
|
||||
@@ -342,7 +349,6 @@ static void* mydlopen(const char *filename, int flag) {
|
||||
}
|
||||
strcpy(buf+needslash+(end-start), filename);
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "\ttrying %s\n", buf);
|
||||
result=dlopen(buf, flag);
|
||||
|
||||
free(buf);
|
||||
@@ -356,7 +362,6 @@ static void* mydlopen(const char *filename, int flag) {
|
||||
} /* end if there's a ladspapath */
|
||||
|
||||
/* last resort, just open it again, so the dlerror() message is correct */
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "\ttrying %s\n", filename);
|
||||
return dlopen(filename,flag);
|
||||
}
|
||||
|
||||
@@ -377,24 +382,25 @@ static void* mydlopen(const char *filename, int flag) {
|
||||
* \return Either AF_ERROR or AF_OK, depending on the success of the operation.
|
||||
*/
|
||||
|
||||
static int af_ladspa_load_plugin(af_ladspa_t *setup) {
|
||||
static int af_ladspa_load_plugin(struct af_instance *af) {
|
||||
af_ladspa_t *setup = af->priv;
|
||||
const LADSPA_Descriptor *ladspa_descriptor;
|
||||
LADSPA_Descriptor_Function descriptor_function;
|
||||
int i;
|
||||
|
||||
/* load library */
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: loading ladspa plugin library %s\n",
|
||||
MP_VERBOSE(af, "%s: loading ladspa plugin library %s\n",
|
||||
setup->myname, setup->file);
|
||||
|
||||
setup->libhandle = mydlopen(setup->file, RTLD_NOW);
|
||||
|
||||
if (!setup->libhandle) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s %s\n\t%s\n", setup->myname,
|
||||
MP_ERR(af, "%s: %s %s\n\t%s\n", setup->myname,
|
||||
_("failed to load"), setup->file, dlerror() );
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: library found.\n", setup->myname);
|
||||
MP_VERBOSE(af, "%s: library found.\n", setup->myname);
|
||||
|
||||
/* find descriptor function */
|
||||
dlerror();
|
||||
@@ -402,7 +408,7 @@ static int af_ladspa_load_plugin(af_ladspa_t *setup) {
|
||||
"ladspa_descriptor");
|
||||
|
||||
if (!descriptor_function) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n\t%s\n", setup->myname,
|
||||
MP_ERR(af, "%s: %s\n\t%s\n", setup->myname,
|
||||
_("Couldn't find ladspa_descriptor() function in the specified library file."), dlerror());
|
||||
return AF_ERROR;
|
||||
}
|
||||
@@ -410,33 +416,33 @@ static int af_ladspa_load_plugin(af_ladspa_t *setup) {
|
||||
/* if label == help, list all labels in library and exit */
|
||||
|
||||
if (strcmp(setup->label, "help") == 0) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO, "%s: %s %s:\n", setup->myname,
|
||||
MP_INFO(af, "%s: %s %s:\n", setup->myname,
|
||||
_("available labels in"), setup->file);
|
||||
for (i=0; ; i++) {
|
||||
ladspa_descriptor = descriptor_function(i);
|
||||
if (ladspa_descriptor == NULL) {
|
||||
return AF_ERROR;
|
||||
}
|
||||
mp_msg(MSGT_AFILTER, MSGL_INFO, " %-16s - %s (%lu)\n",
|
||||
MP_INFO(af, " %-16s - %s (%lu)\n",
|
||||
ladspa_descriptor->Label,
|
||||
ladspa_descriptor->Name,
|
||||
ladspa_descriptor->UniqueID);
|
||||
}
|
||||
}
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: looking for label\n", setup->myname);
|
||||
MP_VERBOSE(af, "%s: looking for label\n", setup->myname);
|
||||
|
||||
/* find label in library */
|
||||
for (i=0; ; i++) {
|
||||
ladspa_descriptor = descriptor_function(i);
|
||||
if (ladspa_descriptor == NULL) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname,
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("Couldn't find label in plugin library."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (strcmp(ladspa_descriptor->Label, setup->label) == 0) {
|
||||
setup->plugin_descriptor = ladspa_descriptor;
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: %s found\n", setup->myname,
|
||||
MP_VERBOSE(af, "%s: %s found\n", setup->myname,
|
||||
setup->label);
|
||||
return AF_OK;
|
||||
}
|
||||
@@ -457,7 +463,6 @@ static int af_ladspa_load_plugin(af_ladspa_t *setup) {
|
||||
*/
|
||||
|
||||
static int af_ladspa_malloc_failed(char *myname) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s", myname, "Memory allocation failed.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
@@ -470,13 +475,6 @@ static int af_ladspa_malloc_failed(char *myname) {
|
||||
* Commands:
|
||||
* CONTROL_REINIT Sets the af structure with proper values for number
|
||||
* of channels, rate, format, et cetera.
|
||||
* CONTROL_COMMAND_LINE Parses the suboptions given to this filter
|
||||
* through arg. It first parses the filename and
|
||||
* the label. After that, it loads the filter
|
||||
* and finds out its proprties. Then in continues
|
||||
* parsing the controls given on the commandline,
|
||||
* if any are needed.
|
||||
*
|
||||
* \param af Audio filter instance
|
||||
* \param cmd The command to execute
|
||||
* \param arg Arguments to the command
|
||||
@@ -486,157 +484,20 @@ static int af_ladspa_malloc_failed(char *myname) {
|
||||
*/
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg) {
|
||||
af_ladspa_t *setup = (af_ladspa_t*) af->setup;
|
||||
float val;
|
||||
af_ladspa_t *setup = (af_ladspa_t*) af->priv;
|
||||
|
||||
switch(cmd) {
|
||||
case AF_CONTROL_REINIT:
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: (re)init\n", setup->myname);
|
||||
MP_VERBOSE(af, "%s: (re)init\n", setup->myname);
|
||||
|
||||
if (!arg) return AF_ERROR;
|
||||
|
||||
/* accept FLOAT, let af_format do conversion */
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
|
||||
/* arg->len is not set here yet, so init of buffers and connecting the
|
||||
* filter, has to be done in play() :-/
|
||||
*/
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
return af_test_output(af, (struct mp_audio*)arg);
|
||||
case AF_CONTROL_COMMAND_LINE: {
|
||||
char *buf;
|
||||
char *line = arg;
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: parse suboptions\n", setup->myname);
|
||||
|
||||
/* suboption parser here!
|
||||
* format is (ladspa=)file:label:controls....
|
||||
*/
|
||||
|
||||
if (!line) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname,
|
||||
_("No suboptions specified."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
buf = malloc(strlen(line)+1);
|
||||
if (!buf) return af_ladspa_malloc_failed(setup->myname);
|
||||
|
||||
/* file... */
|
||||
buf[0] = '\0';
|
||||
sscanf(line, "%[^:]", buf);
|
||||
if (buf[0] == '\0') {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname,
|
||||
_("No library file specified."));
|
||||
free(buf);
|
||||
return AF_ERROR;
|
||||
}
|
||||
line += strlen(buf);
|
||||
setup->file = strdup(buf);
|
||||
if (!setup->file) {
|
||||
free(buf);
|
||||
return af_ladspa_malloc_failed(setup->myname);
|
||||
}
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: file --> %s\n", setup->myname,
|
||||
setup->file);
|
||||
if (*line != '\0') line++; /* read ':' */
|
||||
|
||||
/* label... */
|
||||
buf[0] = '\0';
|
||||
sscanf(line, "%[^:]", buf);
|
||||
if (buf[0] == '\0') {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname,
|
||||
_("No filter label specified."));
|
||||
free(buf);
|
||||
return AF_ERROR;
|
||||
}
|
||||
line += strlen(buf);
|
||||
setup->label = strdup(buf);
|
||||
if (!setup->label) {
|
||||
free(buf);
|
||||
return af_ladspa_malloc_failed(setup->myname);
|
||||
}
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: label --> %s\n", setup->myname,
|
||||
setup->label);
|
||||
/* if (*line != '0') line++; */ /* read ':' */
|
||||
|
||||
free(buf); /* no longer needed */
|
||||
|
||||
/* set new setup->myname */
|
||||
|
||||
free(setup->myname);
|
||||
setup->myname = calloc(strlen(af_info_ladspa.name)+strlen(setup->file)+
|
||||
strlen(setup->label)+6, 1);
|
||||
snprintf(setup->myname, strlen(af_info_ladspa.name)+
|
||||
strlen(setup->file)+strlen(setup->label)+6, "%s: (%s:%s)",
|
||||
af_info_ladspa.name, setup->file, setup->label);
|
||||
|
||||
/* load plugin :) */
|
||||
|
||||
if ( af_ladspa_load_plugin(setup) != AF_OK )
|
||||
return AF_ERROR;
|
||||
|
||||
/* see what inputs, outputs and controls this plugin has */
|
||||
if ( af_ladspa_parse_plugin(setup) != AF_OK )
|
||||
return AF_ERROR;
|
||||
|
||||
/* ninputcontrols is set by now, read control values from arg */
|
||||
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
if (!line || *line != ':') {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname,
|
||||
_("Not enough controls specified on the command line."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
line++;
|
||||
if (sscanf(line, "%f", &val) != 1) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "%s: %s\n", setup->myname,
|
||||
_("Not enough controls specified on the command line."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
setup->inputcontrols[setup->inputcontrolsmap[i]] = val;
|
||||
line = strchr(line, ':');
|
||||
}
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: input controls: ", setup->myname);
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%0.4f ",
|
||||
setup->inputcontrols[setup->inputcontrolsmap[i]]);
|
||||
}
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "\n");
|
||||
|
||||
/* check boundaries of inputcontrols */
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: checking boundaries of input controls\n",
|
||||
setup->myname);
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
int p = setup->inputcontrolsmap[i];
|
||||
LADSPA_PortRangeHint hint =
|
||||
setup->plugin_descriptor->PortRangeHints[p];
|
||||
val = setup->inputcontrols[p];
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor) &&
|
||||
val < hint.LowerBound) {
|
||||
mp_tmsg(MSGT_AFILTER, MSGL_ERR, "%s: Input control #%d is below lower boundary of %0.4f.\n",
|
||||
setup->myname, i, hint.LowerBound);
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor) &&
|
||||
val > hint.UpperBound) {
|
||||
mp_tmsg(MSGT_AFILTER, MSGL_ERR, "%s: Input control #%d is above upper boundary of %0.4f.\n",
|
||||
setup->myname, i, hint.UpperBound);
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: all controls have sane values\n",
|
||||
setup->myname);
|
||||
|
||||
/* All is well! */
|
||||
setup->status = AF_OK;
|
||||
|
||||
return AF_OK; }
|
||||
}
|
||||
|
||||
return AF_UNKNOWN;
|
||||
@@ -653,13 +514,12 @@ static int control(struct af_instance *af, int cmd, void *arg) {
|
||||
*/
|
||||
|
||||
static void uninit(struct af_instance *af) {
|
||||
free(af->data);
|
||||
if (af->setup) {
|
||||
af_ladspa_t *setup = (af_ladspa_t*) af->setup;
|
||||
if (af->priv) {
|
||||
af_ladspa_t *setup = (af_ladspa_t*) af->priv;
|
||||
const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
|
||||
|
||||
if (setup->myname) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "%s: cleaning up\n", setup->myname);
|
||||
MP_VERBOSE(af, "%s: cleaning up\n", setup->myname);
|
||||
free(setup->myname);
|
||||
}
|
||||
|
||||
@@ -671,8 +531,6 @@ static void uninit(struct af_instance *af) {
|
||||
free(setup->chhandles);
|
||||
}
|
||||
|
||||
free(setup->file);
|
||||
free(setup->label);
|
||||
free(setup->inputcontrolsmap);
|
||||
free(setup->inputcontrols);
|
||||
free(setup->outputcontrolsmap);
|
||||
@@ -694,9 +552,6 @@ static void uninit(struct af_instance *af) {
|
||||
|
||||
if (setup->libhandle)
|
||||
dlclose(setup->libhandle);
|
||||
|
||||
free(setup);
|
||||
setup = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -710,26 +565,22 @@ static void uninit(struct af_instance *af) {
|
||||
* \return Either AF_ERROR or AF_OK
|
||||
*/
|
||||
|
||||
static struct mp_audio* play(struct af_instance *af, struct mp_audio *data) {
|
||||
af_ladspa_t *setup = af->setup;
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int flags) {
|
||||
af_ladspa_t *setup = af->priv;
|
||||
const LADSPA_Descriptor *pdes = setup->plugin_descriptor;
|
||||
float *audio = (float*)data->audio;
|
||||
int nsamples = data->len/4; /* /4 because it's 32-bit float */
|
||||
float *audio = (float*)data->planes[0];
|
||||
int nsamples = data->samples*data->nch;
|
||||
int nch = data->nch;
|
||||
int rate = data->rate;
|
||||
int i, p;
|
||||
|
||||
if (setup->status !=AF_OK)
|
||||
return data;
|
||||
return -1;
|
||||
|
||||
/* See if it's the first call. If so, setup inbufs/outbufs, instantiate
|
||||
* plugin, connect ports and activate plugin
|
||||
*/
|
||||
|
||||
/* 2004-12-07: Also check if the buffersize has to be changed!
|
||||
* data->len is not constant per se! re-init buffers.
|
||||
*/
|
||||
|
||||
if ( (setup->bufsize != nsamples/nch) || (setup->nch != nch) ) {
|
||||
|
||||
/* if setup->nch==0, it's the first call, if not, something has
|
||||
@@ -737,7 +588,7 @@ static struct mp_audio* play(struct af_instance *af, struct mp_audio *data) {
|
||||
*/
|
||||
|
||||
if (setup->nch != 0) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG3, "%s: bufsize change; free old buffer\n",
|
||||
MP_TRACE(af, "%s: bufsize change; free old buffer\n",
|
||||
setup->myname);
|
||||
|
||||
if(setup->inbufs) {
|
||||
@@ -758,7 +609,7 @@ static struct mp_audio* play(struct af_instance *af, struct mp_audio *data) {
|
||||
setup->inbufs = calloc(nch, sizeof(float*));
|
||||
setup->outbufs = calloc(nch, sizeof(float*));
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG3, "%s: bufsize = %d\n",
|
||||
MP_TRACE(af, "%s: bufsize = %d\n",
|
||||
setup->myname, setup->bufsize);
|
||||
|
||||
for(i=0; i<nch; i++) {
|
||||
@@ -870,7 +721,7 @@ static struct mp_audio* play(struct af_instance *af, struct mp_audio *data) {
|
||||
|
||||
/* done */
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
@@ -886,29 +737,105 @@ static int af_open(struct af_instance *af) {
|
||||
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->filter=filter;
|
||||
|
||||
af->data = calloc(1, sizeof(struct mp_audio));
|
||||
if (af->data == NULL)
|
||||
return af_ladspa_malloc_failed((char*)af_info_ladspa.name);
|
||||
af_ladspa_t *setup = af->priv;
|
||||
|
||||
af->setup = calloc(1, sizeof(af_ladspa_t));
|
||||
if (af->setup == NULL) {
|
||||
free(af->data);
|
||||
af->data=NULL;
|
||||
return af_ladspa_malloc_failed((char*)af_info_ladspa.name);
|
||||
}
|
||||
|
||||
((af_ladspa_t*)af->setup)->status = AF_ERROR; /* will be set to AF_OK if
|
||||
* all went OK and play()
|
||||
setup->status = AF_ERROR; /* will be set to AF_OK if
|
||||
* all went OK and filter()
|
||||
* should proceed.
|
||||
*/
|
||||
|
||||
((af_ladspa_t*)af->setup)->myname = strdup(af_info_ladspa.name);
|
||||
if (!((af_ladspa_t*)af->setup)->myname)
|
||||
setup->myname = strdup(af_info_ladspa.name);
|
||||
if (!setup->myname)
|
||||
return af_ladspa_malloc_failed((char*)af_info_ladspa.name);
|
||||
|
||||
if (!setup->file || !setup->file[0]) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("No library file specified."));
|
||||
uninit(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (!setup->label || !setup->label[0]) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("No filter label specified."));
|
||||
uninit(af);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
free(setup->myname);
|
||||
setup->myname = calloc(strlen(af_info_ladspa.name)+strlen(setup->file)+
|
||||
strlen(setup->label)+6, 1);
|
||||
snprintf(setup->myname, strlen(af_info_ladspa.name)+
|
||||
strlen(setup->file)+strlen(setup->label)+6, "%s: (%s:%s)",
|
||||
af_info_ladspa.name, setup->file, setup->label);
|
||||
|
||||
/* load plugin :) */
|
||||
|
||||
if ( af_ladspa_load_plugin(af) != AF_OK )
|
||||
return AF_ERROR;
|
||||
|
||||
/* see what inputs, outputs and controls this plugin has */
|
||||
if ( af_ladspa_parse_plugin(af) != AF_OK )
|
||||
return AF_ERROR;
|
||||
|
||||
/* ninputcontrols is set by now, read control values from arg */
|
||||
|
||||
float val;
|
||||
char *line = setup->controls;
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
if (!line || (i != 0 && *line != ',')) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("Not enough controls specified on the command line."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (i != 0)
|
||||
line++;
|
||||
if (sscanf(line, "%f", &val) != 1) {
|
||||
MP_ERR(af, "%s: %s\n", setup->myname,
|
||||
_("Not enough controls specified on the command line."));
|
||||
return AF_ERROR;
|
||||
}
|
||||
setup->inputcontrols[setup->inputcontrolsmap[i]] = val;
|
||||
line = strchr(line, ',');
|
||||
}
|
||||
|
||||
MP_VERBOSE(af, "%s: input controls: ", setup->myname);
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
MP_VERBOSE(af, "%0.4f ",
|
||||
setup->inputcontrols[setup->inputcontrolsmap[i]]);
|
||||
}
|
||||
MP_VERBOSE(af, "\n");
|
||||
|
||||
/* check boundaries of inputcontrols */
|
||||
|
||||
MP_VERBOSE(af, "%s: checking boundaries of input controls\n",
|
||||
setup->myname);
|
||||
for (int i = 0; i < setup->ninputcontrols; i++) {
|
||||
int p = setup->inputcontrolsmap[i];
|
||||
LADSPA_PortRangeHint hint =
|
||||
setup->plugin_descriptor->PortRangeHints[p];
|
||||
val = setup->inputcontrols[p];
|
||||
|
||||
if (LADSPA_IS_HINT_BOUNDED_BELOW(hint.HintDescriptor) &&
|
||||
val < hint.LowerBound) {
|
||||
MP_ERR(af, "%s: Input control #%d is below lower boundary of %0.4f.\n",
|
||||
setup->myname, i, hint.LowerBound);
|
||||
return AF_ERROR;
|
||||
}
|
||||
if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint.HintDescriptor) &&
|
||||
val > hint.UpperBound) {
|
||||
MP_ERR(af, "%s: Input control #%d is above upper boundary of %0.4f.\n",
|
||||
setup->myname, i, hint.UpperBound);
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
MP_VERBOSE(af, "%s: all controls have sane values\n",
|
||||
setup->myname);
|
||||
|
||||
/* All is well! */
|
||||
setup->status = AF_OK;
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
|
||||
+163
-227
@@ -32,9 +32,10 @@
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/mem.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "audio/reorder_ch.h"
|
||||
#include "audio/audio_buffer.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
|
||||
|
||||
#define AC3_MAX_CHANNELS 6
|
||||
@@ -50,339 +51,274 @@ typedef struct af_ac3enc_s {
|
||||
struct AVCodec *lavc_acodec;
|
||||
struct AVCodecContext *lavc_actx;
|
||||
AVPacket pkt;
|
||||
bool planarize;
|
||||
int add_iec61937_header;
|
||||
int bit_rate;
|
||||
int pending_data_size;
|
||||
char *pending_data;
|
||||
int pending_len;
|
||||
int expect_len;
|
||||
int min_channel_num;
|
||||
struct mp_audio_buffer *pending;
|
||||
int in_samples; // samples of input per AC3 frame
|
||||
int out_samples; // upper bound on encoded output per AC3 frame
|
||||
int in_sampleformat;
|
||||
|
||||
int cfg_add_iec61937_header;
|
||||
int cfg_bit_rate;
|
||||
int cfg_min_channel_num;
|
||||
} af_ac3enc_t;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_ac3enc_t *s = af->setup;
|
||||
struct mp_audio *data = arg;
|
||||
int i, bit_rate, test_output_res;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
static const int default_bit_rate[AC3_MAX_CHANNELS+1] = \
|
||||
{0, 96000, 192000, 256000, 384000, 448000, 448000};
|
||||
|
||||
switch (cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
if (AF_FORMAT_IS_AC3(data->format) || data->nch < s->min_channel_num)
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
struct mp_audio orig_in = *in;
|
||||
|
||||
if (AF_FORMAT_IS_AC3(in->format) || in->nch < s->cfg_min_channel_num)
|
||||
return AF_DETACH;
|
||||
|
||||
mp_audio_set_format(af->data, s->in_sampleformat);
|
||||
if (data->rate == 48000 || data->rate == 44100 || data->rate == 32000)
|
||||
af->data->rate = data->rate;
|
||||
else
|
||||
af->data->rate = 48000;
|
||||
if (data->nch > AC3_MAX_CHANNELS)
|
||||
mp_audio_set_num_channels(af->data, AC3_MAX_CHANNELS);
|
||||
else
|
||||
mp_audio_set_channels(af->data, &data->channels);
|
||||
mp_chmap_reorder_to_lavc(&af->data->channels);
|
||||
test_output_res = af_test_output(af, data);
|
||||
mp_audio_set_format(in, s->in_sampleformat);
|
||||
|
||||
s->pending_len = 0;
|
||||
s->expect_len = AC3_FRAME_SIZE * data->nch * af->data->bps;
|
||||
assert(s->expect_len <= s->pending_data_size);
|
||||
if (s->add_iec61937_header)
|
||||
af->mul = (double)AC3_FRAME_SIZE * 2 * 2 / s->expect_len;
|
||||
else
|
||||
af->mul = (double)AC3_MAX_CODED_FRAME_SIZE / s->expect_len;
|
||||
if (in->rate != 48000 && in->rate != 44100 && in->rate != 32000)
|
||||
in->rate = 48000;
|
||||
af->data->rate = in->rate;
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG2, "af_lavcac3enc reinit: %d, %d, %f, %d.\n",
|
||||
data->nch, data->rate, af->mul, s->expect_len);
|
||||
mp_chmap_reorder_to_lavc(&in->channels);
|
||||
if (in->nch > AC3_MAX_CHANNELS)
|
||||
mp_audio_set_num_channels(in, AC3_MAX_CHANNELS);
|
||||
|
||||
bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[af->data->nch];
|
||||
mp_audio_set_format(af->data, AF_FORMAT_AC3_BE);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
|
||||
if (s->lavc_actx->channels != af->data->nch ||
|
||||
s->lavc_actx->sample_rate != af->data->rate ||
|
||||
s->lavc_actx->bit_rate != bit_rate) {
|
||||
if (!mp_audio_config_equals(in, &orig_in))
|
||||
return AF_FALSE;
|
||||
|
||||
s->in_samples = AC3_FRAME_SIZE;
|
||||
if (s->cfg_add_iec61937_header) {
|
||||
s->out_samples = AC3_FRAME_SIZE;
|
||||
} else {
|
||||
s->out_samples = AC3_MAX_CODED_FRAME_SIZE / af->data->sstride;
|
||||
}
|
||||
af->mul = s->out_samples / (double)s->in_samples;
|
||||
|
||||
mp_audio_buffer_reinit(s->pending, in);
|
||||
|
||||
MP_DBG(af, "af_lavcac3enc reinit: %d, %d, %f, %d.\n",
|
||||
in->nch, in->rate, af->mul, s->in_samples);
|
||||
|
||||
int bit_rate = s->bit_rate ? s->bit_rate : default_bit_rate[in->nch];
|
||||
|
||||
if (s->lavc_actx->channels != in->nch ||
|
||||
s->lavc_actx->sample_rate != in->rate ||
|
||||
s->lavc_actx->bit_rate != bit_rate)
|
||||
{
|
||||
avcodec_close(s->lavc_actx);
|
||||
|
||||
// Put sample parameters
|
||||
s->lavc_actx->channels = af->data->nch;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&af->data->channels);
|
||||
s->lavc_actx->sample_rate = af->data->rate;
|
||||
s->lavc_actx->channels = in->nch;
|
||||
s->lavc_actx->channel_layout = mp_chmap_to_lavc(&in->channels);
|
||||
s->lavc_actx->sample_rate = in->rate;
|
||||
s->lavc_actx->bit_rate = bit_rate;
|
||||
|
||||
if (avcodec_open2(s->lavc_actx, s->lavc_acodec, NULL) < 0) {
|
||||
mp_tmsg(MSGT_AFILTER, MSGL_ERR, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
MP_ERR(af, "Couldn't open codec %s, br=%d.\n", "ac3", bit_rate);
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
if (s->lavc_actx->frame_size != AC3_FRAME_SIZE) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "lavcac3enc: unexpected ac3 "
|
||||
MP_ERR(af, "lavcac3enc: unexpected ac3 "
|
||||
"encoder frame size %d\n", s->lavc_actx->frame_size);
|
||||
return AF_ERROR;
|
||||
}
|
||||
mp_audio_set_format(af->data, AF_FORMAT_AC3_BE);
|
||||
mp_audio_set_num_channels(af->data, 2);
|
||||
return test_output_res;
|
||||
case AF_CONTROL_COMMAND_LINE:
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG2, "af_lavcac3enc cmdline: %s.\n", (char*)arg);
|
||||
s->bit_rate = 0;
|
||||
s->min_channel_num = 0;
|
||||
s->add_iec61937_header = 0;
|
||||
sscanf(arg,"%d:%d:%d", &s->add_iec61937_header, &s->bit_rate,
|
||||
&s->min_channel_num);
|
||||
if (s->bit_rate < 1000)
|
||||
s->bit_rate *= 1000;
|
||||
if (s->bit_rate) {
|
||||
for (i = 0; i < 19; ++i)
|
||||
if (ac3_bitrate_tab[i] * 1000 == s->bit_rate)
|
||||
break;
|
||||
if (i >= 19) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_WARN, "af_lavcac3enc unable set unsupported "
|
||||
"bitrate %d, use default bitrate (check manpage to see "
|
||||
"supported bitrates).\n", s->bit_rate);
|
||||
s->bit_rate = 0;
|
||||
}
|
||||
}
|
||||
if (s->min_channel_num == 0)
|
||||
s->min_channel_num = 5;
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "af_lavcac3enc config spdif:%d, bitrate:%d, "
|
||||
"minchnum:%d.\n", s->add_iec61937_header, s->bit_rate,
|
||||
s->min_channel_num);
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
af_ac3enc_t *s = af->setup;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
|
||||
if (af->data)
|
||||
free(af->data->audio);
|
||||
free(af->data);
|
||||
if (s) {
|
||||
av_free_packet(&s->pkt);
|
||||
if(s->lavc_actx) {
|
||||
avcodec_close(s->lavc_actx);
|
||||
av_free(s->lavc_actx);
|
||||
}
|
||||
free(s->pending_data);
|
||||
free(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* audio)
|
||||
static int filter(struct af_instance* af, struct mp_audio* audio, int flags)
|
||||
{
|
||||
af_ac3enc_t *s = af->setup;
|
||||
struct mp_audio *c = audio; // Current working data
|
||||
struct mp_audio *l;
|
||||
int left, outsize = 0;
|
||||
char *buf, *src;
|
||||
int max_output_len;
|
||||
int frame_num = (audio->len + s->pending_len) / s->expect_len;
|
||||
int samplesize = af_fmt2bits(s->in_sampleformat) / 8;
|
||||
struct mp_audio *out = af->data;
|
||||
af_ac3enc_t *s = af->priv;
|
||||
int num_frames = (audio->samples + mp_audio_buffer_samples(s->pending))
|
||||
/ s->in_samples;
|
||||
|
||||
if (s->add_iec61937_header)
|
||||
max_output_len = AC3_FRAME_SIZE * 2 * 2 * frame_num;
|
||||
else
|
||||
max_output_len = AC3_MAX_CODED_FRAME_SIZE * frame_num;
|
||||
int max_out_samples = s->out_samples * num_frames;
|
||||
mp_audio_realloc_min(out, max_out_samples);
|
||||
out->samples = 0;
|
||||
|
||||
if (af->data->len < max_output_len) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Reallocating memory in module %s, "
|
||||
"old len = %i, new len = %i\n", af->info->name, af->data->len,
|
||||
max_output_len);
|
||||
free(af->data->audio);
|
||||
af->data->audio = malloc(max_output_len);
|
||||
if (!af->data->audio) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[libaf] Could not allocate memory \n");
|
||||
return NULL;
|
||||
}
|
||||
af->data->len = max_output_len;
|
||||
}
|
||||
|
||||
l = af->data; // Local data
|
||||
buf = l->audio;
|
||||
src = c->audio;
|
||||
left = c->len;
|
||||
|
||||
|
||||
while (left > 0) {
|
||||
while (audio->samples > 0) {
|
||||
int ret;
|
||||
|
||||
if (left + s->pending_len < s->expect_len) {
|
||||
memcpy(s->pending_data + s->pending_len, src, left);
|
||||
src += left;
|
||||
s->pending_len += left;
|
||||
left = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
char *src2 = src;
|
||||
|
||||
if (s->pending_len) {
|
||||
int needs = s->expect_len - s->pending_len;
|
||||
if (needs > 0) {
|
||||
memcpy(s->pending_data + s->pending_len, src, needs);
|
||||
src += needs;
|
||||
left -= needs;
|
||||
int consumed_pending = 0;
|
||||
struct mp_audio in_frame;
|
||||
int pending = mp_audio_buffer_samples(s->pending);
|
||||
if (pending == 0 && audio->samples >= s->in_samples) {
|
||||
in_frame = *audio;
|
||||
mp_audio_skip_samples(audio, s->in_samples);
|
||||
} else {
|
||||
if (pending > 0 && pending < s->in_samples) {
|
||||
struct mp_audio tmp = *audio;
|
||||
tmp.samples = MPMIN(tmp.samples, s->in_samples);
|
||||
mp_audio_buffer_append(s->pending, &tmp);
|
||||
mp_audio_skip_samples(audio, tmp.samples);
|
||||
}
|
||||
src2= s->pending_data;
|
||||
}
|
||||
|
||||
void *data = (void *) src2;
|
||||
if (s->planarize) {
|
||||
void *data2 = malloc(s->expect_len);
|
||||
reorder_to_planar(data2, data, samplesize,
|
||||
c->nch, s->expect_len / samplesize / c->nch);
|
||||
data = data2;
|
||||
mp_audio_buffer_peek(s->pending, &in_frame);
|
||||
if (in_frame.samples < s->in_samples)
|
||||
break;
|
||||
consumed_pending = s->in_samples;
|
||||
}
|
||||
in_frame.samples = s->in_samples;
|
||||
|
||||
AVFrame *frame = avcodec_alloc_frame();
|
||||
if (!frame) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[libaf] Could not allocate memory \n");
|
||||
return NULL;
|
||||
MP_FATAL(af, "[libaf] Could not allocate memory \n");
|
||||
return -1;
|
||||
}
|
||||
frame->nb_samples = AC3_FRAME_SIZE;
|
||||
frame->nb_samples = s->in_samples;
|
||||
frame->format = s->lavc_actx->sample_fmt;
|
||||
frame->channel_layout = s->lavc_actx->channel_layout;
|
||||
|
||||
ret = avcodec_fill_audio_frame(frame, c->nch, s->lavc_actx->sample_fmt,
|
||||
(const uint8_t*)data, s->expect_len, 0);
|
||||
if (ret < 0) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[lavac3enc] Frame setup failed.\n");
|
||||
return NULL;
|
||||
}
|
||||
assert(in_frame.num_planes <= AV_NUM_DATA_POINTERS);
|
||||
for (int n = 0; n < in_frame.num_planes; n++)
|
||||
frame->data[n] = in_frame.planes[n];
|
||||
frame->linesize[0] = s->in_samples * audio->sstride;
|
||||
|
||||
int ok;
|
||||
ret = avcodec_encode_audio2(s->lavc_actx, &s->pkt, frame, &ok);
|
||||
if (ret < 0 || !ok) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[lavac3enc] Encode failed.\n");
|
||||
return NULL;
|
||||
MP_FATAL(af, "[lavac3enc] Encode failed.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (s->planarize)
|
||||
free(data);
|
||||
|
||||
avcodec_free_frame(&frame);
|
||||
|
||||
if (s->pending_len) {
|
||||
s->pending_len = 0;
|
||||
} else {
|
||||
src += s->expect_len;
|
||||
left -= s->expect_len;
|
||||
}
|
||||
mp_audio_buffer_skip(s->pending, consumed_pending);
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG2, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
s->pkt.size, s->pending_len);
|
||||
MP_DBG(af, "avcodec_encode_audio got %d, pending %d.\n",
|
||||
s->pkt.size, mp_audio_buffer_samples(s->pending));
|
||||
|
||||
int len = s->pkt.size;
|
||||
int frame_size = s->pkt.size;
|
||||
int header_len = 0;
|
||||
if (s->add_iec61937_header) {
|
||||
assert(s->pkt.size > 5);
|
||||
char hdr[8];
|
||||
|
||||
if (s->cfg_add_iec61937_header && s->pkt.size > 5) {
|
||||
int bsmod = s->pkt.data[5] & 0x7;
|
||||
int len = frame_size;
|
||||
|
||||
AV_WB16(buf, 0xF872); // iec 61937 syncword 1
|
||||
AV_WB16(buf + 2, 0x4E1F); // iec 61937 syncword 2
|
||||
buf[4] = bsmod; // bsmod
|
||||
buf[5] = 0x01; // data-type ac3
|
||||
AV_WB16(buf + 6, len << 3); // number of bits in payload
|
||||
|
||||
memset(buf + 8 + len, 0, AC3_FRAME_SIZE * 2 * 2 - 8 - len);
|
||||
frame_size = AC3_FRAME_SIZE * 2 * 2;
|
||||
header_len = 8;
|
||||
len = AC3_FRAME_SIZE * 2 * 2;
|
||||
|
||||
AV_WB16(hdr, 0xF872); // iec 61937 syncword 1
|
||||
AV_WB16(hdr + 2, 0x4E1F); // iec 61937 syncword 2
|
||||
hdr[4] = bsmod; // bsmod
|
||||
hdr[5] = 0x01; // data-type ac3
|
||||
AV_WB16(hdr + 6, len << 3); // number of bits in payload
|
||||
}
|
||||
|
||||
assert(buf + len <= (char *)af->data->audio + af->data->len);
|
||||
assert(s->pkt.size <= len - header_len);
|
||||
size_t max_size = (max_out_samples - out->samples) * out->sstride;
|
||||
if (frame_size > max_size)
|
||||
abort();
|
||||
|
||||
char *buf = (char *)out->planes[0] + out->samples * out->sstride;
|
||||
memcpy(buf, hdr, header_len);
|
||||
memcpy(buf + header_len, s->pkt.data, s->pkt.size);
|
||||
|
||||
outsize += len;
|
||||
buf += len;
|
||||
memset(buf + header_len + s->pkt.size, 0,
|
||||
frame_size - (header_len + s->pkt.size));
|
||||
out->samples += frame_size / out->sstride;
|
||||
}
|
||||
c->audio = l->audio;
|
||||
mp_audio_set_num_channels(c, 2);
|
||||
mp_audio_set_format(c, af->data->format);
|
||||
c->len = outsize;
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG2, "play return size %d, pending %d\n",
|
||||
outsize, s->pending_len);
|
||||
return c;
|
||||
|
||||
mp_audio_buffer_append(s->pending, audio);
|
||||
|
||||
*audio = *out;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance* af){
|
||||
|
||||
af_ac3enc_t *s = calloc(1,sizeof(af_ac3enc_t));
|
||||
af_ac3enc_t *s = af->priv;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=s;
|
||||
af->filter=filter;
|
||||
|
||||
s->lavc_acodec = avcodec_find_encoder_by_name("ac3");
|
||||
if (!s->lavc_acodec) {
|
||||
mp_tmsg(MSGT_AFILTER, MSGL_ERR, "Audio LAVC, couldn't find encoder for codec %s.\n", "ac3");
|
||||
MP_ERR(af, "Audio LAVC, couldn't find encoder for codec %s.\n", "ac3");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->lavc_actx = avcodec_alloc_context3(s->lavc_acodec);
|
||||
if (!s->lavc_actx) {
|
||||
mp_tmsg(MSGT_AFILTER, MSGL_ERR, "Audio LAVC, couldn't allocate context!\n");
|
||||
MP_ERR(af, "Audio LAVC, couldn't allocate context!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
const enum AVSampleFormat *fmts = s->lavc_acodec->sample_fmts;
|
||||
for (int i = 0; ; i++) {
|
||||
if (fmts[i] == AV_SAMPLE_FMT_NONE) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "Audio LAVC, encoder doesn't "
|
||||
"support expected sample formats!\n");
|
||||
return AF_ERROR;
|
||||
} else if (fmts[i] == AV_SAMPLE_FMT_S16) {
|
||||
s->in_sampleformat = AF_FORMAT_S16_NE;
|
||||
for (int i = 0; fmts[i] != AV_SAMPLE_FMT_NONE; i++) {
|
||||
s->in_sampleformat = af_from_avformat(fmts[i]);
|
||||
if (s->in_sampleformat) {
|
||||
s->lavc_actx->sample_fmt = fmts[i];
|
||||
s->planarize = 0;
|
||||
break;
|
||||
} else if (fmts[i] == AV_SAMPLE_FMT_FLT) {
|
||||
s->in_sampleformat = AF_FORMAT_FLOAT_NE;
|
||||
s->lavc_actx->sample_fmt = fmts[i];
|
||||
s->planarize = 0;
|
||||
break;
|
||||
} else if (fmts[i] == AV_SAMPLE_FMT_S16P) {
|
||||
s->in_sampleformat = AF_FORMAT_S16_NE;
|
||||
s->lavc_actx->sample_fmt = fmts[i];
|
||||
s->planarize = 1;
|
||||
break;
|
||||
} else if (fmts[i] == AV_SAMPLE_FMT_FLTP) {
|
||||
s->in_sampleformat = AF_FORMAT_FLOAT_NE;
|
||||
s->lavc_actx->sample_fmt = fmts[i];
|
||||
s->planarize = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
char buf[100];
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[af_lavcac3enc]: in sample format: %s\n",
|
||||
af_fmt2str(s->in_sampleformat, buf, 100));
|
||||
s->pending_data_size = AF_NCH * AC3_FRAME_SIZE *
|
||||
af_fmt2bits(s->in_sampleformat) / 8;
|
||||
s->pending_data = malloc(s->pending_data_size);
|
||||
|
||||
if (s->planarize)
|
||||
mp_msg(MSGT_AFILTER, MSGL_WARN,
|
||||
"[af_lavcac3enc]: need to planarize audio data\n");
|
||||
if (!s->in_sampleformat) {
|
||||
MP_ERR(af, "Audio LAVC, encoder doesn't "
|
||||
"support expected sample formats!\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
MP_VERBOSE(af, "[af_lavcac3enc]: in sample format: %s\n",
|
||||
af_fmt_to_str(s->in_sampleformat));
|
||||
|
||||
av_init_packet(&s->pkt);
|
||||
|
||||
s->pending = mp_audio_buffer_create(af);
|
||||
|
||||
if (s->cfg_bit_rate) {
|
||||
int i;
|
||||
for (i = 0; i < 19; i++) {
|
||||
if (ac3_bitrate_tab[i] == s->cfg_bit_rate)
|
||||
break;
|
||||
}
|
||||
if (i >= 19) {
|
||||
MP_WARN(af, "af_lavcac3enc unable set unsupported "
|
||||
"bitrate %d, use default bitrate (check manpage to see "
|
||||
"supported bitrates).\n", s->cfg_bit_rate);
|
||||
s->cfg_bit_rate = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct af_ac3enc_s
|
||||
|
||||
struct af_info af_info_lavcac3enc = {
|
||||
"runtime encode to ac3 using libavcodec",
|
||||
"lavcac3enc",
|
||||
"Ulion",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "runtime encode to ac3 using libavcodec",
|
||||
.name = "lavcac3enc",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct af_ac3enc_s),
|
||||
.priv_defaults = &(const struct af_ac3enc_s){
|
||||
.cfg_add_iec61937_header = 1,
|
||||
.cfg_min_channel_num = 5,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("tospdif", cfg_add_iec61937_header, 0),
|
||||
OPT_CHOICE_OR_INT("bitrate", cfg_bit_rate, 0, 32, 640,
|
||||
({"default", 0})),
|
||||
OPT_INTRANGE("minch", cfg_min_channel_num, 0, 2, 6),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+46
-62
@@ -38,8 +38,8 @@
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "af.h"
|
||||
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "mpvcore/av_opts.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/av_opts.h"
|
||||
|
||||
#define IS_LIBAV_FORK (LIBAVFILTER_VERSION_MICRO < 100)
|
||||
|
||||
@@ -58,14 +58,7 @@ struct priv {
|
||||
AVFilterContext *in;
|
||||
AVFilterContext *out;
|
||||
|
||||
// Guarantee that the data stays valid until next filter call
|
||||
char *out_buffer;
|
||||
|
||||
struct mp_audio data;
|
||||
struct mp_audio temp;
|
||||
|
||||
int64_t bytes_in;
|
||||
int64_t bytes_out;
|
||||
int64_t samples_in;
|
||||
|
||||
AVRational timebase_out;
|
||||
|
||||
@@ -89,19 +82,19 @@ static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
|
||||
int r;
|
||||
|
||||
if (bstr0(p->cfg_graph).len == 0) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "lavfi: no filter graph set\n");
|
||||
MP_FATAL(af, "lavfi: no filter graph set\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
destroy_graph(af);
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "lavfi: create graph: '%s'\n", p->cfg_graph);
|
||||
MP_VERBOSE(af, "lavfi: create graph: '%s'\n", p->cfg_graph);
|
||||
|
||||
AVFilterGraph *graph = avfilter_graph_alloc();
|
||||
if (!graph)
|
||||
goto error;
|
||||
|
||||
if (parse_avopts(graph, p->cfg_avopts) < 0) {
|
||||
mp_msg(MSGT_VFILTER, MSGL_FATAL, "lavfi: could not set opts: '%s'\n",
|
||||
MP_FATAL(af, "lavfi: could not set opts: '%s'\n",
|
||||
p->cfg_avopts);
|
||||
goto error;
|
||||
}
|
||||
@@ -129,6 +122,8 @@ static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
|
||||
static const enum AVSampleFormat sample_fmts[] = {
|
||||
AV_SAMPLE_FMT_U8, AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_S32,
|
||||
AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_DBL,
|
||||
AV_SAMPLE_FMT_U8P, AV_SAMPLE_FMT_S16P, AV_SAMPLE_FMT_S32P,
|
||||
AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_DBLP,
|
||||
AV_SAMPLE_FMT_NONE
|
||||
};
|
||||
r = av_opt_set_int_list(out, "sample_fmts", sample_fmts,
|
||||
@@ -163,7 +158,7 @@ static bool recreate_graph(struct af_instance *af, struct mp_audio *config)
|
||||
return true;
|
||||
|
||||
error:
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "Can't configure libavfilter graph.\n");
|
||||
MP_FATAL(af, "Can't configure libavfilter graph.\n");
|
||||
avfilter_graph_free(&graph);
|
||||
talloc_free(tmp);
|
||||
return false;
|
||||
@@ -180,7 +175,7 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT_NE);
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
|
||||
if (!mp_chmap_is_lavc(&in->channels))
|
||||
mp_chmap_reorder_to_lavc(&in->channels); // will always work
|
||||
@@ -200,55 +195,51 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
mp_chmap_from_channels(&out_cm, l_out->channels);
|
||||
mp_audio_set_channels(out, &out_cm);
|
||||
|
||||
if (!mp_audio_config_valid(out))
|
||||
return AF_ERROR;
|
||||
|
||||
p->timebase_out = l_out->time_base;
|
||||
|
||||
af->mul = (double) (out->rate * out->nch) / (in->rate * in->nch);
|
||||
// Blatantly incorrect; we don't know what the filters do.
|
||||
af->mul = out->rate / (double)in->rate;
|
||||
|
||||
return mp_audio_config_equals(in, &orig_in) ? AF_OK : AF_FALSE;
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE: {
|
||||
talloc_free(p->cfg_graph);
|
||||
p->cfg_graph = talloc_strdup(p, (char *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static struct mp_audio *play(struct af_instance *af, struct mp_audio *data)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
{
|
||||
struct priv *p = af->priv;
|
||||
|
||||
struct mp_audio *r = af->data;
|
||||
bool eof = data->samples == 0 && (flags & AF_FILTER_FLAG_EOF);
|
||||
AVFilterLink *l_in = p->in->outputs[0];
|
||||
|
||||
struct mp_audio *r = &p->temp;
|
||||
*r = *af->data;
|
||||
|
||||
int in_frame_size = data->bps * data->channels.num;
|
||||
int out_frame_size = r->bps * r->channels.num;
|
||||
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
frame->nb_samples = data->len / in_frame_size;
|
||||
frame->nb_samples = data->samples;
|
||||
frame->format = l_in->format;
|
||||
|
||||
// Timebase is 1/sample_rate
|
||||
frame->pts = p->bytes_in / in_frame_size;
|
||||
frame->pts = p->samples_in;
|
||||
|
||||
av_frame_set_channels(frame, l_in->channels);
|
||||
av_frame_set_channel_layout(frame, l_in->channel_layout);
|
||||
av_frame_set_sample_rate(frame, l_in->sample_rate);
|
||||
|
||||
frame->data[0] = data->audio;
|
||||
frame->extended_data = frame->data;
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
frame->data[n] = data->planes[n];
|
||||
frame->linesize[0] = frame->nb_samples * data->sstride;
|
||||
|
||||
if (av_buffersrc_add_frame(p->in, frame) < 0) {
|
||||
if (av_buffersrc_add_frame(p->in, eof ? NULL : frame) < 0) {
|
||||
av_frame_free(&frame);
|
||||
return NULL;
|
||||
return -1;
|
||||
}
|
||||
av_frame_free(&frame);
|
||||
|
||||
int64_t out_pts = AV_NOPTS_VALUE;
|
||||
size_t out_len = 0;
|
||||
r->samples = 0;
|
||||
for (;;) {
|
||||
frame = av_frame_alloc();
|
||||
if (av_buffersink_get_frame(p->out, frame) < 0) {
|
||||
@@ -257,62 +248,55 @@ static struct mp_audio *play(struct af_instance *af, struct mp_audio *data)
|
||||
break;
|
||||
}
|
||||
|
||||
size_t new_len = out_len + frame->nb_samples * out_frame_size;
|
||||
if (new_len > talloc_get_size(p->out_buffer))
|
||||
p->out_buffer = talloc_realloc(p, p->out_buffer, char, new_len);
|
||||
memcpy(p->out_buffer + out_len, frame->data[0], new_len - out_len);
|
||||
out_len = new_len;
|
||||
mp_audio_realloc_min(r, r->samples + frame->nb_samples);
|
||||
for (int n = 0; n < r->num_planes; n++) {
|
||||
memcpy((char *)r->planes[n] + r->samples * r->sstride,
|
||||
frame->extended_data[n], frame->nb_samples * r->sstride);
|
||||
}
|
||||
r->samples += frame->nb_samples;
|
||||
|
||||
if (out_pts == AV_NOPTS_VALUE)
|
||||
out_pts = frame->pts;
|
||||
|
||||
av_frame_free(&frame);
|
||||
}
|
||||
|
||||
r->audio = p->out_buffer;
|
||||
r->len = out_len;
|
||||
|
||||
p->bytes_in += data->len;
|
||||
p->bytes_out += r->len;
|
||||
p->samples_in += data->samples;
|
||||
|
||||
if (out_pts != AV_NOPTS_VALUE) {
|
||||
int64_t num_in_frames = p->bytes_in / in_frame_size;
|
||||
double in_time = num_in_frames / (double)data->rate;
|
||||
|
||||
double in_time = p->samples_in / (double)data->rate;
|
||||
double out_time = out_pts * av_q2d(p->timebase_out);
|
||||
// Need pts past the last output sample.
|
||||
int out_frames = r->len / out_frame_size;
|
||||
out_time += out_frames / (double)r->rate;
|
||||
out_time += r->samples / (double)r->rate;
|
||||
|
||||
af->delay = (in_time - out_time) * r->rate * out_frame_size;
|
||||
af->delay = in_time - out_time;
|
||||
}
|
||||
|
||||
return r;
|
||||
*data = *r;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
destroy_graph(af);
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->play = play;
|
||||
af->mul = 1;
|
||||
struct priv *priv = af->priv;
|
||||
af->data = &priv->data;
|
||||
af->filter = filter;
|
||||
// Removing this requires fixing AVFrame.data vs. AVFrame.extended_data
|
||||
assert(MP_NUM_CHANNELS <= AV_NUM_DATA_POINTERS);
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
struct af_info af_info_lavfi = {
|
||||
"libavfilter bridge",
|
||||
"lavfi",
|
||||
"",
|
||||
"",
|
||||
0,
|
||||
af_open,
|
||||
.info = "libavfilter bridge",
|
||||
.name = "lavfi",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_STRING("graph", cfg_graph, 0),
|
||||
|
||||
+181
-144
@@ -3,6 +3,7 @@
|
||||
* Copyright (c) 2013 Stefano Pigozzi <stefano.pigozzi@gmail.com>
|
||||
*
|
||||
* This file is part of mpv.
|
||||
* Based on Michael Niedermayer's lavcresample.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -23,6 +24,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <libavutil/opt.h>
|
||||
#include <libavutil/audioconvert.h>
|
||||
#include <libavutil/common.h>
|
||||
@@ -32,27 +35,28 @@
|
||||
#include "talloc.h"
|
||||
#include "config.h"
|
||||
|
||||
#if defined(CONFIG_LIBAVRESAMPLE)
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
#include <libavresample/avresample.h>
|
||||
#define USE_SET_CHANNEL_MAPPING HAVE_AVRESAMPLE_SET_CHANNEL_MAPPING
|
||||
#elif defined(CONFIG_LIBSWRESAMPLE)
|
||||
#elif HAVE_LIBSWRESAMPLE
|
||||
#include <libswresample/swresample.h>
|
||||
#define AVAudioResampleContext SwrContext
|
||||
#define avresample_alloc_context swr_alloc
|
||||
#define avresample_open swr_init
|
||||
#define avresample_close(x) do { } while(0)
|
||||
#define avresample_free swr_free
|
||||
#define avresample_available(x) 0
|
||||
#define avresample_convert(ctx, out, out_planesize, out_samples, in, in_planesize, in_samples) \
|
||||
swr_convert(ctx, out, out_samples, (const uint8_t**)(in), in_samples)
|
||||
#define avresample_set_channel_mapping swr_set_channel_mapping
|
||||
#define USE_SET_CHANNEL_MAPPING 1
|
||||
#else
|
||||
#error "config.h broken"
|
||||
#error "config.h broken or no resampler found"
|
||||
#endif
|
||||
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "mpvcore/av_opts.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/av_opts.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "audio/reorder_ch.h"
|
||||
@@ -84,16 +88,24 @@ struct af_resample {
|
||||
uint8_t *reorder_buffer;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_LIBAVRESAMPLE
|
||||
#if HAVE_LIBAVRESAMPLE
|
||||
static int get_delay(struct af_resample *s)
|
||||
{
|
||||
return avresample_get_delay(s->avrctx);
|
||||
}
|
||||
static void drop_all_output(struct af_resample *s)
|
||||
{
|
||||
while (avresample_read(s->avrctx, NULL, 1000) > 0) {}
|
||||
}
|
||||
#else
|
||||
static int get_delay(struct af_resample *s)
|
||||
{
|
||||
return swr_get_delay(s->avrctx, s->ctx.in_rate);
|
||||
}
|
||||
static void drop_all_output(struct af_resample *s)
|
||||
{
|
||||
while (swr_drop_output(s->avrctx, 1000) > 0) {}
|
||||
}
|
||||
#endif
|
||||
|
||||
static double af_resample_default_cutoff(int filter_size)
|
||||
@@ -124,8 +136,98 @@ static bool test_conversion(int src_format, int dst_format)
|
||||
af_to_avformat(dst_format) != AV_SAMPLE_FMT_NONE;
|
||||
}
|
||||
|
||||
#define ctx_opt_set_int(a,b) av_opt_set_int(s->avrctx, (a), (b), 0)
|
||||
#define ctx_opt_set_dbl(a,b) av_opt_set_double(s->avrctx, (a), (b), 0)
|
||||
static int configure_lavrr(struct af_instance *af, struct mp_audio *in,
|
||||
struct mp_audio *out)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
|
||||
enum AVSampleFormat in_samplefmt = af_to_avformat(in->format);
|
||||
enum AVSampleFormat out_samplefmt = af_to_avformat(out->format);
|
||||
|
||||
if (in_samplefmt == AV_SAMPLE_FMT_NONE || out_samplefmt == AV_SAMPLE_FMT_NONE)
|
||||
return AF_ERROR;
|
||||
|
||||
avresample_close(s->avrctx);
|
||||
avresample_close(s->avrctx_out);
|
||||
|
||||
s->ctx.out_rate = out->rate;
|
||||
s->ctx.in_rate = in->rate;
|
||||
s->ctx.out_format = out->format;
|
||||
s->ctx.in_format = in->format;
|
||||
s->ctx.out_channels= out->channels;
|
||||
s->ctx.in_channels = in->channels;
|
||||
s->ctx.filter_size = s->opts.filter_size;
|
||||
s->ctx.phase_shift = s->opts.phase_shift;
|
||||
s->ctx.linear = s->opts.linear;
|
||||
s->ctx.cutoff = s->opts.cutoff;
|
||||
|
||||
av_opt_set_int(s->avrctx, "filter_size", s->ctx.filter_size, 0);
|
||||
av_opt_set_int(s->avrctx, "phase_shift", s->ctx.phase_shift, 0);
|
||||
av_opt_set_int(s->avrctx, "linear_interp", s->ctx.linear, 0);
|
||||
|
||||
av_opt_set_double(s->avrctx, "cutoff", s->ctx.cutoff, 0);
|
||||
|
||||
if (parse_avopts(s->avrctx, s->avopts) < 0) {
|
||||
MP_FATAL(af, "af_lavrresample: could not set opts: '%s'\n", s->avopts);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
struct mp_chmap map_in = in->channels;
|
||||
struct mp_chmap map_out = out->channels;
|
||||
|
||||
// Try not to do any remixing if at least one is "unknown".
|
||||
if (mp_chmap_is_unknown(&map_in) || mp_chmap_is_unknown(&map_out)) {
|
||||
mp_chmap_set_unknown(&map_in, map_in.num);
|
||||
mp_chmap_set_unknown(&map_out, map_out.num);
|
||||
}
|
||||
|
||||
// unchecked: don't take any channel reordering into account
|
||||
uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
|
||||
uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_out);
|
||||
|
||||
av_opt_set_int(s->avrctx, "in_channel_layout", in_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx, "out_channel_layout", out_ch_layout, 0);
|
||||
|
||||
av_opt_set_int(s->avrctx, "in_sample_rate", s->ctx.in_rate, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_rate", s->ctx.out_rate, 0);
|
||||
|
||||
av_opt_set_int(s->avrctx, "in_sample_fmt", in_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx, "out_sample_fmt", out_samplefmt, 0);
|
||||
|
||||
struct mp_chmap in_lavc;
|
||||
mp_chmap_from_lavc(&in_lavc, in_ch_layout);
|
||||
mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
|
||||
|
||||
struct mp_chmap out_lavc;
|
||||
mp_chmap_from_lavc(&out_lavc, out_ch_layout);
|
||||
mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
|
||||
|
||||
// Same configuration; we just reorder.
|
||||
av_opt_set_int(s->avrctx_out, "in_channel_layout", out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_channel_layout", out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->ctx.out_rate, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_rate", s->ctx.out_rate, 0);
|
||||
|
||||
#if USE_SET_CHANNEL_MAPPING
|
||||
// API has weird requirements, quoting avresample.h:
|
||||
// * This function can only be called when the allocated context is not open.
|
||||
// * Also, the input channel layout must have already been set.
|
||||
avresample_set_channel_mapping(s->avrctx, s->reorder_in);
|
||||
avresample_set_channel_mapping(s->avrctx_out, s->reorder_out);
|
||||
#endif
|
||||
|
||||
if (avresample_open(s->avrctx) < 0 ||
|
||||
avresample_open(s->avrctx_out) < 0)
|
||||
{
|
||||
MP_ERR(af, "[lavrresample] Cannot open "
|
||||
"Libavresample Context. \n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
@@ -149,120 +251,38 @@ static int control(struct af_instance *af, int cmd, void *arg)
|
||||
if (mp_chmap_is_empty(&out->channels))
|
||||
mp_audio_set_channels(out, &in->channels);
|
||||
|
||||
enum AVSampleFormat in_samplefmt = af_to_avformat(in->format);
|
||||
if (in_samplefmt == AV_SAMPLE_FMT_NONE) {
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT_NE);
|
||||
in_samplefmt = af_to_avformat(in->format);
|
||||
}
|
||||
enum AVSampleFormat out_samplefmt = af_to_avformat(out->format);
|
||||
if (out_samplefmt == AV_SAMPLE_FMT_NONE) {
|
||||
if (af_to_avformat(in->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
if (af_to_avformat(out->format) == AV_SAMPLE_FMT_NONE)
|
||||
mp_audio_set_format(out, in->format);
|
||||
out_samplefmt = in_samplefmt;
|
||||
}
|
||||
|
||||
af->mul = (double) (out->rate * out->nch) / (in->rate * in->nch);
|
||||
af->delay = out->nch * s->opts.filter_size / FFMIN(af->mul, 1);
|
||||
af->mul = out->rate / (double)in->rate;
|
||||
|
||||
if (needs_lavrctx_reconfigure(s, in, out)) {
|
||||
avresample_close(s->avrctx);
|
||||
avresample_close(s->avrctx_out);
|
||||
|
||||
s->ctx.out_rate = out->rate;
|
||||
s->ctx.in_rate = in->rate;
|
||||
s->ctx.out_format = out->format;
|
||||
s->ctx.in_format = in->format;
|
||||
s->ctx.out_channels= out->channels;
|
||||
s->ctx.in_channels = in->channels;
|
||||
s->ctx.filter_size = s->opts.filter_size;
|
||||
s->ctx.phase_shift = s->opts.phase_shift;
|
||||
s->ctx.linear = s->opts.linear;
|
||||
s->ctx.cutoff = s->opts.cutoff;
|
||||
|
||||
ctx_opt_set_int("filter_size", s->ctx.filter_size);
|
||||
ctx_opt_set_int("phase_shift", s->ctx.phase_shift);
|
||||
ctx_opt_set_int("linear_interp", s->ctx.linear);
|
||||
|
||||
ctx_opt_set_dbl("cutoff", s->ctx.cutoff);
|
||||
|
||||
if (parse_avopts(s->avrctx, s->avopts) < 0) {
|
||||
mp_msg(MSGT_VFILTER, MSGL_FATAL,
|
||||
"af_lavrresample: could not set opts: '%s'\n", s->avopts);
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
struct mp_chmap map_in = in->channels;
|
||||
struct mp_chmap map_out = out->channels;
|
||||
|
||||
// Try not to do any remixing if at least one is "unknown".
|
||||
if (mp_chmap_is_unknown(&map_in) || mp_chmap_is_unknown(&map_out)) {
|
||||
mp_chmap_set_unknown(&map_in, map_in.num);
|
||||
mp_chmap_set_unknown(&map_out, map_out.num);
|
||||
}
|
||||
|
||||
// unchecked: don't take any channel reordering into account
|
||||
uint64_t in_ch_layout = mp_chmap_to_lavc_unchecked(&map_in);
|
||||
uint64_t out_ch_layout = mp_chmap_to_lavc_unchecked(&map_out);
|
||||
|
||||
ctx_opt_set_int("in_channel_layout", in_ch_layout);
|
||||
ctx_opt_set_int("out_channel_layout", out_ch_layout);
|
||||
|
||||
ctx_opt_set_int("in_sample_rate", s->ctx.in_rate);
|
||||
ctx_opt_set_int("out_sample_rate", s->ctx.out_rate);
|
||||
|
||||
ctx_opt_set_int("in_sample_fmt", in_samplefmt);
|
||||
ctx_opt_set_int("out_sample_fmt", out_samplefmt);
|
||||
|
||||
struct mp_chmap in_lavc;
|
||||
mp_chmap_from_lavc(&in_lavc, in_ch_layout);
|
||||
mp_chmap_get_reorder(s->reorder_in, &map_in, &in_lavc);
|
||||
|
||||
struct mp_chmap out_lavc;
|
||||
mp_chmap_from_lavc(&out_lavc, out_ch_layout);
|
||||
mp_chmap_get_reorder(s->reorder_out, &out_lavc, &map_out);
|
||||
|
||||
// Same configuration; we just reorder.
|
||||
av_opt_set_int(s->avrctx_out, "in_channel_layout", out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_channel_layout", out_ch_layout, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_fmt", out_samplefmt, 0);
|
||||
av_opt_set_int(s->avrctx_out, "in_sample_rate", s->ctx.out_rate, 0);
|
||||
av_opt_set_int(s->avrctx_out, "out_sample_rate", s->ctx.out_rate, 0);
|
||||
|
||||
#if USE_SET_CHANNEL_MAPPING
|
||||
// API has weird requirements, quoting avresample.h:
|
||||
// * This function can only be called when the allocated context is not open.
|
||||
// * Also, the input channel layout must have already been set.
|
||||
avresample_set_channel_mapping(s->avrctx, s->reorder_in);
|
||||
avresample_set_channel_mapping(s->avrctx_out, s->reorder_out);
|
||||
#endif
|
||||
|
||||
if (avresample_open(s->avrctx) < 0 ||
|
||||
avresample_open(s->avrctx_out) < 0)
|
||||
{
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[lavrresample] Cannot open "
|
||||
"Libavresample Context. \n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return ((in->format == orig_in.format) &&
|
||||
int r = ((in->format == orig_in.format) &&
|
||||
mp_chmap_equals(&in->channels, &orig_in.channels))
|
||||
? AF_OK : AF_FALSE;
|
||||
? AF_OK : AF_FALSE;
|
||||
|
||||
if (r == AF_OK && needs_lavrctx_reconfigure(s, in, out))
|
||||
r = configure_lavrr(af, in, out);
|
||||
return r;
|
||||
}
|
||||
case AF_CONTROL_FORMAT_FMT | AF_CONTROL_SET: {
|
||||
case AF_CONTROL_SET_FORMAT: {
|
||||
if (af_to_avformat(*(int*)arg) == AV_SAMPLE_FMT_NONE)
|
||||
return AF_FALSE;
|
||||
|
||||
mp_audio_set_format(af->data, *(int*)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_CHANNELS | AF_CONTROL_SET: {
|
||||
case AF_CONTROL_SET_CHANNELS: {
|
||||
mp_audio_set_channels(af->data, (struct mp_chmap *)arg);
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_RESAMPLE_RATE | AF_CONTROL_SET:
|
||||
case AF_CONTROL_SET_RESAMPLE_RATE:
|
||||
out->rate = *(int *)arg;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_RESET:
|
||||
drop_all_output(s);
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
@@ -275,8 +295,10 @@ static void uninit(struct af_instance *af)
|
||||
struct af_resample *s = af->priv;
|
||||
if (s->avrctx)
|
||||
avresample_close(s->avrctx);
|
||||
avresample_free(&s->avrctx);
|
||||
if (s->avrctx_out)
|
||||
avresample_close(s->avrctx_out);
|
||||
avresample_free(&s->avrctx_out);
|
||||
}
|
||||
|
||||
static bool needs_reorder(int *reorder, int num_ch)
|
||||
@@ -288,52 +310,74 @@ static bool needs_reorder(int *reorder, int num_ch)
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct mp_audio *play(struct af_instance *af, struct mp_audio *data)
|
||||
static void reorder_planes(struct mp_audio *mpa, int *reorder)
|
||||
{
|
||||
struct mp_audio prev = *mpa;
|
||||
for (int n = 0; n < mpa->num_planes; n++) {
|
||||
assert(reorder[n] >= 0 && reorder[n] < mpa->num_planes);
|
||||
mpa->planes[n] = prev.planes[reorder[n]];
|
||||
}
|
||||
}
|
||||
|
||||
#if !USE_SET_CHANNEL_MAPPING
|
||||
static void do_reorder(struct mp_audio *mpa, int *reorder)
|
||||
{
|
||||
if (af_fmt_is_planar(mpa->format)) {
|
||||
reorder_planes(mpa, reorder);
|
||||
} else {
|
||||
reorder_channels(mpa->planes[0], reorder, mpa->bps, mpa->nch, mpa->samples);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
{
|
||||
struct af_resample *s = af->priv;
|
||||
struct mp_audio *in = data;
|
||||
struct mp_audio *out = af->data;
|
||||
|
||||
|
||||
int in_size = data->len;
|
||||
int in_samples = in_size / (data->bps * data->nch);
|
||||
int out_samples = avresample_available(s->avrctx) +
|
||||
av_rescale_rnd(get_delay(s) + in_samples,
|
||||
out->samples = avresample_available(s->avrctx) +
|
||||
av_rescale_rnd(get_delay(s) + in->samples,
|
||||
s->ctx.out_rate, s->ctx.in_rate, AV_ROUND_UP);
|
||||
int out_size = out->bps * out_samples * out->nch;
|
||||
|
||||
if (talloc_get_size(out->audio) < out_size)
|
||||
out->audio = talloc_realloc_size(out, out->audio, out_size);
|
||||
mp_audio_realloc_min(out, out->samples);
|
||||
|
||||
af->delay = out->bps * av_rescale_rnd(get_delay(s),
|
||||
s->ctx.out_rate, s->ctx.in_rate,
|
||||
AV_ROUND_UP);
|
||||
af->delay = get_delay(s) / (double)s->ctx.in_rate;
|
||||
|
||||
#if !USE_SET_CHANNEL_MAPPING
|
||||
reorder_channels(data->audio, s->reorder_in, data->bps, data->nch, in_samples);
|
||||
do_reorder(in, s->reorder_in);
|
||||
#endif
|
||||
|
||||
out_samples = avresample_convert(s->avrctx,
|
||||
(uint8_t **) &out->audio, out_size, out_samples,
|
||||
(uint8_t **) &in->audio, in_size, in_samples);
|
||||
if (out->samples) {
|
||||
out->samples = avresample_convert(s->avrctx,
|
||||
(uint8_t **) out->planes, out->samples * out->sstride, out->samples,
|
||||
(uint8_t **) in->planes, in->samples * in->sstride, in->samples);
|
||||
if (out->samples < 0)
|
||||
return -1; // error
|
||||
}
|
||||
|
||||
*data = *out;
|
||||
|
||||
#if USE_SET_CHANNEL_MAPPING
|
||||
if (needs_reorder(s->reorder_out, out->nch)) {
|
||||
if (talloc_get_size(s->reorder_buffer) < out_size)
|
||||
s->reorder_buffer = talloc_realloc_size(s, s->reorder_buffer, out_size);
|
||||
data->audio = s->reorder_buffer;
|
||||
out_samples = avresample_convert(s->avrctx_out,
|
||||
(uint8_t **) &data->audio, out_size, out_samples,
|
||||
(uint8_t **) &out->audio, out_size, out_samples);
|
||||
if (af_fmt_is_planar(out->format)) {
|
||||
reorder_planes(data, s->reorder_out);
|
||||
} else {
|
||||
int out_size = out->samples * out->sstride;
|
||||
if (talloc_get_size(s->reorder_buffer) < out_size)
|
||||
s->reorder_buffer = talloc_realloc_size(s, s->reorder_buffer, out_size);
|
||||
data->planes[0] = s->reorder_buffer;
|
||||
int out_samples = avresample_convert(s->avrctx_out,
|
||||
(uint8_t **) data->planes, out_size, out->samples,
|
||||
(uint8_t **) out->planes, out_size, out->samples);
|
||||
assert(out_samples == data->samples);
|
||||
}
|
||||
}
|
||||
#else
|
||||
reorder_channels(data->audio, s->reorder_out, out->bps, out->nch, out_samples);
|
||||
do_reorder(data, s->reorder_out);
|
||||
#endif
|
||||
|
||||
data->len = out->bps * out_samples * out->nch;
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
@@ -342,11 +386,7 @@ static int af_open(struct af_instance *af)
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->play = play;
|
||||
af->mul = 1;
|
||||
af->data = talloc_zero(s, struct mp_audio);
|
||||
|
||||
af->data->rate = 0;
|
||||
af->filter = filter;
|
||||
|
||||
if (s->opts.cutoff <= 0.0)
|
||||
s->opts.cutoff = af_resample_default_cutoff(s->opts.filter_size);
|
||||
@@ -357,7 +397,7 @@ static int af_open(struct af_instance *af)
|
||||
if (s->avrctx && s->avrctx_out) {
|
||||
return AF_OK;
|
||||
} else {
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[lavrresample] Cannot initialize "
|
||||
MP_ERR(af, "[lavrresample] Cannot initialize "
|
||||
"Libavresample Context. \n");
|
||||
uninit(af);
|
||||
return AF_ERROR;
|
||||
@@ -367,12 +407,9 @@ static int af_open(struct af_instance *af)
|
||||
#define OPT_BASE_STRUCT struct af_resample
|
||||
|
||||
struct af_info af_info_lavrresample = {
|
||||
"Sample frequency conversion using libavresample",
|
||||
"lavrresample",
|
||||
"Stefano Pigozzi (based on Michael Niedermayer's lavcresample)",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open,
|
||||
.info = "Sample frequency conversion using libavresample",
|
||||
.name = "lavrresample",
|
||||
.open = af_open,
|
||||
.test_conversion = test_conversion,
|
||||
.priv_size = sizeof(struct af_resample),
|
||||
.priv_defaults = &(const struct af_resample) {
|
||||
|
||||
+59
-83
@@ -25,6 +25,7 @@
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
@@ -32,6 +33,7 @@ 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)
|
||||
@@ -46,7 +48,7 @@ static void set_channels(struct mp_audio *mpa, int num)
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_pan_t* s = af->setup;
|
||||
af_pan_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
@@ -54,9 +56,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
set_channels(af->data, s->nch ? s->nch: ((struct mp_audio*)arg)->nch);
|
||||
af->mul = (double)af->data->nch / ((struct mp_audio*)arg)->nch;
|
||||
|
||||
if((af->data->format != ((struct mp_audio*)arg)->format) ||
|
||||
(af->data->bps != ((struct mp_audio*)arg)->bps)){
|
||||
@@ -64,33 +65,7 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
return AF_FALSE;
|
||||
}
|
||||
return AF_OK;
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
int nch = 0;
|
||||
int n = 0;
|
||||
char* cp = NULL;
|
||||
int j,k;
|
||||
// Read number of outputs
|
||||
sscanf((char*)arg,"%i%n", &nch,&n);
|
||||
if(AF_OK != control(af,AF_CONTROL_PAN_NOUT | AF_CONTROL_SET, &nch))
|
||||
return AF_ERROR;
|
||||
|
||||
// Read pan values
|
||||
cp = &((char*)arg)[n];
|
||||
j = 0; k = 0;
|
||||
while((*cp == ':') && (k < AF_NCH)){
|
||||
sscanf(cp, ":%f%n" , &s->level[j][k], &n);
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[pan] Pan level from channel %i to"
|
||||
" channel %i = %f\n",k,j,s->level[j][k]);
|
||||
cp =&cp[n];
|
||||
j++;
|
||||
if(j>=nch){
|
||||
j = 0;
|
||||
k++;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_PAN_LEVEL | AF_CONTROL_SET:{
|
||||
case AF_CONTROL_SET_PAN_LEVEL:{
|
||||
int i;
|
||||
int ch = ((af_control_ext_t*)arg)->ch;
|
||||
float* level = ((af_control_ext_t*)arg)->arg;
|
||||
@@ -100,40 +75,30 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
s->level[ch][i] = level[i];
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_PAN_LEVEL | AF_CONTROL_GET:{
|
||||
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++)
|
||||
level[i] = s->level[ch][i];
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_PAN_NOUT | AF_CONTROL_SET:
|
||||
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_msg(MSGT_AFILTER, MSGL_ERR, "[pan] The number of output channels must be"
|
||||
MP_ERR(af, "[pan] 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_PAN_BALANCE | AF_CONTROL_SET:{
|
||||
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] = min(1.f, 1.f - val);
|
||||
s->level[0][1] = max(0.f, val);
|
||||
s->level[1][0] = max(0.f, -val);
|
||||
s->level[1][1] = min(1.f, 1.f + val);
|
||||
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_PAN_BALANCE | AF_CONTROL_GET:
|
||||
case AF_CONTROL_GET_PAN_BALANCE:
|
||||
if (s->nch)
|
||||
return AF_ERROR;
|
||||
*(float*)arg = s->level[0][1] - s->level[1][0];
|
||||
@@ -142,32 +107,22 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
if(af->data)
|
||||
free(af->data->audio);
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
struct mp_audio* l = af->data; // Local data
|
||||
af_pan_t* s = af->setup; // Setup for this instance
|
||||
float* in = c->audio; // Input audio data
|
||||
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->len/4; // End of loop
|
||||
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;
|
||||
|
||||
if(AF_OK != RESIZE_LOCAL_BUFFER(af,data))
|
||||
return NULL;
|
||||
mp_audio_realloc_min(af->data, data->samples);
|
||||
|
||||
out = l->audio;
|
||||
out = l->planes[0];
|
||||
// Execute panning
|
||||
// FIXME: Too slow
|
||||
while(in < end){
|
||||
@@ -183,32 +138,53 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
}
|
||||
|
||||
// Set output data
|
||||
c->audio = l->audio;
|
||||
c->len = c->len / c->nch * l->nch;
|
||||
c->planes[0] = l->planes[0];
|
||||
set_channels(c, l->nch);
|
||||
|
||||
return c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_pan_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
return AF_OK;
|
||||
af->control=control;
|
||||
af->filter=filter;
|
||||
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){
|
||||
sscanf(cp, "%f%n" , &s->level[j][k], &n);
|
||||
MP_VERBOSE(af, "[pan] Pan level from channel %i to"
|
||||
" channel %i = %f\n",k,j,s->level[j][k]);
|
||||
cp =&cp[n];
|
||||
j++;
|
||||
if(j>=nch){
|
||||
j = 0;
|
||||
k++;
|
||||
}
|
||||
if (*cp != ',')
|
||||
break;
|
||||
cp++;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_pan_t
|
||||
struct af_info af_info_pan = {
|
||||
"Panning audio filter",
|
||||
"pan",
|
||||
"Anders",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "Panning audio filter",
|
||||
.name = "pan",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_pan_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_INTRANGE("channels", nch, 0, 0, AF_NCH),
|
||||
OPT_STRING("matrix", matrixstr, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+360
-402
@@ -36,483 +36,441 @@
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "mpvcore/mp_common.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include "af.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_scaletempo_s
|
||||
{
|
||||
// stride
|
||||
float scale;
|
||||
float speed;
|
||||
float frames_stride_scaled;
|
||||
float frames_stride_error;
|
||||
int bytes_per_frame;
|
||||
int bytes_stride;
|
||||
float bytes_stride_scaled;
|
||||
int bytes_queue;
|
||||
int bytes_queued;
|
||||
int bytes_to_slide;
|
||||
int8_t* buf_queue;
|
||||
// overlap
|
||||
int samples_overlap;
|
||||
int samples_standing;
|
||||
int bytes_overlap;
|
||||
int bytes_standing;
|
||||
void* buf_overlap;
|
||||
void* table_blend;
|
||||
void (*output_overlap)(struct af_scaletempo_s* s, void* out_buf, int bytes_off);
|
||||
// best overlap
|
||||
int frames_search;
|
||||
int num_channels;
|
||||
void* buf_pre_corr;
|
||||
void* table_window;
|
||||
int (*best_overlap_offset)(struct af_scaletempo_s* s);
|
||||
// command line
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float percent_overlap;
|
||||
float ms_search;
|
||||
int speed_opt;
|
||||
short speed_tempo;
|
||||
short speed_pitch;
|
||||
// stride
|
||||
float scale;
|
||||
float speed;
|
||||
int frames_stride;
|
||||
float frames_stride_scaled;
|
||||
float frames_stride_error;
|
||||
int bytes_per_frame;
|
||||
int bytes_stride;
|
||||
int bytes_queue;
|
||||
int bytes_queued;
|
||||
int bytes_to_slide;
|
||||
int8_t *buf_queue;
|
||||
// overlap
|
||||
int samples_overlap;
|
||||
int samples_standing;
|
||||
int bytes_overlap;
|
||||
int bytes_standing;
|
||||
void *buf_overlap;
|
||||
void *table_blend;
|
||||
void (*output_overlap)(struct af_scaletempo_s *s, void *out_buf,
|
||||
int bytes_off);
|
||||
// best overlap
|
||||
int frames_search;
|
||||
int num_channels;
|
||||
void *buf_pre_corr;
|
||||
void *table_window;
|
||||
int (*best_overlap_offset)(struct af_scaletempo_s *s);
|
||||
// command line
|
||||
float scale_nominal;
|
||||
float ms_stride;
|
||||
float percent_overlap;
|
||||
float ms_search;
|
||||
int speed_opt;
|
||||
short speed_tempo;
|
||||
short speed_pitch;
|
||||
} af_scaletempo_t;
|
||||
|
||||
static int fill_queue(struct af_instance* af, struct mp_audio* data, int offset)
|
||||
static int fill_queue(struct af_instance *af, struct mp_audio *data, int offset)
|
||||
{
|
||||
af_scaletempo_t* s = af->priv;
|
||||
int bytes_in = data->len - offset;
|
||||
int offset_unchanged = offset;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
int bytes_in = mp_audio_psize(data) - offset;
|
||||
int offset_unchanged = offset;
|
||||
|
||||
if (s->bytes_to_slide > 0) {
|
||||
if (s->bytes_to_slide < s->bytes_queued) {
|
||||
int bytes_move = s->bytes_queued - s->bytes_to_slide;
|
||||
memmove(s->buf_queue,
|
||||
s->buf_queue + s->bytes_to_slide,
|
||||
bytes_move);
|
||||
s->bytes_to_slide = 0;
|
||||
s->bytes_queued = bytes_move;
|
||||
} else {
|
||||
int bytes_skip;
|
||||
s->bytes_to_slide -= s->bytes_queued;
|
||||
bytes_skip = MPMIN(s->bytes_to_slide, bytes_in);
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide -= bytes_skip;
|
||||
offset += bytes_skip;
|
||||
bytes_in -= bytes_skip;
|
||||
if (s->bytes_to_slide > 0) {
|
||||
if (s->bytes_to_slide < s->bytes_queued) {
|
||||
int bytes_move = s->bytes_queued - s->bytes_to_slide;
|
||||
memmove(s->buf_queue, s->buf_queue + s->bytes_to_slide, bytes_move);
|
||||
s->bytes_to_slide = 0;
|
||||
s->bytes_queued = bytes_move;
|
||||
} else {
|
||||
int bytes_skip;
|
||||
s->bytes_to_slide -= s->bytes_queued;
|
||||
bytes_skip = MPMIN(s->bytes_to_slide, bytes_in);
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide -= bytes_skip;
|
||||
offset += bytes_skip;
|
||||
bytes_in -= bytes_skip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_in > 0) {
|
||||
int bytes_copy = MPMIN(s->bytes_queue - s->bytes_queued, bytes_in);
|
||||
assert(bytes_copy >= 0);
|
||||
memcpy(s->buf_queue + s->bytes_queued,
|
||||
(int8_t*)data->audio + offset,
|
||||
bytes_copy);
|
||||
s->bytes_queued += bytes_copy;
|
||||
offset += bytes_copy;
|
||||
}
|
||||
if (bytes_in > 0) {
|
||||
int bytes_copy = MPMIN(s->bytes_queue - s->bytes_queued, bytes_in);
|
||||
assert(bytes_copy >= 0);
|
||||
memcpy(s->buf_queue + s->bytes_queued,
|
||||
(int8_t *)data->planes[0] + offset, bytes_copy);
|
||||
s->bytes_queued += bytes_copy;
|
||||
offset += bytes_copy;
|
||||
}
|
||||
|
||||
return offset - offset_unchanged;
|
||||
return offset - offset_unchanged;
|
||||
}
|
||||
|
||||
#define UNROLL_PADDING (4*4)
|
||||
#define UNROLL_PADDING (4 * 4)
|
||||
|
||||
static int best_overlap_offset_float(af_scaletempo_t* s)
|
||||
static int best_overlap_offset_float(af_scaletempo_t *s)
|
||||
{
|
||||
float *pw, *po, *ppc, *search_start;
|
||||
float best_corr = INT_MIN;
|
||||
int best_off = 0;
|
||||
int i, off;
|
||||
float best_corr = INT_MIN;
|
||||
int best_off = 0;
|
||||
|
||||
pw = s->table_window;
|
||||
po = s->buf_overlap;
|
||||
po += s->num_channels;
|
||||
ppc = s->buf_pre_corr;
|
||||
for (i=s->num_channels; i<s->samples_overlap; i++) {
|
||||
*ppc++ = *pw++ * *po++;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
float *po = s->buf_overlap;
|
||||
po += s->num_channels;
|
||||
float *ppc = s->buf_pre_corr;
|
||||
for (int i = s->num_channels; i < s->samples_overlap; i++)
|
||||
*ppc++ = *pw++ **po++;
|
||||
|
||||
search_start = (float*)s->buf_queue + s->num_channels;
|
||||
for (off=0; off<s->frames_search; off++) {
|
||||
float corr = 0;
|
||||
float* ps = search_start;
|
||||
ppc = s->buf_pre_corr;
|
||||
for (i=s->num_channels; i<s->samples_overlap; i++) {
|
||||
corr += *ppc++ * *ps++;
|
||||
float *search_start = (float *)s->buf_queue + s->num_channels;
|
||||
for (int off = 0; off < s->frames_search; off++) {
|
||||
float corr = 0;
|
||||
float *ps = search_start;
|
||||
ppc = s->buf_pre_corr;
|
||||
for (int i = s->num_channels; i < s->samples_overlap; i++)
|
||||
corr += *ppc++ **ps++;
|
||||
if (corr > best_corr) {
|
||||
best_corr = corr;
|
||||
best_off = off;
|
||||
}
|
||||
search_start += s->num_channels;
|
||||
}
|
||||
if (corr > best_corr) {
|
||||
best_corr = corr;
|
||||
best_off = off;
|
||||
}
|
||||
search_start += s->num_channels;
|
||||
}
|
||||
|
||||
return best_off * 4 * s->num_channels;
|
||||
return best_off * 4 * s->num_channels;
|
||||
}
|
||||
|
||||
static int best_overlap_offset_s16(af_scaletempo_t* s)
|
||||
static int best_overlap_offset_s16(af_scaletempo_t *s)
|
||||
{
|
||||
int32_t *pw, *ppc;
|
||||
int16_t *po, *search_start;
|
||||
int64_t best_corr = INT64_MIN;
|
||||
int best_off = 0;
|
||||
int off;
|
||||
long i;
|
||||
int64_t best_corr = INT64_MIN;
|
||||
int best_off = 0;
|
||||
|
||||
pw = s->table_window;
|
||||
po = s->buf_overlap;
|
||||
po += s->num_channels;
|
||||
ppc = s->buf_pre_corr;
|
||||
for (i=s->num_channels; i<s->samples_overlap; i++) {
|
||||
*ppc++ = ( *pw++ * *po++ ) >> 15;
|
||||
}
|
||||
int32_t *pw = s->table_window;
|
||||
int16_t *po = s->buf_overlap;
|
||||
po += s->num_channels;
|
||||
int32_t *ppc = s->buf_pre_corr;
|
||||
for (long i = s->num_channels; i < s->samples_overlap; i++)
|
||||
*ppc++ = (*pw++ **po++) >> 15;
|
||||
|
||||
search_start = (int16_t*)s->buf_queue + s->num_channels;
|
||||
for (off=0; off<s->frames_search; off++) {
|
||||
int64_t corr = 0;
|
||||
int16_t* ps = search_start;
|
||||
ppc = s->buf_pre_corr;
|
||||
ppc += s->samples_overlap - s->num_channels;
|
||||
ps += s->samples_overlap - s->num_channels;
|
||||
i = -(s->samples_overlap - s->num_channels);
|
||||
do {
|
||||
corr += ppc[i+0] * ps[i+0];
|
||||
corr += ppc[i+1] * ps[i+1];
|
||||
corr += ppc[i+2] * ps[i+2];
|
||||
corr += ppc[i+3] * ps[i+3];
|
||||
i += 4;
|
||||
} while (i < 0);
|
||||
if (corr > best_corr) {
|
||||
best_corr = corr;
|
||||
best_off = off;
|
||||
int16_t *search_start = (int16_t *)s->buf_queue + s->num_channels;
|
||||
for (int off = 0; off < s->frames_search; off++) {
|
||||
int64_t corr = 0;
|
||||
int16_t *ps = search_start;
|
||||
ppc = s->buf_pre_corr;
|
||||
ppc += s->samples_overlap - s->num_channels;
|
||||
ps += s->samples_overlap - s->num_channels;
|
||||
long i = -(s->samples_overlap - s->num_channels);
|
||||
do {
|
||||
corr += ppc[i + 0] * ps[i + 0];
|
||||
corr += ppc[i + 1] * ps[i + 1];
|
||||
corr += ppc[i + 2] * ps[i + 2];
|
||||
corr += ppc[i + 3] * ps[i + 3];
|
||||
i += 4;
|
||||
} while (i < 0);
|
||||
if (corr > best_corr) {
|
||||
best_corr = corr;
|
||||
best_off = off;
|
||||
}
|
||||
search_start += s->num_channels;
|
||||
}
|
||||
search_start += s->num_channels;
|
||||
}
|
||||
|
||||
return best_off * 2 * s->num_channels;
|
||||
return best_off * 2 * s->num_channels;
|
||||
}
|
||||
|
||||
static void output_overlap_float(af_scaletempo_t* s, void* buf_out,
|
||||
int bytes_off)
|
||||
static void output_overlap_float(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
float* pout = buf_out;
|
||||
float* pb = s->table_blend;
|
||||
float* po = s->buf_overlap;
|
||||
float* pin = (float*)(s->buf_queue + bytes_off);
|
||||
int i;
|
||||
for (i=0; i<s->samples_overlap; i++) {
|
||||
*pout++ = *po - *pb++ * ( *po - *pin++ ); po++;
|
||||
}
|
||||
float *pout = buf_out;
|
||||
float *pb = s->table_blend;
|
||||
float *po = s->buf_overlap;
|
||||
float *pin = (float *)(s->buf_queue + bytes_off);
|
||||
for (int i = 0; i < s->samples_overlap; i++) {
|
||||
*pout++ = *po - *pb++ *(*po - *pin++);
|
||||
po++;
|
||||
}
|
||||
}
|
||||
static void output_overlap_s16(af_scaletempo_t* s, void* buf_out,
|
||||
int bytes_off)
|
||||
|
||||
static void output_overlap_s16(af_scaletempo_t *s, void *buf_out,
|
||||
int bytes_off)
|
||||
{
|
||||
int16_t* pout = buf_out;
|
||||
int32_t* pb = s->table_blend;
|
||||
int16_t* po = s->buf_overlap;
|
||||
int16_t* pin = (int16_t*)(s->buf_queue + bytes_off);
|
||||
int i;
|
||||
for (i=0; i<s->samples_overlap; i++) {
|
||||
*pout++ = *po - ( ( *pb++ * ( *po - *pin++ ) ) >> 16 ); po++;
|
||||
}
|
||||
int16_t *pout = buf_out;
|
||||
int32_t *pb = s->table_blend;
|
||||
int16_t *po = s->buf_overlap;
|
||||
int16_t *pin = (int16_t *)(s->buf_queue + bytes_off);
|
||||
for (int i = 0; i < s->samples_overlap; i++) {
|
||||
*pout++ = *po - ((*pb++ *(*po - *pin++)) >> 16);
|
||||
po++;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int flags)
|
||||
{
|
||||
af_scaletempo_t* s = af->priv;
|
||||
int offset_in;
|
||||
int max_bytes_out;
|
||||
int8_t* pout;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
if (s->scale == 1.0) {
|
||||
af->delay = 0;
|
||||
return data;
|
||||
}
|
||||
|
||||
// RESIZE_LOCAL_BUFFER - can't use macro
|
||||
max_bytes_out = ((int)(data->len / s->bytes_stride_scaled) + 1) * s->bytes_stride;
|
||||
if (max_bytes_out > af->data->len) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[libaf] Reallocating memory in module %s, "
|
||||
"old len = %i, new len = %i\n",af->info->name,af->data->len,max_bytes_out);
|
||||
af->data->audio = realloc(af->data->audio, max_bytes_out);
|
||||
if (!af->data->audio) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[libaf] Could not allocate memory\n");
|
||||
return NULL;
|
||||
if (s->scale == 1.0) {
|
||||
af->delay = 0;
|
||||
return 0;
|
||||
}
|
||||
af->data->len = max_bytes_out;
|
||||
}
|
||||
|
||||
offset_in = fill_queue(af, data, 0);
|
||||
pout = af->data->audio;
|
||||
while (s->bytes_queued >= s->bytes_queue) {
|
||||
int ti;
|
||||
float tf;
|
||||
int bytes_off = 0;
|
||||
mp_audio_realloc_min(af->data,
|
||||
((int)(data->samples / s->frames_stride_scaled) + 1) * s->frames_stride);
|
||||
|
||||
// output stride
|
||||
if (s->output_overlap) {
|
||||
if (s->best_overlap_offset)
|
||||
bytes_off = s->best_overlap_offset(s);
|
||||
s->output_overlap(s, pout, bytes_off);
|
||||
int offset_in = fill_queue(af, data, 0);
|
||||
int8_t *pout = af->data->planes[0];
|
||||
while (s->bytes_queued >= s->bytes_queue) {
|
||||
int ti;
|
||||
float tf;
|
||||
int bytes_off = 0;
|
||||
|
||||
// output stride
|
||||
if (s->output_overlap) {
|
||||
if (s->best_overlap_offset)
|
||||
bytes_off = s->best_overlap_offset(s);
|
||||
s->output_overlap(s, pout, bytes_off);
|
||||
}
|
||||
memcpy(pout + s->bytes_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_overlap,
|
||||
s->bytes_standing);
|
||||
pout += s->bytes_stride;
|
||||
|
||||
// input stride
|
||||
memcpy(s->buf_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_stride,
|
||||
s->bytes_overlap);
|
||||
tf = s->frames_stride_scaled + s->frames_stride_error;
|
||||
ti = (int)tf;
|
||||
s->frames_stride_error = tf - ti;
|
||||
s->bytes_to_slide = ti * s->bytes_per_frame;
|
||||
|
||||
offset_in += fill_queue(af, data, offset_in);
|
||||
}
|
||||
memcpy(pout + s->bytes_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_overlap,
|
||||
s->bytes_standing);
|
||||
pout += s->bytes_stride;
|
||||
|
||||
// input stride
|
||||
memcpy(s->buf_overlap,
|
||||
s->buf_queue + bytes_off + s->bytes_stride,
|
||||
s->bytes_overlap);
|
||||
tf = s->frames_stride_scaled + s->frames_stride_error;
|
||||
ti = (int)tf;
|
||||
s->frames_stride_error = tf - ti;
|
||||
s->bytes_to_slide = ti * s->bytes_per_frame;
|
||||
// This filter can have a negative delay when scale > 1:
|
||||
// output corresponding to some length of input can be decided and written
|
||||
// after receiving only a part of that input.
|
||||
af->delay = (s->bytes_queued - s->bytes_to_slide) / s->scale
|
||||
/ af->data->sstride / af->data->rate;
|
||||
|
||||
offset_in += fill_queue(af, data, offset_in);
|
||||
}
|
||||
|
||||
// This filter can have a negative delay when scale > 1:
|
||||
// output corresponding to some length of input can be decided and written
|
||||
// after receiving only a part of that input.
|
||||
af->delay = s->bytes_queued - s->bytes_to_slide;
|
||||
|
||||
data->audio = af->data->audio;
|
||||
data->len = pout - (int8_t *)af->data->audio;
|
||||
return data;
|
||||
data->planes[0] = af->data->planes[0];
|
||||
data->samples = (pout - (int8_t *)af->data->planes[0]) / af->data->sstride;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
af_scaletempo_t* s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
struct mp_audio* data = (struct mp_audio*)arg;
|
||||
float srate = data->rate / 1000;
|
||||
int nch = data->nch;
|
||||
int bps;
|
||||
int use_int = 0;
|
||||
int frames_stride, frames_overlap;
|
||||
int i, j;
|
||||
af_scaletempo_t *s = af->priv;
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *data = (struct mp_audio *)arg;
|
||||
float srate = data->rate / 1000;
|
||||
int nch = data->nch;
|
||||
int use_int = 0;
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V,
|
||||
"[scaletempo] %.3f speed * %.3f scale_nominal = %.3f\n",
|
||||
s->speed, s->scale_nominal, s->scale);
|
||||
MP_VERBOSE(af, "[scaletempo] %.3f speed * %.3f scale_nominal = %.3f\n",
|
||||
s->speed, s->scale_nominal, s->scale);
|
||||
|
||||
mp_audio_copy_config(af->data, data);
|
||||
mp_audio_force_interleaved_format(data);
|
||||
mp_audio_copy_config(af->data, data);
|
||||
|
||||
if (s->scale == 1.0) {
|
||||
if (s->speed_tempo && s->speed_pitch)
|
||||
return AF_DETACH;
|
||||
af->delay = 0;
|
||||
af->mul = 1;
|
||||
return af_test_output(af, data);
|
||||
}
|
||||
|
||||
if (data->format == AF_FORMAT_S16_NE) {
|
||||
use_int = 1;
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
}
|
||||
bps = af->data->bps;
|
||||
|
||||
frames_stride = srate * s->ms_stride;
|
||||
s->bytes_stride = frames_stride * bps * nch;
|
||||
s->bytes_stride_scaled = s->scale * s->bytes_stride;
|
||||
s->frames_stride_scaled = s->scale * frames_stride;
|
||||
s->frames_stride_error = 0;
|
||||
af->mul = (double)s->bytes_stride / s->bytes_stride_scaled;
|
||||
af->delay = 0;
|
||||
|
||||
frames_overlap = frames_stride * s->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if(!s->buf_overlap || !s->table_blend) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[scaletempo] Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t* pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (i=0; i<frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (j=0; j<nch; j++) {
|
||||
*pb++ = v;
|
||||
}
|
||||
blend += 65536; // 2^16
|
||||
if (s->scale == 1.0) {
|
||||
if (s->speed_tempo && s->speed_pitch)
|
||||
return AF_DETACH;
|
||||
af->delay = 0;
|
||||
af->mul = 1;
|
||||
return af_test_output(af, data);
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float* pb = s->table_blend;
|
||||
for (i=0; i<frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (j=0; j<nch; j++) {
|
||||
*pb++ = v;
|
||||
}
|
||||
|
||||
if (data->format == AF_FORMAT_S16) {
|
||||
use_int = 1;
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
int bps = af->data->bps;
|
||||
|
||||
s->frames_stride = srate * s->ms_stride;
|
||||
s->bytes_stride = s->frames_stride * bps * nch;
|
||||
s->frames_stride_scaled = s->scale * s->frames_stride;
|
||||
s->frames_stride_error = 0;
|
||||
af->mul = 1.0 / s->scale;
|
||||
af->delay = 0;
|
||||
|
||||
int frames_overlap = s->frames_stride * s->percent_overlap;
|
||||
if (frames_overlap <= 0) {
|
||||
s->bytes_standing = s->bytes_stride;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->output_overlap = NULL;
|
||||
s->bytes_overlap = 0;
|
||||
} else {
|
||||
s->samples_overlap = frames_overlap * nch;
|
||||
s->bytes_overlap = frames_overlap * nch * bps;
|
||||
s->bytes_standing = s->bytes_stride - s->bytes_overlap;
|
||||
s->samples_standing = s->bytes_standing / bps;
|
||||
s->buf_overlap = realloc(s->buf_overlap, s->bytes_overlap);
|
||||
s->table_blend = realloc(s->table_blend, s->bytes_overlap * 4);
|
||||
if (!s->buf_overlap || !s->table_blend) {
|
||||
MP_FATAL(af, "[scaletempo] Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
if (use_int) {
|
||||
int32_t *pb = s->table_blend;
|
||||
int64_t blend = 0;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
int32_t v = blend / frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
blend += 65536; // 2^16
|
||||
}
|
||||
s->output_overlap = output_overlap_s16;
|
||||
} else {
|
||||
float *pb = s->table_blend;
|
||||
for (int i = 0; i < frames_overlap; i++) {
|
||||
float v = i / (float)frames_overlap;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pb++ = v;
|
||||
}
|
||||
s->output_overlap = output_overlap_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0;
|
||||
if (s->frames_search <= 0)
|
||||
s->best_overlap_offset = NULL;
|
||||
else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr,
|
||||
s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "[scaletempo] Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0,
|
||||
UNROLL_PADDING);
|
||||
int32_t *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
int32_t v = (i * (t - i) * n) >> 15;
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window,
|
||||
s->bytes_overlap - nch * bps);
|
||||
if (!s->buf_pre_corr || !s->table_window) {
|
||||
MP_FATAL(af, "[scaletempo] Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
float *pw = s->table_window;
|
||||
for (int i = 1; i < frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (int j = 0; j < nch; j++)
|
||||
*pw++ = v;
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap)
|
||||
* bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if (!s->buf_queue) {
|
||||
MP_FATAL(af, "[scaletempo] Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
MP_DBG(af, "[scaletempo] "
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int ? "s16" : "float"));
|
||||
|
||||
return af_test_output(af, (struct mp_audio *)arg);
|
||||
}
|
||||
|
||||
s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0;
|
||||
if (s->frames_search <= 0) {
|
||||
s->best_overlap_offset = NULL;
|
||||
} else {
|
||||
if (use_int) {
|
||||
int64_t t = frames_overlap;
|
||||
int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2
|
||||
int32_t* pw;
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap * 2 + UNROLL_PADDING);
|
||||
s->table_window = realloc(s->table_window, s->bytes_overlap * 2 - nch * bps * 2);
|
||||
if(!s->buf_pre_corr || !s->table_window) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[scaletempo] Out of memory\n");
|
||||
return AF_ERROR;
|
||||
case AF_CONTROL_SET_PLAYBACK_SPEED: {
|
||||
if (s->speed_tempo) {
|
||||
if (s->speed_pitch)
|
||||
break;
|
||||
s->speed = *(double *)arg;
|
||||
s->scale = s->speed * s->scale_nominal;
|
||||
} else {
|
||||
if (s->speed_pitch) {
|
||||
s->speed = 1 / *(double *)arg;
|
||||
s->scale = s->speed * s->scale_nominal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0, UNROLL_PADDING);
|
||||
pw = s->table_window;
|
||||
for (i=1; i<frames_overlap; i++) {
|
||||
int32_t v = ( i * (t - i) * n ) >> 15;
|
||||
for (j=0; j<nch; j++) {
|
||||
*pw++ = v;
|
||||
}
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_s16;
|
||||
} else {
|
||||
float* pw;
|
||||
s->buf_pre_corr = realloc(s->buf_pre_corr, s->bytes_overlap);
|
||||
s->table_window = realloc(s->table_window, s->bytes_overlap - nch * bps);
|
||||
if(!s->buf_pre_corr || !s->table_window) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[scaletempo] Out of memory\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
pw = s->table_window;
|
||||
for (i=1; i<frames_overlap; i++) {
|
||||
float v = i * (frames_overlap - i);
|
||||
for (j=0; j<nch; j++) {
|
||||
*pw++ = v;
|
||||
}
|
||||
}
|
||||
s->best_overlap_offset = best_overlap_offset_float;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
s->bytes_per_frame = bps * nch;
|
||||
s->num_channels = nch;
|
||||
|
||||
s->bytes_queue
|
||||
= (s->frames_search + frames_stride + frames_overlap) * bps * nch;
|
||||
s->buf_queue = realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING);
|
||||
if(!s->buf_queue) {
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[scaletempo] Out of memory\n");
|
||||
return AF_ERROR;
|
||||
case AF_CONTROL_RESET:
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
s->frames_stride_error = 0;
|
||||
memset(s->buf_overlap, 0, s->bytes_overlap);
|
||||
}
|
||||
|
||||
s->bytes_queued = 0;
|
||||
s->bytes_to_slide = 0;
|
||||
|
||||
mp_msg (MSGT_AFILTER, MSGL_DBG2, "[scaletempo] "
|
||||
"%.2f stride_in, %i stride_out, %i standing, "
|
||||
"%i overlap, %i search, %i queue, %s mode\n",
|
||||
s->frames_stride_scaled,
|
||||
(int)(s->bytes_stride / nch / bps),
|
||||
(int)(s->bytes_standing / nch / bps),
|
||||
(int)(s->bytes_overlap / nch / bps),
|
||||
s->frames_search,
|
||||
(int)(s->bytes_queue / nch / bps),
|
||||
(use_int?"s16":"float"));
|
||||
|
||||
return af_test_output(af, (struct mp_audio*)arg);
|
||||
}
|
||||
case AF_CONTROL_PLAYBACK_SPEED | AF_CONTROL_SET:{
|
||||
if (s->speed_tempo) {
|
||||
if (s->speed_pitch) {
|
||||
break;
|
||||
}
|
||||
s->speed = *(double *)arg;
|
||||
s->scale = s->speed * s->scale_nominal;
|
||||
} else {
|
||||
if (s->speed_pitch) {
|
||||
s->speed = 1 / *(double *)arg;
|
||||
s->scale = s->speed * s->scale_nominal;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SCALETEMPO_AMOUNT | AF_CONTROL_SET:{
|
||||
s->scale = *(float*)arg;
|
||||
s->scale = s->speed * s->scale_nominal;
|
||||
return AF_OK;
|
||||
}
|
||||
case AF_CONTROL_SCALETEMPO_AMOUNT | AF_CONTROL_GET:
|
||||
*(float*)arg = s->scale;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
static void uninit(struct af_instance *af)
|
||||
{
|
||||
af_scaletempo_t* s = af->priv;
|
||||
free(af->data->audio);
|
||||
free(af->data);
|
||||
free(s->buf_queue);
|
||||
free(s->buf_overlap);
|
||||
free(s->buf_pre_corr);
|
||||
free(s->table_blend);
|
||||
free(s->table_window);
|
||||
af_scaletempo_t *s = af->priv;
|
||||
free(s->buf_queue);
|
||||
free(s->buf_overlap);
|
||||
free(s->buf_pre_corr);
|
||||
free(s->table_blend);
|
||||
free(s->table_window);
|
||||
}
|
||||
|
||||
#define SCALE_TEMPO 1
|
||||
#define SCALE_PITCH 2
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af_scaletempo_t *s = af->priv;
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
af_scaletempo_t *s = af->priv;
|
||||
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->play = play;
|
||||
af->mul = 1;
|
||||
af->data = calloc(1,sizeof(struct mp_audio));
|
||||
if(af->data == NULL)
|
||||
return AF_ERROR;
|
||||
af->control = control;
|
||||
af->uninit = uninit;
|
||||
af->filter = filter;
|
||||
|
||||
s->speed_tempo = !!(s->speed_opt & SCALE_TEMPO);
|
||||
s->speed_pitch = !!(s->speed_opt & SCALE_PITCH);
|
||||
s->speed_tempo = !!(s->speed_opt & SCALE_TEMPO);
|
||||
s->speed_pitch = !!(s->speed_opt & SCALE_PITCH);
|
||||
|
||||
s->scale = s->speed * s->scale_nominal;
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG2, "[scaletempo] %6.3f scale, %6.2f stride, %6.2f overlap, %6.2f search, speed = %s\n", s->scale_nominal, s->ms_stride, s->percent_overlap, s->ms_search, (s->speed_tempo?(s->speed_pitch?"tempo and speed":"tempo"):(s->speed_pitch?"pitch":"none")));
|
||||
|
||||
return AF_OK;
|
||||
s->scale = s->speed * s->scale_nominal;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_scaletempo_t
|
||||
|
||||
struct af_info af_info_scaletempo = {
|
||||
"Scale audio tempo while maintaining pitch",
|
||||
"scaletempo",
|
||||
"Robert Juliano",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open,
|
||||
.info = "Scale audio tempo while maintaining pitch",
|
||||
.name = "scaletempo",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_scaletempo_t),
|
||||
.priv_defaults = &(const af_scaletempo_t) {
|
||||
.ms_stride = 60,
|
||||
|
||||
@@ -41,14 +41,12 @@ typedef struct af_sinesuppress_s
|
||||
double pos;
|
||||
}af_sinesuppress_t;
|
||||
|
||||
static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data);
|
||||
static int play_s16(struct af_instance* af, struct mp_audio* data, int f);
|
||||
//static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data);
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_sinesuppress_t* s = (af_sinesuppress_t*)af->setup;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
// Sanity check
|
||||
@@ -57,45 +55,31 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_num_channels(af->data, 1);
|
||||
#if 0
|
||||
if (((struct mp_audio*)arg)->format == AF_FORMAT_FLOAT_NE)
|
||||
if (((struct mp_audio*)arg)->format == AF_FORMAT_FLOAT)
|
||||
{
|
||||
af->data->format = AF_FORMAT_FLOAT_NE;
|
||||
af->data->format = AF_FORMAT_FLOAT;
|
||||
af->data->bps = 4;
|
||||
af->play = play_float;
|
||||
}// else
|
||||
#endif
|
||||
{
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16_NE);
|
||||
af->play = play_s16;
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
af->filter = play_s16;
|
||||
}
|
||||
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
float f1,f2;
|
||||
sscanf((char*)arg,"%f:%f", &f1,&f2);
|
||||
s->freq = f1;
|
||||
s->decay = f2;
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data)
|
||||
static int play_s16(struct af_instance* af, struct mp_audio* data, int f)
|
||||
{
|
||||
af_sinesuppress_t *s = af->setup;
|
||||
af_sinesuppress_t *s = af->priv;
|
||||
register int i = 0;
|
||||
int16_t *a = (int16_t*)data->audio; // Audio data
|
||||
int len = data->len/2; // Number of samples
|
||||
int16_t *a = (int16_t*)data->planes[0]; // Audio data
|
||||
int len = data->samples*data->nch; // Number of samples
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
@@ -115,9 +99,9 @@ static struct mp_audio* play_s16(struct af_instance* af, struct mp_audio* data)
|
||||
s->pos += 2 * M_PI * s->freq / data->rate;
|
||||
}
|
||||
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[sinesuppress] f:%8.2f: amp:%8.2f\n", s->freq, sqrt(s->real*s->real + s->imag*s->imag) / s->ref);
|
||||
MP_VERBOSE(af, "[sinesuppress] f:%8.2f: amp:%8.2f\n", s->freq, sqrt(s->real*s->real + s->imag*s->imag) / s->ref);
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -147,25 +131,19 @@ static struct mp_audio* play_float(struct af_instance* af, struct mp_audio* data
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play_s16;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_sinesuppress_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
|
||||
((af_sinesuppress_t*)af->setup)->freq = 50.0;
|
||||
((af_sinesuppress_t*)af->setup)->decay = 0.0001;
|
||||
af->filter=play_s16;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_sinesuppress_t
|
||||
struct af_info af_info_sinesuppress = {
|
||||
"Sine Suppress",
|
||||
"sinesuppress",
|
||||
"Michael Niedermayer",
|
||||
"",
|
||||
0,
|
||||
af_open
|
||||
.info = "Sine Suppress",
|
||||
.name = "sinesuppress",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_sinesuppress_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_DOUBLE("freq", freq, 0, OPTDEF_DOUBLE(50.0)),
|
||||
OPT_DOUBLE("decay", decay, 0, OPTDEF_DOUBLE(0.0001)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+21
-66
@@ -32,6 +32,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
#include "dsp.h"
|
||||
|
||||
@@ -62,7 +63,7 @@ typedef struct af_sub_s
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_sub_t* s = af->setup;
|
||||
af_sub_t* s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
@@ -70,8 +71,8 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
if(!arg) return AF_ERROR;
|
||||
|
||||
af->data->rate = ((struct mp_audio*)arg)->rate;
|
||||
mp_audio_set_channels_old(af->data, max(s->ch+1,((struct mp_audio*)arg)->nch));
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
mp_audio_set_channels_old(af->data, MPMAX(s->ch+1,((struct mp_audio*)arg)->nch));
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
|
||||
// Design low-pass filter
|
||||
s->k = 1.0;
|
||||
@@ -82,50 +83,10 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
return AF_ERROR;
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
}
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
int ch=5;
|
||||
float fc=60.0;
|
||||
sscanf(arg,"%f:%i", &fc , &ch);
|
||||
if(AF_OK != control(af,AF_CONTROL_SUB_CH | AF_CONTROL_SET, &ch))
|
||||
return AF_ERROR;
|
||||
return control(af,AF_CONTROL_SUB_FC | AF_CONTROL_SET, &fc);
|
||||
}
|
||||
case AF_CONTROL_SUB_CH | AF_CONTROL_SET: // Requires reinit
|
||||
// Sanity check
|
||||
if((*(int*)arg >= AF_NCH) || (*(int*)arg < 0)){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[sub] Subwoofer channel number must be between "
|
||||
" 0 and %i current value is %i\n", AF_NCH-1, *(int*)arg);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->ch = *(int*)arg;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SUB_CH | AF_CONTROL_GET:
|
||||
*(int*)arg = s->ch;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SUB_FC | AF_CONTROL_SET: // Requires reinit
|
||||
// Sanity check
|
||||
if((*(float*)arg > 300) || (*(float*)arg < 20)){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[sub] Cutoff frequency must be between 20Hz and"
|
||||
" 300Hz current value is %0.2f",*(float*)arg);
|
||||
return AF_ERROR;
|
||||
}
|
||||
// Set cutoff frequency
|
||||
s->fc = *(float*)arg;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_SUB_FC | AF_CONTROL_GET:
|
||||
*(float*)arg = s->fc;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
#ifndef IIR
|
||||
#define IIR(in,w,q,out) { \
|
||||
float h0 = (q)[0]; \
|
||||
@@ -138,12 +99,12 @@ static void uninit(struct af_instance* af)
|
||||
#endif
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_sub_t* s = af->setup; // Setup for this instance
|
||||
float* a = c->audio; // Audio data
|
||||
int len = c->len/4; // Number of samples in current audio block
|
||||
af_sub_t* s = af->priv; // Setup for this instance
|
||||
float* a = c->planes[0]; // Audio data
|
||||
int len = c->samples*c->nch; // Number of samples in current audio block
|
||||
int nch = c->nch; // Number of channels
|
||||
int ch = s->ch; // Channel in which to insert the sub audio
|
||||
register int i;
|
||||
@@ -156,32 +117,26 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
IIR(x , s->w[1], s->q[1], a[i+ch]);
|
||||
}
|
||||
|
||||
return c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
af_sub_t* s;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=s=calloc(1,sizeof(af_sub_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
// Set default values
|
||||
s->ch = 5; // Channel nr 6
|
||||
s->fc = 60; // Cutoff frequency 60Hz
|
||||
af->filter=filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
// Description of this filter
|
||||
#define OPT_BASE_STRUCT af_sub_t
|
||||
struct af_info af_info_sub = {
|
||||
"Audio filter for adding a sub-base channel",
|
||||
"sub",
|
||||
"Anders",
|
||||
"",
|
||||
AF_FLAGS_NOT_REENTRANT,
|
||||
af_open
|
||||
.info = "Audio filter for adding a sub-base channel",
|
||||
.name = "sub",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_sub_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOATRANGE("fc", fc, 0, 20, 300, OPTDEF_FLOAT(60.0)),
|
||||
OPT_INTRANGE("ch", ch, 0, 0, AF_NCH - 1, OPTDEF_INT(5)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+31
-59
@@ -88,22 +88,24 @@ typedef struct af_surround_s
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_surround_t *s = af->setup;
|
||||
af_surround_t *s = af->priv;
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:{
|
||||
struct mp_audio *in = arg;
|
||||
float fc;
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
mp_audio_set_channels_old(af->data, ((struct mp_audio*)arg)->nch*2);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
|
||||
if (af->data->nch != 4){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[surround] Only stereo input is supported.\n");
|
||||
return AF_DETACH;
|
||||
if (!mp_chmap_is_stereo(&in->channels)) {
|
||||
MP_ERR(af, "[surround] Only stereo input is supported.\n");
|
||||
return AF_DETACH;
|
||||
}
|
||||
|
||||
mp_audio_set_format(in, AF_FORMAT_FLOAT);
|
||||
mp_audio_copy_config(af->data, in);
|
||||
mp_audio_set_channels_old(af->data, in->nch * 2);
|
||||
|
||||
// Surround filer coefficients
|
||||
fc = 2.0 * 7000.0/(float)af->data->rate;
|
||||
if (-1 == af_filter_design_fir(L, s->w, &fc, LP|HAMMING, 0)){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[surround] Unable to design low-pass filter.\n");
|
||||
MP_ERR(af, "[surround] Unable to design low-pass filter.\n");
|
||||
return AF_ERROR;
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
s->dl = calloc(LD,af->data->bps);
|
||||
s->dr = calloc(LD,af->data->bps);
|
||||
if((NULL == s->dl) || (NULL == s->dr))
|
||||
mp_msg(MSGT_AFILTER, MSGL_FATAL, "[delay] Out of memory\n");
|
||||
MP_FATAL(af, "[delay] Out of memory\n");
|
||||
|
||||
// Initialize delay queue index
|
||||
if(AF_OK != af_from_ms(1, &s->d, &s->wi, af->data->rate, 0.0, 1000.0))
|
||||
@@ -122,37 +124,12 @@ static int control(struct af_instance* af, int cmd, void* arg)
|
||||
// printf("%i\n",s->wi);
|
||||
s->ri = 0;
|
||||
|
||||
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_COMMAND_LINE:{
|
||||
float d = 0;
|
||||
sscanf((char*)arg,"%f",&d);
|
||||
if ((d < 0) || (d > 1000)){
|
||||
mp_msg(MSGT_AFILTER, MSGL_ERR, "[surround] Invalid delay time, valid time values"
|
||||
" are 0ms to 1000ms current value is %0.3f ms\n",d);
|
||||
return AF_ERROR;
|
||||
}
|
||||
s->d = d;
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
if(af->data)
|
||||
free(af->data->audio);
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
// The beginnings of an active matrix...
|
||||
static float steering_matrix[][12] = {
|
||||
// LL RL LR RR LS RS
|
||||
@@ -165,20 +142,19 @@ static float steering_matrix[][12] = {
|
||||
//static int amp_L = 0, amp_R = 0, amp_C = 0, amp_S = 0;
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data){
|
||||
af_surround_t* s = (af_surround_t*)af->setup;
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int flags){
|
||||
af_surround_t* s = (af_surround_t*)af->priv;
|
||||
float* m = steering_matrix[0];
|
||||
float* in = data->audio; // Input audio data
|
||||
float* in = data->planes[0]; // Input audio data
|
||||
float* out = NULL; // Output audio data
|
||||
float* end = in + data->len / sizeof(float); // Loop end
|
||||
float* end = in + data->samples * data->nch;
|
||||
int i = s->i; // Filter queue index
|
||||
int ri = s->ri; // Read index for delay queue
|
||||
int wi = s->wi; // Write index for delay queue
|
||||
|
||||
if (AF_OK != RESIZE_LOCAL_BUFFER(af, data))
|
||||
return NULL;
|
||||
mp_audio_realloc_min(af->data, data->samples);
|
||||
|
||||
out = af->data->audio;
|
||||
out = af->data->planes[0];
|
||||
|
||||
while(in < end){
|
||||
/* Dominance:
|
||||
@@ -240,32 +216,28 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data){
|
||||
s->i = i; s->ri = ri; s->wi = wi;
|
||||
|
||||
// Set output data
|
||||
data->audio = af->data->audio;
|
||||
data->len *= 2;
|
||||
data->planes[0] = af->data->planes[0];
|
||||
mp_audio_set_channels_old(data, af->data->nch);
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=2;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_surround_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
((af_surround_t*)af->setup)->d = 20;
|
||||
af->filter=filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_surround_t
|
||||
struct af_info af_info_surround =
|
||||
{
|
||||
"Surround decoder filter",
|
||||
"surround",
|
||||
"Steve Davies <steve@daviesfam.org>",
|
||||
"",
|
||||
AF_FLAGS_NOT_REENTRANT,
|
||||
af_open
|
||||
.info = "Surround decoder filter",
|
||||
.name = "surround",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_surround_t),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOATRANGE("d", d, 0, 0, 1000, OPTDEF_FLOAT(20.0)),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+16
-31
@@ -36,40 +36,26 @@ typedef struct af_sweep_s{
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
{
|
||||
af_sweept* s = (af_sweept*)af->setup;
|
||||
struct mp_audio *data= (struct mp_audio*)arg;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
mp_audio_copy_config(af->data, data);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16_NE);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
|
||||
return af_test_output(af, data);
|
||||
case AF_CONTROL_COMMAND_LINE:
|
||||
sscanf((char*)arg,"%lf", &s->delta);
|
||||
return AF_OK;
|
||||
/* case AF_CONTROL_RESAMPLE_RATE | AF_CONTROL_SET:
|
||||
af->data->rate = *(int*)arg;
|
||||
return AF_OK;*/
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
static int filter(struct af_instance* af, struct mp_audio* data, int f)
|
||||
{
|
||||
af_sweept *s = af->setup;
|
||||
af_sweept *s = af->priv;
|
||||
int i, j;
|
||||
int16_t *in = (int16_t*)data->audio;
|
||||
int16_t *in = (int16_t*)data->planes[0];
|
||||
int chans = data->nch;
|
||||
int in_len = data->len/(2*chans);
|
||||
int in_len = data->samples;
|
||||
|
||||
for(i=0; i<in_len; i++){
|
||||
for(j=0; j<chans; j++)
|
||||
@@ -78,24 +64,23 @@ static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
if(2*s->x*s->delta >= 3.141592) s->x=0;
|
||||
}
|
||||
|
||||
return data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance* af){
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_sweept));
|
||||
af->filter=filter;
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT af_sweept
|
||||
struct af_info af_info_sweep = {
|
||||
"sine sweep",
|
||||
"sweep",
|
||||
"Michael Niedermayer",
|
||||
"",
|
||||
AF_FLAGS_REENTRANT,
|
||||
af_open
|
||||
.info = "sine sweep",
|
||||
.name = "sweep",
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(af_sweept),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_DOUBLE("delta", delta, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+83
-165
@@ -18,22 +18,6 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/* This audio filter changes the volume of the sound, and can be used
|
||||
when the mixer doesn't support the PCM channel. It can handle
|
||||
between 1 and AF_NCH channels. The volume can be adjusted between -60dB
|
||||
to +20dB and is set on a per channels basis. The is accessed through
|
||||
AF_CONTROL_VOLUME_LEVEL.
|
||||
|
||||
The filter has support for soft-clipping, it is enabled by
|
||||
AF_CONTROL_VOLUME_SOFTCLIPP. It has also a probing feature which
|
||||
can be used to measure the power in the audio stream, both an
|
||||
instantaneous value and the maximum value can be probed. The
|
||||
probing is enable by AF_CONTROL_VOLUME_PROBE_ON_OFF and is done on a
|
||||
per channel basis. The result from the probing is obtained using
|
||||
AF_CONTROL_VOLUME_PROBE_GET and AF_CONTROL_VOLUME_PROBE_GET_MAX. The
|
||||
probed values are calculated in dB.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -42,167 +26,101 @@
|
||||
#include <math.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
// Data for specific instances of this filter
|
||||
typedef struct af_volume_s
|
||||
struct priv {
|
||||
float level; // Gain level for each channel
|
||||
int soft; // Enable/disable soft clipping
|
||||
int fast; // Use fix-point volume control
|
||||
float cfg_volume;
|
||||
};
|
||||
|
||||
static int control(struct af_instance *af, int cmd, void *arg)
|
||||
{
|
||||
int enable[AF_NCH]; // Enable/disable / channel
|
||||
float pow[AF_NCH]; // Estimated power level [dB]
|
||||
float max[AF_NCH]; // Max Power level [dB]
|
||||
float level[AF_NCH]; // Gain level for each channel
|
||||
float time; // Forgetting factor for power estimate
|
||||
int soft; // Enable/disable soft clipping
|
||||
int fast; // Use fix-point volume control
|
||||
}af_volume_t;
|
||||
struct priv *s = af->priv;
|
||||
|
||||
// Initialization and runtime control
|
||||
static int control(struct af_instance* af, int cmd, void* arg)
|
||||
switch (cmd) {
|
||||
case AF_CONTROL_REINIT: {
|
||||
struct mp_audio *in = arg;
|
||||
|
||||
mp_audio_copy_config(af->data, in);
|
||||
mp_audio_force_interleaved_format(af->data);
|
||||
|
||||
if (s->fast && af_fmt_from_planar(in->format) != AF_FORMAT_FLOAT) {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16);
|
||||
} else {
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT);
|
||||
}
|
||||
if (af_fmt_is_planar(in->format))
|
||||
mp_audio_set_format(af->data, af_fmt_to_planar(af->data->format));
|
||||
return af_test_output(af, in);
|
||||
}
|
||||
case AF_CONTROL_SET_VOLUME:
|
||||
s->level = *(float *)arg;
|
||||
return AF_OK;
|
||||
case AF_CONTROL_GET_VOLUME:
|
||||
*(float *)arg = s->level;
|
||||
return AF_OK;
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
static void filter_plane(struct af_instance *af, void *ptr, int num_samples)
|
||||
{
|
||||
af_volume_t* s = (af_volume_t*)af->setup;
|
||||
struct priv *s = af->priv;
|
||||
|
||||
switch(cmd){
|
||||
case AF_CONTROL_REINIT:
|
||||
// Sanity check
|
||||
if(!arg) return AF_ERROR;
|
||||
if (af_fmt_from_planar(af->data->format) == AF_FORMAT_S16) {
|
||||
int16_t *a = ptr;
|
||||
int vol = 256.0 * s->level;
|
||||
if (vol != 256) {
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
int x = (a[i] * vol) >> 8;
|
||||
a[i] = MPCLAMP(x, SHRT_MIN, SHRT_MAX);
|
||||
}
|
||||
}
|
||||
} else if (af_fmt_from_planar(af->data->format) == AF_FORMAT_FLOAT) {
|
||||
float *a = ptr;
|
||||
float vol = s->level;
|
||||
if (vol != 1.0) {
|
||||
for (int i = 0; i < num_samples; i++) {
|
||||
float x = a[i] * vol;
|
||||
a[i] = s->soft ? af_softclip(x) : MPCLAMP(x, -1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mp_audio_copy_config(af->data, (struct mp_audio*)arg);
|
||||
static int filter(struct af_instance *af, struct mp_audio *data, int f)
|
||||
{
|
||||
for (int n = 0; n < data->num_planes; n++)
|
||||
filter_plane(af, data->planes[n], data->samples * data->spf);
|
||||
|
||||
if(s->fast && (((struct mp_audio*)arg)->format != (AF_FORMAT_FLOAT_NE))){
|
||||
mp_audio_set_format(af->data, AF_FORMAT_S16_NE);
|
||||
}
|
||||
else{
|
||||
// Cutoff set to 10Hz for forgetting factor
|
||||
float x = 2.0*M_PI*15.0/(float)af->data->rate;
|
||||
float t = 2.0-cos(x);
|
||||
s->time = 1.0 - (t - sqrt(t*t - 1));
|
||||
mp_msg(MSGT_AFILTER, MSGL_DBG2, "[volume] Forgetting factor = %0.5f\n",s->time);
|
||||
mp_audio_set_format(af->data, AF_FORMAT_FLOAT_NE);
|
||||
}
|
||||
return af_test_output(af,(struct mp_audio*)arg);
|
||||
case AF_CONTROL_COMMAND_LINE:{
|
||||
float v=1000.0;
|
||||
float vol[AF_NCH];
|
||||
int i;
|
||||
sscanf((char*)arg,"%f:%i:%i", &v, &s->soft, &s->fast);
|
||||
float dest = 0.0;
|
||||
if (v < 1000)
|
||||
af_from_dB(1,&v,&dest,20.0,-200.0,60.0);
|
||||
for(i=0;i<AF_NCH;i++) vol[i]=dest;
|
||||
return control(af,AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET, vol);
|
||||
}
|
||||
case AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET:
|
||||
memcpy(s->level, arg, sizeof(float) * AF_NCH);
|
||||
return AF_OK;
|
||||
case AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_GET:
|
||||
memcpy(arg, s->level, sizeof(float) * AF_NCH);
|
||||
return AF_OK;
|
||||
case AF_CONTROL_PRE_DESTROY:{
|
||||
float m = 0.0;
|
||||
int i;
|
||||
if(!s->fast){
|
||||
for(i=0;i<AF_NCH;i++)
|
||||
m=max(m,s->max[i]);
|
||||
af_to_dB(1, &m, &m, 10.0);
|
||||
mp_msg(MSGT_AFILTER, MSGL_V, "[volume] The maximum volume was %0.2fdB \n", m);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int af_open(struct af_instance *af)
|
||||
{
|
||||
struct priv *s = af->priv;
|
||||
af->control = control;
|
||||
af->filter = filter;
|
||||
af_from_dB(1, &s->cfg_volume, &s->level, 20.0, -200.0, 60.0);
|
||||
return AF_OK;
|
||||
}
|
||||
}
|
||||
return AF_UNKNOWN;
|
||||
}
|
||||
|
||||
// Deallocate memory
|
||||
static void uninit(struct af_instance* af)
|
||||
{
|
||||
free(af->data);
|
||||
free(af->setup);
|
||||
}
|
||||
|
||||
// Filter data through filter
|
||||
static struct mp_audio* play(struct af_instance* af, struct mp_audio* data)
|
||||
{
|
||||
struct mp_audio* c = data; // Current working data
|
||||
af_volume_t* s = (af_volume_t*)af->setup; // Setup for this instance
|
||||
register int nch = c->nch; // Number of channels
|
||||
register int i = 0;
|
||||
|
||||
// Basic operation volume control only (used on slow machines)
|
||||
if(af->data->format == (AF_FORMAT_S16_NE)){
|
||||
int16_t* a = (int16_t*)c->audio; // Audio data
|
||||
int len = c->len/2; // Number of samples
|
||||
for (int ch = 0; ch < nch; ch++) {
|
||||
int vol = 256.0 * s->level[ch];
|
||||
if (s->enable[ch] && vol != 256) {
|
||||
for(i=ch;i<len;i+=nch){
|
||||
register int x = (a[i] * vol) >> 8;
|
||||
a[i]=clamp(x,SHRT_MIN,SHRT_MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Machine is fast and data is floating point
|
||||
else if(af->data->format == (AF_FORMAT_FLOAT_NE)){
|
||||
float* a = (float*)c->audio; // Audio data
|
||||
int len = c->len/4; // Number of samples
|
||||
for (int ch = 0; ch < nch; ch++) {
|
||||
// Volume control (fader)
|
||||
if(s->enable[ch]){
|
||||
float t = 1.0 - s->time;
|
||||
for(i=ch;i<len;i+=nch){
|
||||
register float x = a[i];
|
||||
register float pow = x*x;
|
||||
// Check maximum power value
|
||||
if(pow > s->max[ch])
|
||||
s->max[ch] = pow;
|
||||
// Set volume
|
||||
x *= s->level[ch];
|
||||
// Peak meter
|
||||
pow = x*x;
|
||||
if(pow > s->pow[ch])
|
||||
s->pow[ch] = pow;
|
||||
else
|
||||
s->pow[ch] = t*s->pow[ch] + pow*s->time; // LP filter
|
||||
/* Soft clipping, the sound of a dream, thanks to Jon Wattes
|
||||
post to Musicdsp.org */
|
||||
if(s->soft)
|
||||
x=af_softclip(x);
|
||||
// Hard clipping
|
||||
else
|
||||
x=clamp(x,-1.0,1.0);
|
||||
a[i] = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// Allocate memory and set function pointers
|
||||
static int af_open(struct af_instance* af){
|
||||
int i = 0;
|
||||
af->control=control;
|
||||
af->uninit=uninit;
|
||||
af->play=play;
|
||||
af->mul=1;
|
||||
af->data=calloc(1,sizeof(struct mp_audio));
|
||||
af->setup=calloc(1,sizeof(af_volume_t));
|
||||
if(af->data == NULL || af->setup == NULL)
|
||||
return AF_ERROR;
|
||||
// Enable volume control and set initial volume to 0dB.
|
||||
for(i=0;i<AF_NCH;i++){
|
||||
((af_volume_t*)af->setup)->enable[i] = 1;
|
||||
((af_volume_t*)af->setup)->level[i] = 1.0;
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
// Description of this filter
|
||||
struct af_info af_info_volume = {
|
||||
"Volume control audio filter",
|
||||
"volume",
|
||||
"Anders",
|
||||
"",
|
||||
AF_FLAGS_NOT_REENTRANT,
|
||||
af_open
|
||||
.info = "Volume control audio filter",
|
||||
.name = "volume",
|
||||
.flags = AF_FLAGS_NOT_REENTRANT,
|
||||
.open = af_open,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLOATRANGE("volumedb", cfg_volume, 0, -200, 60),
|
||||
OPT_FLAG("softclip", soft, 0),
|
||||
OPT_FLAG("s16", fast, 0),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef MPLAYER_CONTROL_H
|
||||
#define MPLAYER_CONTROL_H
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
/*********************************************
|
||||
// Extended control used with arguments that operates on only one
|
||||
// channel at the time
|
||||
*/
|
||||
typedef struct af_control_ext_s{
|
||||
void* arg; // Argument
|
||||
int ch; // Chanel number
|
||||
}af_control_ext_t;
|
||||
|
||||
/*********************************************
|
||||
// Control parameters
|
||||
*/
|
||||
|
||||
/* The control system is divided into 3 levels
|
||||
mandatory calls - all filters must answer to all of these
|
||||
optional calls - are optional
|
||||
filter specific calls - applies only to some filters
|
||||
*/
|
||||
|
||||
#define AF_CONTROL_MANDATORY 0x10000000
|
||||
#define AF_CONTROL_OPTIONAL 0x20000000
|
||||
#define AF_CONTROL_FILTER_SPECIFIC 0x40000000
|
||||
|
||||
// MANDATORY CALLS
|
||||
|
||||
/* Reinitialize filter. The optional argument contains the new
|
||||
configuration in form of a struct mp_audio struct. If the filter does not
|
||||
support the new format the struct should be changed and AF_FALSE
|
||||
should be returned. If the incoming and outgoing data streams are
|
||||
identical the filter can return AF_DETACH. This will remove the
|
||||
filter. */
|
||||
#define AF_CONTROL_REINIT 0x00000100 | AF_CONTROL_MANDATORY
|
||||
|
||||
// OPTIONAL CALLS
|
||||
|
||||
// Called just before destruction of a filter
|
||||
#define AF_CONTROL_PRE_DESTROY 0x00000200 | AF_CONTROL_OPTIONAL
|
||||
|
||||
/* Commandline parameters. If there were any commandline parameters
|
||||
for this specific filter, they will be given as a char* in the
|
||||
argument */
|
||||
#define AF_CONTROL_COMMAND_LINE 0x00000300 | AF_CONTROL_OPTIONAL
|
||||
|
||||
|
||||
// FILTER SPECIFIC CALLS
|
||||
|
||||
// Basic operations: These can be ored with any of the below calls
|
||||
// Set argument
|
||||
#define AF_CONTROL_SET 0x00000000
|
||||
// Get argument
|
||||
#define AF_CONTROL_GET 0x00000001
|
||||
|
||||
// Resample
|
||||
|
||||
// Set output rate in resample
|
||||
#define AF_CONTROL_RESAMPLE_RATE 0x00000100 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
// Format
|
||||
|
||||
#define AF_CONTROL_FORMAT_FMT 0x00000400 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
// Channels
|
||||
|
||||
// Set number of output channels in channels
|
||||
#define AF_CONTROL_CHANNELS 0x00000600 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
// Volume
|
||||
|
||||
// Set volume level, arg is a float* with the volume for all the channels
|
||||
#define AF_CONTROL_VOLUME_LEVEL 0x00000D00 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
// Pan
|
||||
|
||||
// Pan levels, arg is a control_ext with a float*
|
||||
#define AF_CONTROL_PAN_LEVEL 0x00001A00 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
// Number of outputs from pan, arg is int*
|
||||
#define AF_CONTROL_PAN_NOUT 0x00001B00 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
// Balance, arg is float*; range -1 (left) to 1 (right), 0 center
|
||||
#define AF_CONTROL_PAN_BALANCE 0x00001C00 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
|
||||
// Delay length in ms, arg is a control_ext with a float*
|
||||
#define AF_CONTROL_DELAY_LEN 0x00001E00 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
|
||||
// Subwoofer
|
||||
|
||||
// Channel number which to insert the filtered data, arg in int*
|
||||
#define AF_CONTROL_SUB_CH 0x00001F00 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
// Cutoff frequency [Hz] for lowpass filter, arg is float*
|
||||
#define AF_CONTROL_SUB_FC 0x00002000 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
|
||||
// Export
|
||||
#define AF_CONTROL_EXPORT_SZ 0x00003000 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
// Channel number which to inster the filtered data, arg in int*
|
||||
#define AF_CONTROL_CENTER_CH 0x00003200 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
|
||||
#define AF_CONTROL_PLAYBACK_SPEED 0x00003500 | AF_CONTROL_FILTER_SPECIFIC
|
||||
#define AF_CONTROL_SCALETEMPO_AMOUNT 0x00003600 | AF_CONTROL_FILTER_SPECIFIC
|
||||
|
||||
#endif /* MPLAYER_CONTROL_H */
|
||||
@@ -18,10 +18,12 @@
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "af.h"
|
||||
|
||||
/* Convert to gain value from dB. Returns AF_OK if of and AF_ERROR if
|
||||
fail */
|
||||
* fail. input <= -200dB will become 0 gain. */
|
||||
int af_from_dB(int n, float* in, float* out, float k, float mi, float ma)
|
||||
{
|
||||
int i = 0;
|
||||
@@ -33,13 +35,13 @@ int af_from_dB(int n, float* in, float* out, float k, float mi, float ma)
|
||||
if(in[i]<=-200)
|
||||
out[i]=0.0;
|
||||
else
|
||||
out[i]=pow(10.0,clamp(in[i],mi,ma)/k);
|
||||
out[i]=pow(10.0,MPCLAMP(in[i],mi,ma)/k);
|
||||
}
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* Convert from gain value to dB. Returns AF_OK if of and AF_ERROR if
|
||||
fail */
|
||||
* fail. gain=0 will become -200 dB. k is just a multiplier. */
|
||||
int af_to_dB(int n, float* in, float* out, float k)
|
||||
{
|
||||
int i = 0;
|
||||
@@ -65,7 +67,7 @@ int af_from_ms(int n, float* in, int* out, int rate, float mi, float ma)
|
||||
return AF_ERROR;
|
||||
|
||||
for(i=0;i<n;i++)
|
||||
out[i]=(int)((float)rate * clamp(in[i],mi,ma)/1000.0);
|
||||
out[i]=(int)((float)rate * MPCLAMP(in[i],mi,ma)/1000.0);
|
||||
|
||||
return AF_OK;
|
||||
}
|
||||
@@ -84,7 +86,18 @@ int af_to_ms(int n, int* in, float* out, int rate)
|
||||
return AF_OK;
|
||||
}
|
||||
|
||||
/* Helper function for testing the output format */
|
||||
/*
|
||||
* test if output format matches
|
||||
* af: audio filter
|
||||
* out: needed format, will be overwritten by available
|
||||
* format if they do not match
|
||||
* returns: AF_FALSE if formats do not match, AF_OK if they match
|
||||
*
|
||||
* compares the format, rate and nch values of af->data with out
|
||||
* Note: logically, *out=*af->data always happens, because out contains the
|
||||
* format only, no actual audio data or memory allocations. *out always
|
||||
* contains the parameters from af->data after the function returns.
|
||||
*/
|
||||
int af_test_output(struct af_instance* af, struct mp_audio* out)
|
||||
{
|
||||
if((af->data->format != out->format) ||
|
||||
+17
-23
@@ -16,7 +16,6 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/samplefmt.h>
|
||||
#include "format.h"
|
||||
@@ -27,39 +26,34 @@ static const struct {
|
||||
int fmt;
|
||||
} audio_conversion_map[] = {
|
||||
{AV_SAMPLE_FMT_U8, AF_FORMAT_U8},
|
||||
{AV_SAMPLE_FMT_S16, AF_FORMAT_S16_NE},
|
||||
{AV_SAMPLE_FMT_S32, AF_FORMAT_S32_NE},
|
||||
{AV_SAMPLE_FMT_FLT, AF_FORMAT_FLOAT_NE},
|
||||
{AV_SAMPLE_FMT_DBL, AF_FORMAT_DOUBLE_NE},
|
||||
{AV_SAMPLE_FMT_S16, AF_FORMAT_S16},
|
||||
{AV_SAMPLE_FMT_S32, AF_FORMAT_S32},
|
||||
{AV_SAMPLE_FMT_FLT, AF_FORMAT_FLOAT},
|
||||
{AV_SAMPLE_FMT_DBL, AF_FORMAT_DOUBLE},
|
||||
|
||||
{AV_SAMPLE_FMT_U8P, AF_FORMAT_U8P},
|
||||
{AV_SAMPLE_FMT_S16P, AF_FORMAT_S16P},
|
||||
{AV_SAMPLE_FMT_S32P, AF_FORMAT_S32P},
|
||||
{AV_SAMPLE_FMT_FLTP, AF_FORMAT_FLOATP},
|
||||
{AV_SAMPLE_FMT_DBLP, AF_FORMAT_DOUBLEP},
|
||||
|
||||
{AV_SAMPLE_FMT_NONE, 0},
|
||||
};
|
||||
|
||||
enum AVSampleFormat af_to_avformat(int fmt)
|
||||
{
|
||||
int i;
|
||||
enum AVSampleFormat sample_fmt;
|
||||
for (i = 0; audio_conversion_map[i].fmt; i++)
|
||||
for (int i = 0; audio_conversion_map[i].fmt; i++) {
|
||||
if (audio_conversion_map[i].fmt == fmt)
|
||||
break;
|
||||
sample_fmt = audio_conversion_map[i].sample_fmt;
|
||||
if (sample_fmt == AF_FORMAT_UNKNOWN)
|
||||
mp_msg(MSGT_GLOBAL, MSGL_V, "Unsupported sample format: %s\n",
|
||||
af_fmt2str_short(fmt));
|
||||
return sample_fmt;
|
||||
return audio_conversion_map[i].sample_fmt;
|
||||
}
|
||||
return AV_SAMPLE_FMT_NONE;
|
||||
}
|
||||
|
||||
int af_from_avformat(enum AVSampleFormat sample_fmt)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; audio_conversion_map[i].fmt; i++)
|
||||
for (int i = 0; audio_conversion_map[i].fmt; i++) {
|
||||
if (audio_conversion_map[i].sample_fmt == sample_fmt)
|
||||
break;
|
||||
int fmt = audio_conversion_map[i].fmt;
|
||||
if (!fmt) {
|
||||
const char *fmtname = av_get_sample_fmt_name(sample_fmt);
|
||||
mp_msg(MSGT_GLOBAL, MSGL_ERR, "Unsupported AVSampleFormat %s (%d)\n",
|
||||
fmtname ? fmtname : "INVALID", sample_fmt);
|
||||
return audio_conversion_map[i].fmt;
|
||||
}
|
||||
return fmt;
|
||||
return 0;
|
||||
}
|
||||
+164
-54
@@ -23,7 +23,9 @@
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "common/common.h"
|
||||
#include "audio/filter/af.h"
|
||||
|
||||
int af_fmt2bits(int format)
|
||||
@@ -31,78 +33,132 @@ int af_fmt2bits(int format)
|
||||
if (AF_FORMAT_IS_AC3(format)) return 16;
|
||||
if (format == AF_FORMAT_UNKNOWN)
|
||||
return 0;
|
||||
switch(format & AF_FORMAT_BITS_MASK)
|
||||
{
|
||||
case AF_FORMAT_8BIT: return 8;
|
||||
case AF_FORMAT_16BIT: return 16;
|
||||
case AF_FORMAT_24BIT: return 24;
|
||||
case AF_FORMAT_32BIT: return 32;
|
||||
case AF_FORMAT_64BIT: return 64;
|
||||
switch (format & AF_FORMAT_BITS_MASK) {
|
||||
case AF_FORMAT_8BIT: return 8;
|
||||
case AF_FORMAT_16BIT: return 16;
|
||||
case AF_FORMAT_24BIT: return 24;
|
||||
case AF_FORMAT_32BIT: return 32;
|
||||
case AF_FORMAT_64BIT: return 64;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Convert format to str input str is a buffer for the
|
||||
converted string, size is the size of the buffer */
|
||||
char* af_fmt2str(int format, char* str, int size)
|
||||
static int bits_to_mask(int bits)
|
||||
{
|
||||
const char *name = af_fmt2str_short(format);
|
||||
snprintf(str, size, "%s", name);
|
||||
return str;
|
||||
switch (bits) {
|
||||
case 8: return AF_FORMAT_8BIT;
|
||||
case 16: return AF_FORMAT_16BIT;
|
||||
case 24: return AF_FORMAT_24BIT;
|
||||
case 32: return AF_FORMAT_32BIT;
|
||||
case 64: return AF_FORMAT_64BIT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct af_fmt_entry af_fmtstr_table[] = {
|
||||
{ "mpeg2", AF_FORMAT_MPEG2 },
|
||||
{ "ac3le", AF_FORMAT_AC3_LE },
|
||||
{ "ac3be", AF_FORMAT_AC3_BE },
|
||||
{ "ac3ne", AF_FORMAT_AC3_NE },
|
||||
{ "iec61937le", AF_FORMAT_IEC61937_LE },
|
||||
{ "iec61937be", AF_FORMAT_IEC61937_BE },
|
||||
{ "iec61937ne", AF_FORMAT_IEC61937_NE },
|
||||
int af_fmt_change_bits(int format, int bits)
|
||||
{
|
||||
if (!af_fmt_is_valid(format) || (format & AF_FORMAT_SPECIAL_MASK))
|
||||
return 0;
|
||||
int mask = bits_to_mask(bits);
|
||||
format = (format & ~AF_FORMAT_BITS_MASK) | mask;
|
||||
return af_fmt_is_valid(format) ? format : 0;
|
||||
}
|
||||
|
||||
{ "u8", AF_FORMAT_U8 },
|
||||
{ "s8", AF_FORMAT_S8 },
|
||||
{ "u16le", AF_FORMAT_U16_LE },
|
||||
{ "u16be", AF_FORMAT_U16_BE },
|
||||
{ "u16ne", AF_FORMAT_U16_NE },
|
||||
{ "s16le", AF_FORMAT_S16_LE },
|
||||
{ "s16be", AF_FORMAT_S16_BE },
|
||||
{ "s16ne", AF_FORMAT_S16_NE },
|
||||
{ "u24le", AF_FORMAT_U24_LE },
|
||||
{ "u24be", AF_FORMAT_U24_BE },
|
||||
{ "u24ne", AF_FORMAT_U24_NE },
|
||||
{ "s24le", AF_FORMAT_S24_LE },
|
||||
{ "s24be", AF_FORMAT_S24_BE },
|
||||
{ "s24ne", AF_FORMAT_S24_NE },
|
||||
{ "u32le", AF_FORMAT_U32_LE },
|
||||
{ "u32be", AF_FORMAT_U32_BE },
|
||||
{ "u32ne", AF_FORMAT_U32_NE },
|
||||
{ "s32le", AF_FORMAT_S32_LE },
|
||||
{ "s32be", AF_FORMAT_S32_BE },
|
||||
{ "s32ne", AF_FORMAT_S32_NE },
|
||||
{ "floatle", AF_FORMAT_FLOAT_LE },
|
||||
{ "floatbe", AF_FORMAT_FLOAT_BE },
|
||||
{ "floatne", AF_FORMAT_FLOAT_NE },
|
||||
{ "doublele", AF_FORMAT_DOUBLE_LE },
|
||||
{ "doublebe", AF_FORMAT_DOUBLE_BE },
|
||||
{ "doublene", AF_FORMAT_DOUBLE_NE },
|
||||
static const int planar_formats[][2] = {
|
||||
{AF_FORMAT_U8P, AF_FORMAT_U8},
|
||||
{AF_FORMAT_S16P, AF_FORMAT_S16},
|
||||
{AF_FORMAT_S32P, AF_FORMAT_S32},
|
||||
{AF_FORMAT_FLOATP, AF_FORMAT_FLOAT},
|
||||
{AF_FORMAT_DOUBLEP, AF_FORMAT_DOUBLE},
|
||||
};
|
||||
|
||||
// Return the planar format corresponding to the given format.
|
||||
// If the format is already planar, return it.
|
||||
// Return 0 if there's no equivalent.
|
||||
int af_fmt_to_planar(int format)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(planar_formats); n++) {
|
||||
if (planar_formats[n][1] == format)
|
||||
return planar_formats[n][0];
|
||||
if (planar_formats[n][0] == format)
|
||||
return format;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Return the interleaved format corresponding to the given format.
|
||||
// If the format is already interleaved, return it.
|
||||
// Always succeeds if format is actually planar; otherwise return 0.
|
||||
int af_fmt_from_planar(int format)
|
||||
{
|
||||
for (int n = 0; n < MP_ARRAY_SIZE(planar_formats); n++) {
|
||||
if (planar_formats[n][0] == format)
|
||||
return planar_formats[n][1];
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
// false for interleaved and AF_FORMAT_UNKNOWN
|
||||
bool af_fmt_is_planar(int format)
|
||||
{
|
||||
return !!(format & AF_FORMAT_PLANAR);
|
||||
}
|
||||
|
||||
#define FMT(string, id) \
|
||||
{string, id},
|
||||
|
||||
#define FMT_ENDIAN(string, id) \
|
||||
{string, id}, \
|
||||
{string "le", MP_CONCAT(id, _LE)}, \
|
||||
{string "be", MP_CONCAT(id, _BE)}, \
|
||||
|
||||
const struct af_fmt_entry af_fmtstr_table[] = {
|
||||
FMT("mpeg2", AF_FORMAT_MPEG2)
|
||||
FMT_ENDIAN("ac3", AF_FORMAT_AC3)
|
||||
FMT_ENDIAN("iec61937", AF_FORMAT_IEC61937)
|
||||
|
||||
FMT("u8", AF_FORMAT_U8)
|
||||
FMT("s8", AF_FORMAT_S8)
|
||||
FMT_ENDIAN("u16", AF_FORMAT_U16)
|
||||
FMT_ENDIAN("s16", AF_FORMAT_S16)
|
||||
FMT_ENDIAN("u24", AF_FORMAT_U24)
|
||||
FMT_ENDIAN("s24", AF_FORMAT_S24)
|
||||
FMT_ENDIAN("u32", AF_FORMAT_U32)
|
||||
FMT_ENDIAN("s32", AF_FORMAT_S32)
|
||||
FMT_ENDIAN("float", AF_FORMAT_FLOAT)
|
||||
FMT_ENDIAN("double", AF_FORMAT_DOUBLE)
|
||||
|
||||
FMT("u8p", AF_FORMAT_U8P)
|
||||
FMT("s16p", AF_FORMAT_S16P)
|
||||
FMT("s32p", AF_FORMAT_S32P)
|
||||
FMT("floatp", AF_FORMAT_FLOATP)
|
||||
FMT("doublep", AF_FORMAT_DOUBLEP)
|
||||
|
||||
{0}
|
||||
};
|
||||
|
||||
const char *af_fmt2str_short(int format)
|
||||
bool af_fmt_is_valid(int format)
|
||||
{
|
||||
int i;
|
||||
for (int i = 0; af_fmtstr_table[i].name; i++) {
|
||||
if (af_fmtstr_table[i].format == format)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (i = 0; af_fmtstr_table[i].name; i++)
|
||||
if (af_fmtstr_table[i].format == format)
|
||||
return af_fmtstr_table[i].name;
|
||||
const char *af_fmt_to_str(int format)
|
||||
{
|
||||
for (int i = 0; af_fmtstr_table[i].name; i++) {
|
||||
if (af_fmtstr_table[i].format == format)
|
||||
return af_fmtstr_table[i].name;
|
||||
}
|
||||
|
||||
return "??";
|
||||
}
|
||||
|
||||
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate)
|
||||
{
|
||||
assert(!af_fmt_is_planar(format));
|
||||
int bps = (af_fmt2bits(format) / 8);
|
||||
int framelen = channels * bps;
|
||||
int bytes = seconds * bps * samplerate;
|
||||
@@ -113,9 +169,63 @@ int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int sampler
|
||||
|
||||
int af_str2fmt_short(bstr str)
|
||||
{
|
||||
for (int i = 0; af_fmtstr_table[i].name; i++)
|
||||
for (int i = 0; af_fmtstr_table[i].name; i++) {
|
||||
if (!bstrcasecmp0(str, af_fmtstr_table[i].name))
|
||||
return af_fmtstr_table[i].format;
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format)
|
||||
{
|
||||
bool us = (format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_US;
|
||||
memset(dst, us ? 0x80 : 0, bytes);
|
||||
}
|
||||
|
||||
#define FMT_DIFF(type, a, b) (((a) & type) - ((b) & type))
|
||||
|
||||
// Returns a "score" that serves as heuristic how lossy or hard a conversion is.
|
||||
// If the formats are equal, 1024 is returned. If they are gravely incompatible
|
||||
// (like s16<->ac3), INT_MIN is returned. If there is implied loss of precision
|
||||
// (like s16->s8), a value <0 is returned.
|
||||
int af_format_conversion_score(int dst_format, int src_format)
|
||||
{
|
||||
if (dst_format == AF_FORMAT_UNKNOWN || src_format == AF_FORMAT_UNKNOWN)
|
||||
return INT_MIN;
|
||||
if (dst_format == src_format)
|
||||
return 1024;
|
||||
// Just endian swapping (separate because it works for special formats)
|
||||
if ((dst_format & ~AF_FORMAT_END_MASK) == (src_format & ~AF_FORMAT_END_MASK))
|
||||
return 1024 - 2;
|
||||
// Can't be normally converted
|
||||
if (AF_FORMAT_IS_SPECIAL(dst_format) || AF_FORMAT_IS_SPECIAL(src_format))
|
||||
return INT_MIN;
|
||||
int score = 1024;
|
||||
if (FMT_DIFF(AF_FORMAT_INTERLEAVING_MASK, dst_format, src_format))
|
||||
score -= 1; // has to (de-)planarize
|
||||
if (FMT_DIFF(AF_FORMAT_END_MASK, dst_format, src_format))
|
||||
score -= 2; // has to swap endian
|
||||
if (FMT_DIFF(AF_FORMAT_SIGN_MASK, dst_format, src_format))
|
||||
score -= 4; // has to swap sign
|
||||
if (FMT_DIFF(AF_FORMAT_POINT_MASK, dst_format, src_format)) {
|
||||
int dst_bits = dst_format & AF_FORMAT_BITS_MASK;
|
||||
if ((dst_format & AF_FORMAT_POINT_MASK) == AF_FORMAT_F) {
|
||||
// For int->float, always prefer 32 bit float.
|
||||
score -= dst_bits == AF_FORMAT_32BIT ? 8 : 0;
|
||||
} else {
|
||||
// For float->int, always prefer highest bit depth int
|
||||
score -= 8 * (AF_FORMAT_64BIT - dst_bits);
|
||||
}
|
||||
} else {
|
||||
int bits = FMT_DIFF(AF_FORMAT_BITS_MASK, dst_format, src_format);
|
||||
if (bits > 0) {
|
||||
score -= 8 * bits; // has to add padding
|
||||
} else if (bits < 0) {
|
||||
score -= 1024 - 8 * bits; // has to reduce bit depth
|
||||
}
|
||||
}
|
||||
// Consider this the worst case.
|
||||
if (FMT_DIFF(AF_FORMAT_POINT_MASK, dst_format, src_format))
|
||||
score -= 2048; // has to convert float<->int
|
||||
return score;
|
||||
}
|
||||
+102
-73
@@ -23,104 +23,123 @@
|
||||
#ifndef MPLAYER_AF_FORMAT_H
|
||||
#define MPLAYER_AF_FORMAT_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/types.h>
|
||||
#include "config.h"
|
||||
#include "mpvcore/bstr.h"
|
||||
|
||||
// Endianness
|
||||
#define AF_FORMAT_BE (0<<0) // Big Endian
|
||||
#define AF_FORMAT_LE (1<<0) // Little Endian
|
||||
#define AF_FORMAT_END_MASK (1<<0)
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
#define AF_FORMAT_NE AF_FORMAT_BE
|
||||
#define AF_SELECT_LE_BE(LE, BE) BE
|
||||
#else
|
||||
#define AF_FORMAT_NE AF_FORMAT_LE
|
||||
#define AF_SELECT_LE_BE(LE, BE) LE
|
||||
#endif
|
||||
|
||||
// Endianness
|
||||
#define AF_FORMAT_BE (0<<0) // Big Endian
|
||||
#define AF_FORMAT_LE (1<<0) // Little Endian
|
||||
#define AF_FORMAT_END_MASK (1<<0)
|
||||
|
||||
#define AF_FORMAT_NE AF_SELECT_LE_BE(AF_FORMAT_LE, AF_FORMAT_BE)
|
||||
|
||||
// Signed/unsigned
|
||||
#define AF_FORMAT_SI (0<<1) // Signed
|
||||
#define AF_FORMAT_US (1<<1) // Unsigned
|
||||
#define AF_FORMAT_SIGN_MASK (1<<1)
|
||||
#define AF_FORMAT_SI (0<<1) // Signed
|
||||
#define AF_FORMAT_US (1<<1) // Unsigned
|
||||
#define AF_FORMAT_SIGN_MASK (1<<1)
|
||||
|
||||
// Bits used
|
||||
#define AF_FORMAT_8BIT (0<<3)
|
||||
#define AF_FORMAT_16BIT (1<<3)
|
||||
#define AF_FORMAT_24BIT (2<<3)
|
||||
#define AF_FORMAT_32BIT (3<<3)
|
||||
#define AF_FORMAT_64BIT (4<<3)
|
||||
#define AF_FORMAT_BITS_MASK (7<<3)
|
||||
// Some code assumes they're sorted by size.
|
||||
#define AF_FORMAT_8BIT (0<<3)
|
||||
#define AF_FORMAT_16BIT (1<<3)
|
||||
#define AF_FORMAT_24BIT (2<<3)
|
||||
#define AF_FORMAT_32BIT (3<<3)
|
||||
#define AF_FORMAT_64BIT (4<<3)
|
||||
#define AF_FORMAT_BITS_MASK (7<<3)
|
||||
|
||||
// Special flags refering to non pcm data (note: 1<<6, 2<<6, 5<<6 unused)
|
||||
#define AF_FORMAT_MPEG2 (3<<6) // MPEG(2) audio
|
||||
#define AF_FORMAT_AC3 (4<<6) // Dolby Digital AC3
|
||||
#define AF_FORMAT_IEC61937 (6<<6)
|
||||
#define AF_FORMAT_SPECIAL_MASK (7<<6)
|
||||
#define AF_FORMAT_S_MPEG2 (3<<6) // MPEG(2) audio
|
||||
#define AF_FORMAT_S_AC3 (4<<6) // Dolby Digital AC3
|
||||
#define AF_FORMAT_S_IEC61937 (6<<6)
|
||||
#define AF_FORMAT_SPECIAL_MASK (7<<6)
|
||||
|
||||
// Fixed or floating point
|
||||
#define AF_FORMAT_I (1<<9) // Int
|
||||
#define AF_FORMAT_F (2<<9) // Foating point
|
||||
#define AF_FORMAT_POINT_MASK (3<<9)
|
||||
|
||||
#define AF_FORMAT_MASK ((1<<11)-1)
|
||||
// Interleaving (planar formats have data for each channel in separate planes)
|
||||
#define AF_FORMAT_INTERLEAVED (0<<11) // must be 0
|
||||
#define AF_FORMAT_PLANAR (1<<11)
|
||||
#define AF_FORMAT_INTERLEAVING_MASK (1<<11)
|
||||
|
||||
// PREDEFINED formats
|
||||
#define AF_FORMAT_MASK ((1<<12)-1)
|
||||
|
||||
#define AF_FORMAT_U8 (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_8BIT|AF_FORMAT_NE)
|
||||
#define AF_FORMAT_S8 (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_8BIT|AF_FORMAT_NE)
|
||||
#define AF_FORMAT_U16_LE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_U16_BE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT|AF_FORMAT_BE)
|
||||
#define AF_FORMAT_S16_LE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_S16_BE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_BE)
|
||||
#define AF_FORMAT_U24_LE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_U24_BE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT|AF_FORMAT_BE)
|
||||
#define AF_FORMAT_S24_LE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_S24_BE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT|AF_FORMAT_BE)
|
||||
#define AF_FORMAT_U32_LE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_U32_BE (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT|AF_FORMAT_BE)
|
||||
#define AF_FORMAT_S32_LE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_S32_BE (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_BE)
|
||||
#define AF_INTP (AF_FORMAT_I|AF_FORMAT_PLANAR)
|
||||
#define AF_FLTP (AF_FORMAT_F|AF_FORMAT_PLANAR)
|
||||
|
||||
#define AF_FORMAT_FLOAT_LE (AF_FORMAT_F|AF_FORMAT_32BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_FLOAT_BE (AF_FORMAT_F|AF_FORMAT_32BIT|AF_FORMAT_BE)
|
||||
// actual sample formats
|
||||
enum af_format {
|
||||
AF_FORMAT_UNKNOWN = 0,
|
||||
|
||||
#define AF_FORMAT_DOUBLE_LE (AF_FORMAT_F|AF_FORMAT_64BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_DOUBLE_BE (AF_FORMAT_F|AF_FORMAT_64BIT|AF_FORMAT_BE)
|
||||
AF_FORMAT_U8 = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_8BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_S8 = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_8BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_U16_LE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_U16_BE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_16BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_S16_LE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_S16_BE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_U24_LE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_U24_BE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_24BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_S24_LE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_S24_BE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_24BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_U32_LE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_U32_BE = (AF_FORMAT_I|AF_FORMAT_US|AF_FORMAT_32BIT|AF_FORMAT_BE),
|
||||
AF_FORMAT_S32_LE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_S32_BE = (AF_FORMAT_I|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_BE),
|
||||
|
||||
#define AF_FORMAT_AC3_LE (AF_FORMAT_AC3|AF_FORMAT_16BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_AC3_BE (AF_FORMAT_AC3|AF_FORMAT_16BIT|AF_FORMAT_BE)
|
||||
AF_FORMAT_FLOAT_LE = (AF_FORMAT_F|AF_FORMAT_32BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_FLOAT_BE = (AF_FORMAT_F|AF_FORMAT_32BIT|AF_FORMAT_BE),
|
||||
|
||||
#define AF_FORMAT_IEC61937_LE (AF_FORMAT_IEC61937|AF_FORMAT_16BIT|AF_FORMAT_LE)
|
||||
#define AF_FORMAT_IEC61937_BE (AF_FORMAT_IEC61937|AF_FORMAT_16BIT|AF_FORMAT_BE)
|
||||
AF_FORMAT_DOUBLE_LE = (AF_FORMAT_F|AF_FORMAT_64BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_DOUBLE_BE = (AF_FORMAT_F|AF_FORMAT_64BIT|AF_FORMAT_BE),
|
||||
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
#define AF_FORMAT_U16_NE AF_FORMAT_U16_BE
|
||||
#define AF_FORMAT_S16_NE AF_FORMAT_S16_BE
|
||||
#define AF_FORMAT_U24_NE AF_FORMAT_U24_BE
|
||||
#define AF_FORMAT_S24_NE AF_FORMAT_S24_BE
|
||||
#define AF_FORMAT_U32_NE AF_FORMAT_U32_BE
|
||||
#define AF_FORMAT_S32_NE AF_FORMAT_S32_BE
|
||||
#define AF_FORMAT_FLOAT_NE AF_FORMAT_FLOAT_BE
|
||||
#define AF_FORMAT_DOUBLE_NE AF_FORMAT_DOUBLE_BE
|
||||
#define AF_FORMAT_AC3_NE AF_FORMAT_AC3_BE
|
||||
#define AF_FORMAT_IEC61937_NE AF_FORMAT_IEC61937_BE
|
||||
#else
|
||||
#define AF_FORMAT_U16_NE AF_FORMAT_U16_LE
|
||||
#define AF_FORMAT_S16_NE AF_FORMAT_S16_LE
|
||||
#define AF_FORMAT_U24_NE AF_FORMAT_U24_LE
|
||||
#define AF_FORMAT_S24_NE AF_FORMAT_S24_LE
|
||||
#define AF_FORMAT_U32_NE AF_FORMAT_U32_LE
|
||||
#define AF_FORMAT_S32_NE AF_FORMAT_S32_LE
|
||||
#define AF_FORMAT_FLOAT_NE AF_FORMAT_FLOAT_LE
|
||||
#define AF_FORMAT_DOUBLE_NE AF_FORMAT_DOUBLE_LE
|
||||
#define AF_FORMAT_AC3_NE AF_FORMAT_AC3_LE
|
||||
#define AF_FORMAT_IEC61937_NE AF_FORMAT_IEC61937_LE
|
||||
#endif
|
||||
AF_FORMAT_AC3_LE = (AF_FORMAT_S_AC3|AF_FORMAT_16BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_AC3_BE = (AF_FORMAT_S_AC3|AF_FORMAT_16BIT|AF_FORMAT_BE),
|
||||
|
||||
#define AF_FORMAT_UNKNOWN 0
|
||||
AF_FORMAT_IEC61937_LE = (AF_FORMAT_S_IEC61937|AF_FORMAT_16BIT|AF_FORMAT_LE),
|
||||
AF_FORMAT_IEC61937_BE = (AF_FORMAT_S_IEC61937|AF_FORMAT_16BIT|AF_FORMAT_BE),
|
||||
|
||||
#define AF_FORMAT_IS_AC3(fmt) (((fmt) & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_AC3)
|
||||
#define AF_FORMAT_IS_IEC61937(fmt) (AF_FORMAT_IS_AC3(fmt) || ((fmt) & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_IEC61937)
|
||||
AF_FORMAT_MPEG2 = (AF_FORMAT_S_MPEG2),
|
||||
|
||||
// Planar variants
|
||||
AF_FORMAT_U8P = (AF_INTP|AF_FORMAT_US|AF_FORMAT_8BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_S16P = (AF_INTP|AF_FORMAT_SI|AF_FORMAT_16BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_S32P = (AF_INTP|AF_FORMAT_SI|AF_FORMAT_32BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_FLOATP = (AF_FLTP|AF_FORMAT_32BIT|AF_FORMAT_NE),
|
||||
AF_FORMAT_DOUBLEP = (AF_FLTP|AF_FORMAT_64BIT|AF_FORMAT_NE),
|
||||
|
||||
// Native endian variants
|
||||
AF_FORMAT_U16 = AF_SELECT_LE_BE(AF_FORMAT_U16_LE, AF_FORMAT_U16_BE),
|
||||
AF_FORMAT_S16 = AF_SELECT_LE_BE(AF_FORMAT_S16_LE, AF_FORMAT_S16_BE),
|
||||
AF_FORMAT_U24 = AF_SELECT_LE_BE(AF_FORMAT_U24_LE, AF_FORMAT_U24_BE),
|
||||
AF_FORMAT_S24 = AF_SELECT_LE_BE(AF_FORMAT_S24_LE, AF_FORMAT_S24_BE),
|
||||
AF_FORMAT_U32 = AF_SELECT_LE_BE(AF_FORMAT_U32_LE, AF_FORMAT_U32_BE),
|
||||
AF_FORMAT_S32 = AF_SELECT_LE_BE(AF_FORMAT_S32_LE, AF_FORMAT_S32_BE),
|
||||
|
||||
AF_FORMAT_FLOAT = AF_SELECT_LE_BE(AF_FORMAT_FLOAT_LE, AF_FORMAT_FLOAT_BE),
|
||||
AF_FORMAT_DOUBLE = AF_SELECT_LE_BE(AF_FORMAT_DOUBLE_LE, AF_FORMAT_DOUBLE_BE),
|
||||
|
||||
AF_FORMAT_AC3 = AF_SELECT_LE_BE(AF_FORMAT_AC3_LE, AF_FORMAT_AC3_BE),
|
||||
AF_FORMAT_IEC61937 = AF_SELECT_LE_BE(AF_FORMAT_IEC61937_LE, AF_FORMAT_IEC61937_BE),
|
||||
};
|
||||
|
||||
#define AF_FORMAT_IS_AC3(fmt) \
|
||||
(((fmt) & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_S_AC3)
|
||||
|
||||
#define AF_FORMAT_IS_IEC61937(fmt) \
|
||||
(AF_FORMAT_IS_AC3(fmt) || \
|
||||
((fmt) & AF_FORMAT_SPECIAL_MASK) == AF_FORMAT_S_IEC61937)
|
||||
|
||||
#define AF_FORMAT_IS_SPECIAL(fmt) \
|
||||
((fmt & AF_FORMAT_SPECIAL_MASK) != 0)
|
||||
|
||||
struct af_fmt_entry {
|
||||
const char *name;
|
||||
@@ -130,12 +149,22 @@ struct af_fmt_entry {
|
||||
extern const struct af_fmt_entry af_fmtstr_table[];
|
||||
|
||||
int af_str2fmt_short(bstr str);
|
||||
const char *af_fmt_to_str(int format);
|
||||
|
||||
int af_fmt2bits(int format);
|
||||
int af_fmt_change_bits(int format, int bits);
|
||||
|
||||
int af_fmt_to_planar(int format);
|
||||
int af_fmt_from_planar(int format);
|
||||
bool af_fmt_is_planar(int format);
|
||||
|
||||
// Amount of bytes that contain audio of the given duration, aligned to frames.
|
||||
int af_fmt_seconds_to_bytes(int format, float seconds, int channels, int samplerate);
|
||||
|
||||
char* af_fmt2str(int format, char* str, int size);
|
||||
const char* af_fmt2str_short(int format);
|
||||
bool af_fmt_is_valid(int format);
|
||||
|
||||
void af_fill_silence(void *dst, size_t bytes, int format);
|
||||
|
||||
int af_format_conversion_score(int dst_format, int src_format);
|
||||
|
||||
#endif /* MPLAYER_AF_FORMAT_H */
|
||||
+25
-43
@@ -24,11 +24,13 @@
|
||||
#include "config.h"
|
||||
#include "audio/out/ao.h"
|
||||
#include "audio/filter/af.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/global.h"
|
||||
#include "common/msg.h"
|
||||
#include "talloc.h"
|
||||
#include "mixer.h"
|
||||
|
||||
struct mixer {
|
||||
struct mp_log *log;
|
||||
struct MPOpts *opts;
|
||||
struct ao *ao;
|
||||
struct af_stream *af;
|
||||
@@ -47,11 +49,12 @@ struct mixer {
|
||||
float balance;
|
||||
};
|
||||
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct MPOpts *opts)
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global)
|
||||
{
|
||||
struct mixer *mixer = talloc_ptrtype(talloc_ctx, mixer);
|
||||
*mixer = (struct mixer) {
|
||||
.opts = opts,
|
||||
.log = mp_log_new(mixer, global->log, "mixer"),
|
||||
.opts = global->opts,
|
||||
.vol_l = 100,
|
||||
.vol_r = 100,
|
||||
.driver = "",
|
||||
@@ -71,12 +74,11 @@ static void checkvolume(struct mixer *mixer)
|
||||
|
||||
ao_control_vol_t vol = {mixer->vol_l, mixer->vol_r};
|
||||
if (mixer->softvol) {
|
||||
float vals[AF_NCH];
|
||||
if (!af_control_any_rev(mixer->af,
|
||||
AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_GET, vals))
|
||||
vals[0] = vals[1] = 1.0;
|
||||
vol.left = (vals[0] / (mixer->opts->softvol_max / 100.0)) * 100.0;
|
||||
vol.right = (vals[1] / (mixer->opts->softvol_max / 100.0)) * 100.0;
|
||||
float gain;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_GET_VOLUME, &gain))
|
||||
gain = 1.0;
|
||||
vol.left = (gain / (mixer->opts->softvol_max / 100.0)) * 100.0;
|
||||
vol.right = (gain / (mixer->opts->softvol_max / 100.0)) * 100.0;
|
||||
} else {
|
||||
// Rely on the values not changing if the query is not supported
|
||||
ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol);
|
||||
@@ -114,26 +116,15 @@ static void setvolume_internal(struct mixer *mixer, float l, float r)
|
||||
struct ao_control_vol vol = {.left = l, .right = r};
|
||||
if (!mixer->softvol) {
|
||||
if (ao_control(mixer->ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK)
|
||||
mp_tmsg(MSGT_GLOBAL, MSGL_ERR,
|
||||
"[Mixer] Failed to change audio output volume.\n");
|
||||
MP_ERR(mixer, "Failed to change audio output volume.\n");
|
||||
return;
|
||||
}
|
||||
float vals[AF_NCH];
|
||||
vals[0] = l / 100.0 * mixer->opts->softvol_max / 100.0;
|
||||
vals[1] = r / 100.0 * mixer->opts->softvol_max / 100.0;
|
||||
for (int i = 2; i < AF_NCH; i++)
|
||||
vals[i] = (vals[0] + vals[1]) / 2.0;
|
||||
if (!af_control_any_rev(mixer->af,
|
||||
AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET,
|
||||
vals))
|
||||
{
|
||||
mp_tmsg(MSGT_GLOBAL, MSGL_V, "[Mixer] Inserting volume filter.\n");
|
||||
float gain = (l + r) / 2.0 / 100.0 * mixer->opts->softvol_max / 100.0;
|
||||
if (!af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)) {
|
||||
MP_VERBOSE(mixer, "Inserting volume filter.\n");
|
||||
if (!(af_add(mixer->af, "volume", NULL)
|
||||
&& af_control_any_rev(mixer->af,
|
||||
AF_CONTROL_VOLUME_LEVEL | AF_CONTROL_SET,
|
||||
vals)))
|
||||
mp_tmsg(MSGT_GLOBAL, MSGL_ERR,
|
||||
"[Mixer] No volume control available.\n");
|
||||
&& af_control_any_rev(mixer->af, AF_CONTROL_SET_VOLUME, &gain)))
|
||||
MP_ERR(mixer, "No volume control available.\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +192,7 @@ void mixer_decvolume(struct mixer *mixer)
|
||||
void mixer_getbalance(struct mixer *mixer, float *val)
|
||||
{
|
||||
if (mixer->af)
|
||||
af_control_any_rev(mixer->af,
|
||||
AF_CONTROL_PAN_BALANCE | AF_CONTROL_GET,
|
||||
&mixer->balance);
|
||||
af_control_any_rev(mixer->af, AF_CONTROL_GET_PAN_BALANCE, &mixer->balance);
|
||||
*val = mixer->balance;
|
||||
}
|
||||
|
||||
@@ -213,9 +202,7 @@ void mixer_getbalance(struct mixer *mixer, float *val)
|
||||
* af_pan instance that was automatically inserted for balance control
|
||||
* only and is otherwise an identity transform, but if the filter was
|
||||
* there for another reason, then ignoring and overriding the original
|
||||
* values is completely wrong. In particular, this will break
|
||||
* automatically inserted downmix filters; the original coefficients that
|
||||
* are significantly below 1 will be overwritten with much higher values.
|
||||
* values is completely wrong.
|
||||
*/
|
||||
|
||||
void mixer_setbalance(struct mixer *mixer, float val)
|
||||
@@ -230,16 +217,14 @@ void mixer_setbalance(struct mixer *mixer, float val)
|
||||
if (!mixer->af)
|
||||
return;
|
||||
|
||||
if (af_control_any_rev(mixer->af,
|
||||
AF_CONTROL_PAN_BALANCE | AF_CONTROL_SET, &val))
|
||||
if (af_control_any_rev(mixer->af, AF_CONTROL_SET_PAN_BALANCE, &val))
|
||||
return;
|
||||
|
||||
if (val == 0 || mixer->ao->channels.num < 2)
|
||||
return;
|
||||
|
||||
if (!(af_pan_balance = af_add(mixer->af, "pan", NULL))) {
|
||||
mp_tmsg(MSGT_GLOBAL, MSGL_ERR,
|
||||
"[Mixer] No balance control available.\n");
|
||||
MP_ERR(mixer, "No balance control available.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -248,14 +233,12 @@ void mixer_setbalance(struct mixer *mixer, float val)
|
||||
for (i = 2; i < AF_NCH; i++) {
|
||||
arg_ext.ch = i;
|
||||
level[i] = 1.f;
|
||||
af_pan_balance->control(af_pan_balance,
|
||||
AF_CONTROL_PAN_LEVEL | AF_CONTROL_SET,
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_LEVEL,
|
||||
&arg_ext);
|
||||
level[i] = 0.f;
|
||||
}
|
||||
|
||||
af_pan_balance->control(af_pan_balance,
|
||||
AF_CONTROL_PAN_BALANCE | AF_CONTROL_SET, &val);
|
||||
af_pan_balance->control(af_pan_balance, AF_CONTROL_SET_PAN_BALANCE, &val);
|
||||
}
|
||||
|
||||
char *mixer_get_volume_restore_data(struct mixer *mixer)
|
||||
@@ -281,8 +264,7 @@ static void probe_softvol(struct mixer *mixer)
|
||||
ao_control_vol_t vol;
|
||||
if (ao_control(mixer->ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK) {
|
||||
mixer->softvol = true;
|
||||
mp_tmsg(MSGT_GLOBAL, MSGL_WARN,
|
||||
"[mixer] Hardware volume control unavailable.\n");
|
||||
MP_WARN(mixer, "Hardware volume control unavailable.\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,7 +285,7 @@ static void restore_volume(struct mixer *mixer)
|
||||
int force_mute = -1;
|
||||
|
||||
const char *prev_driver = mixer->driver;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao->driver->info->short_name;
|
||||
mixer->driver = mixer->softvol ? "softvol" : ao->driver->name;
|
||||
|
||||
bool restore = mixer->softvol || ao->no_persistent_volume;
|
||||
|
||||
|
||||
+2
-2
@@ -28,12 +28,12 @@ enum {
|
||||
SOFTVOL_AUTO = 2,
|
||||
};
|
||||
|
||||
struct MPOpts;
|
||||
struct mpv_global;
|
||||
struct ao;
|
||||
struct af_stream;
|
||||
struct mixer;
|
||||
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct MPOpts *opts);
|
||||
struct mixer *mixer_init(void *talloc_ctx, struct mpv_global *global);
|
||||
void mixer_reinit_audio(struct mixer *mixer, struct ao *ao, struct af_stream *af);
|
||||
void mixer_uninit_audio(struct mixer *mixer);
|
||||
bool mixer_audio_initialized(struct mixer *mixer);
|
||||
|
||||
+64
-43
@@ -27,10 +27,10 @@
|
||||
#include "ao.h"
|
||||
#include "audio/format.h"
|
||||
|
||||
#include "mpvcore/options.h"
|
||||
#include "mpvcore/m_config.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "mpvcore/mpv_global.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_config.h"
|
||||
#include "common/msg.h"
|
||||
#include "common/global.h"
|
||||
|
||||
extern const struct ao_driver audio_out_oss;
|
||||
extern const struct ao_driver audio_out_coreaudio;
|
||||
@@ -50,47 +50,47 @@ extern const struct ao_driver audio_out_sdl;
|
||||
|
||||
static const struct ao_driver * const audio_out_drivers[] = {
|
||||
// native:
|
||||
#ifdef CONFIG_COREAUDIO
|
||||
#if HAVE_COREAUDIO
|
||||
&audio_out_coreaudio,
|
||||
#endif
|
||||
#ifdef CONFIG_PULSE
|
||||
#if HAVE_PULSE
|
||||
&audio_out_pulse,
|
||||
#endif
|
||||
#ifdef CONFIG_SNDIO
|
||||
#if HAVE_SNDIO
|
||||
&audio_out_sndio,
|
||||
#endif
|
||||
#ifdef CONFIG_ALSA
|
||||
#if HAVE_ALSA
|
||||
&audio_out_alsa,
|
||||
#endif
|
||||
#ifdef CONFIG_WASAPI
|
||||
#if HAVE_WASAPI
|
||||
&audio_out_wasapi,
|
||||
#endif
|
||||
#ifdef CONFIG_OSS_AUDIO
|
||||
#if HAVE_OSS_AUDIO
|
||||
&audio_out_oss,
|
||||
#endif
|
||||
#ifdef CONFIG_DSOUND
|
||||
#if HAVE_DSOUND
|
||||
&audio_out_dsound,
|
||||
#endif
|
||||
#ifdef CONFIG_PORTAUDIO
|
||||
#if HAVE_PORTAUDIO
|
||||
&audio_out_portaudio,
|
||||
#endif
|
||||
// wrappers:
|
||||
#ifdef CONFIG_JACK
|
||||
#if HAVE_JACK
|
||||
&audio_out_jack,
|
||||
#endif
|
||||
#ifdef CONFIG_OPENAL
|
||||
#if HAVE_OPENAL
|
||||
&audio_out_openal,
|
||||
#endif
|
||||
#ifdef CONFIG_SDL
|
||||
#if HAVE_SDL || HAVE_SDL2
|
||||
&audio_out_sdl,
|
||||
#endif
|
||||
&audio_out_null,
|
||||
// should not be auto-selected:
|
||||
&audio_out_pcm,
|
||||
#ifdef CONFIG_ENCODING
|
||||
#if HAVE_ENCODING
|
||||
&audio_out_lavc,
|
||||
#endif
|
||||
#ifdef CONFIG_RSOUND
|
||||
#if HAVE_RSOUND
|
||||
&audio_out_rsound,
|
||||
#endif
|
||||
NULL
|
||||
@@ -102,8 +102,8 @@ static bool get_desc(struct m_obj_desc *dst, int index)
|
||||
return false;
|
||||
const struct ao_driver *ao = audio_out_drivers[index];
|
||||
*dst = (struct m_obj_desc) {
|
||||
.name = ao->info->short_name,
|
||||
.description = ao->info->name,
|
||||
.name = ao->name,
|
||||
.description = ao->description,
|
||||
.priv_size = ao->priv_size,
|
||||
.priv_defaults = ao->priv_defaults,
|
||||
.options = ao->options,
|
||||
@@ -130,7 +130,7 @@ static struct ao *ao_create(bool probing, struct mpv_global *global,
|
||||
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
|
||||
struct m_obj_desc desc;
|
||||
if (!m_obj_list_find(&desc, &ao_obj_list, bstr0(name))) {
|
||||
mp_tmsg_log(log, MSGL_ERR, "Audio output %s not found!\n", name);
|
||||
mp_msg(log, MSGL_ERR, "Audio output %s not found!\n", name);
|
||||
talloc_free(log);
|
||||
return NULL;
|
||||
};
|
||||
@@ -149,13 +149,22 @@ static struct ao *ao_create(bool probing, struct mpv_global *global,
|
||||
};
|
||||
if (ao->driver->encode != !!ao->encode_lavc_ctx)
|
||||
goto error;
|
||||
struct m_config *config = m_config_from_obj_desc(ao, &desc);
|
||||
struct m_config *config = m_config_from_obj_desc(ao, ao->log, &desc);
|
||||
if (m_config_apply_defaults(config, name, global->opts->ao_defs) < 0)
|
||||
goto error;
|
||||
if (m_config_set_obj_params(config, args) < 0)
|
||||
goto error;
|
||||
ao->priv = config->optstruct;
|
||||
char *chmap = mp_chmap_to_str(&ao->channels);
|
||||
MP_VERBOSE(ao, "requested format: %d Hz, %s channels, %s\n",
|
||||
ao->samplerate, chmap, af_fmt_to_str(ao->format));
|
||||
talloc_free(chmap);
|
||||
if (ao->driver->init(ao) < 0)
|
||||
goto error;
|
||||
ao->bps = ao->channels.num * ao->samplerate * af_fmt2bits(ao->format) / 8;
|
||||
ao->sstride = af_fmt2bits(ao->format) / 8;
|
||||
if (!af_fmt_is_planar(ao->format))
|
||||
ao->sstride *= ao->channels.num;
|
||||
ao->bps = ao->samplerate * ao->sstride;
|
||||
return ao;
|
||||
error:
|
||||
talloc_free(ao);
|
||||
@@ -167,48 +176,48 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels)
|
||||
{
|
||||
struct mp_log *log = mp_log_new(NULL, global->log, "ao");
|
||||
struct ao *ao = NULL;
|
||||
struct m_obj_settings *ao_list = global->opts->audio_driver_list;
|
||||
if (ao_list && ao_list[0].name) {
|
||||
for (int n = 0; ao_list[n].name; n++) {
|
||||
if (strlen(ao_list[n].name) == 0)
|
||||
goto autoprobe;
|
||||
mp_tmsg(MSGT_AO, MSGL_V, "Trying preferred audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
struct ao *ao = ao_create(false, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels,
|
||||
ao_list[n].name, ao_list[n].attribs);
|
||||
mp_verbose(log, "Trying preferred audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
ao = ao_create(false, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels,
|
||||
ao_list[n].name, ao_list[n].attribs);
|
||||
if (ao)
|
||||
return ao;
|
||||
mp_tmsg(MSGT_AO, MSGL_WARN, "Failed to initialize audio driver '%s'\n",
|
||||
goto done;
|
||||
mp_warn(log, "Failed to initialize audio driver '%s'\n",
|
||||
ao_list[n].name);
|
||||
}
|
||||
return NULL;
|
||||
goto done;
|
||||
}
|
||||
autoprobe:
|
||||
// now try the rest...
|
||||
for (int i = 0; audio_out_drivers[i]; i++) {
|
||||
struct ao *ao = ao_create(true, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels,
|
||||
(char *)audio_out_drivers[i]->info->short_name, NULL);
|
||||
ao = ao_create(true, global, input_ctx, encode_lavc_ctx,
|
||||
samplerate, format, channels,
|
||||
(char *)audio_out_drivers[i]->name, NULL);
|
||||
if (ao)
|
||||
return ao;
|
||||
goto done;
|
||||
}
|
||||
return NULL;
|
||||
done:
|
||||
talloc_free(log);
|
||||
return ao;
|
||||
}
|
||||
|
||||
void ao_uninit(struct ao *ao, bool cut_audio)
|
||||
{
|
||||
assert(ao->buffer.len >= ao->buffer_playable_size);
|
||||
ao->buffer.len = ao->buffer_playable_size;
|
||||
ao->driver->uninit(ao, cut_audio);
|
||||
if (!cut_audio && ao->buffer.len)
|
||||
mp_msg(MSGT_AO, MSGL_WARN, "Audio output truncated at end.\n");
|
||||
talloc_free(ao);
|
||||
}
|
||||
|
||||
int ao_play(struct ao *ao, void *data, int len, int flags)
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
return ao->driver->play(ao, data, len, flags);
|
||||
return ao->driver->play(ao, data, samples, flags);
|
||||
}
|
||||
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
@@ -234,8 +243,6 @@ int ao_get_space(struct ao *ao)
|
||||
|
||||
void ao_reset(struct ao *ao)
|
||||
{
|
||||
ao->buffer.len = 0;
|
||||
ao->buffer_playable_size = 0;
|
||||
if (ao->driver->reset)
|
||||
ao->driver->reset(ao);
|
||||
}
|
||||
@@ -252,6 +259,20 @@ void ao_resume(struct ao *ao)
|
||||
ao->driver->resume(ao);
|
||||
}
|
||||
|
||||
int ao_play_silence(struct ao *ao, int samples)
|
||||
{
|
||||
if (samples <= 0 || AF_FORMAT_IS_SPECIAL(ao->format))
|
||||
return 0;
|
||||
char *p = talloc_size(NULL, samples * ao->sstride);
|
||||
af_fill_silence(p, samples * ao->sstride, ao->format);
|
||||
void *tmp[MP_NUM_CHANNELS];
|
||||
for (int n = 0; n < MP_NUM_CHANNELS; n++)
|
||||
tmp[n] = p;
|
||||
int r = ao_play(ao, tmp, samples, 0);
|
||||
talloc_free(p);
|
||||
return r;
|
||||
}
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map)
|
||||
{
|
||||
|
||||
+15
-18
@@ -21,8 +21,8 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "mpvcore/bstr.h"
|
||||
#include "mpvcore/mp_common.h"
|
||||
#include "bstr/bstr.h"
|
||||
#include "common/common.h"
|
||||
#include "audio/chmap.h"
|
||||
#include "audio/chmap_sel.h"
|
||||
|
||||
@@ -34,6 +34,8 @@ enum aocontrol {
|
||||
// _MUTE commands take a pointer to bool
|
||||
AOCONTROL_GET_MUTE,
|
||||
AOCONTROL_SET_MUTE,
|
||||
// Has char* as argument, which contains the desired stream title.
|
||||
AOCONTROL_UPDATE_STREAM_TITLE,
|
||||
};
|
||||
|
||||
#define AOPLAY_FINAL_CHUNK 1
|
||||
@@ -43,28 +45,18 @@ typedef struct ao_control_vol {
|
||||
float right;
|
||||
} ao_control_vol_t;
|
||||
|
||||
typedef struct ao_info {
|
||||
/* driver name ("Matrox Millennium G200/G400" */
|
||||
const char *name;
|
||||
/* short name (for config strings) ("mga") */
|
||||
const char *short_name;
|
||||
/* author ("Aaron Holtzman <aholtzma@ess.engr.uvic.ca>") */
|
||||
const char *author;
|
||||
/* any additional comments */
|
||||
const char *comment;
|
||||
} ao_info_t;
|
||||
|
||||
struct ao;
|
||||
|
||||
struct ao_driver {
|
||||
bool encode;
|
||||
const struct ao_info *info;
|
||||
const char *name;
|
||||
const char *description;
|
||||
int (*control)(struct ao *ao, enum aocontrol cmd, void *arg);
|
||||
int (*init)(struct ao *ao);
|
||||
void (*uninit)(struct ao *ao, bool cut_audio);
|
||||
void (*reset)(struct ao*ao);
|
||||
int (*get_space)(struct ao *ao);
|
||||
int (*play)(struct ao *ao, void *data, int len, int flags);
|
||||
int (*play)(struct ao *ao, void **data, int samples, int flags);
|
||||
float (*get_delay)(struct ao *ao);
|
||||
void (*pause)(struct ao *ao);
|
||||
void (*resume)(struct ao *ao);
|
||||
@@ -81,9 +73,12 @@ struct ao {
|
||||
struct mp_chmap channels;
|
||||
int format;
|
||||
int bps; // bytes per second
|
||||
int sstride; // size of a sample on each plane
|
||||
// (format_size*num_channels/num_planes)
|
||||
double pts; // some mplayer.c state (why is this here?)
|
||||
struct bstr buffer;
|
||||
int buffer_playable_size;
|
||||
struct mp_audio_buffer *buffer; // queued audio; passed to play() later
|
||||
int buffer_playable_samples;// part of the part of the buffer the AO hasn't
|
||||
// accepted yet with play()
|
||||
bool probing; // if true, don't fail loudly on init
|
||||
bool untimed;
|
||||
bool no_persistent_volume; // the AO does the equivalent of af_volume
|
||||
@@ -102,7 +97,7 @@ struct ao *ao_init_best(struct mpv_global *global,
|
||||
struct encode_lavc_context *encode_lavc_ctx,
|
||||
int samplerate, int format, struct mp_chmap channels);
|
||||
void ao_uninit(struct ao *ao, bool cut_audio);
|
||||
int ao_play(struct ao *ao, void *data, int len, int flags);
|
||||
int ao_play(struct ao *ao, void **data, int samples, int flags);
|
||||
int ao_control(struct ao *ao, enum aocontrol cmd, void *arg);
|
||||
double ao_get_delay(struct ao *ao);
|
||||
int ao_get_space(struct ao *ao);
|
||||
@@ -110,6 +105,8 @@ void ao_reset(struct ao *ao);
|
||||
void ao_pause(struct ao *ao);
|
||||
void ao_resume(struct ao *ao);
|
||||
|
||||
int ao_play_silence(struct ao *ao, int samples);
|
||||
|
||||
bool ao_chmap_sel_adjust(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
struct mp_chmap *map);
|
||||
bool ao_chmap_sel_get_def(struct ao *ao, const struct mp_chmap_sel *s,
|
||||
|
||||
+109
-142
@@ -2,6 +2,7 @@
|
||||
* ALSA 0.9.x-1.x audio output driver
|
||||
*
|
||||
* Copyright (C) 2004 Alex Beregszaszi
|
||||
* Zsolt Barat <joy@streamminister.de>
|
||||
*
|
||||
* modified for real ALSA 0.9.0 support by Zsolt Barat <joy@streamminister.de>
|
||||
* additional AC-3 passthrough support by Andy Lo A Foe <andy@alsaplayer.org>
|
||||
@@ -33,12 +34,11 @@
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <alloca.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "mpvcore/options.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "options/options.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#define ALSA_PCM_NEW_HW_PARAMS_API
|
||||
#define ALSA_PCM_NEW_SW_PARAMS_API
|
||||
@@ -52,18 +52,18 @@
|
||||
struct priv {
|
||||
snd_pcm_t *alsa;
|
||||
snd_pcm_format_t alsa_fmt;
|
||||
size_t bytes_per_sample;
|
||||
int can_pause;
|
||||
snd_pcm_sframes_t prepause_frames;
|
||||
float delay_before_pause;
|
||||
int buffersize;
|
||||
int outburst;
|
||||
int buffersize; // in frames
|
||||
int outburst; // in frames
|
||||
|
||||
int cfg_block;
|
||||
char *cfg_device;
|
||||
char *cfg_mixer_device;
|
||||
char *cfg_mixer_name;
|
||||
int cfg_mixer_index;
|
||||
int cfg_resample;
|
||||
};
|
||||
|
||||
#define BUFFER_TIME 500000 // 0.5 s
|
||||
@@ -78,28 +78,8 @@ struct priv {
|
||||
} while (0)
|
||||
|
||||
static float get_delay(struct ao *ao);
|
||||
static int play(struct ao *ao, void *data, int len, int flags);
|
||||
static void uninit(struct ao *ao, bool immed);
|
||||
|
||||
static void alsa_error_handler(const char *file, int line, const char *function,
|
||||
int err, const char *format, ...)
|
||||
{
|
||||
char tmp[0xc00];
|
||||
va_list va;
|
||||
|
||||
va_start(va, format);
|
||||
vsnprintf(tmp, sizeof tmp, format, va);
|
||||
va_end(va);
|
||||
|
||||
if (err) {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "alsa-lib: %s:%i:(%s) %s: %s\n",
|
||||
file, line, function, tmp, snd_strerror(err));
|
||||
} else {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "alsa-lib: %s:%i:(%s) %s\n",
|
||||
file, line, function, tmp);
|
||||
}
|
||||
}
|
||||
|
||||
/* to set/get/query special features/parameters */
|
||||
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
{
|
||||
@@ -252,6 +232,7 @@ static const int mp_to_alsa_format[][2] = {
|
||||
|
||||
static int find_alsa_format(int af_format)
|
||||
{
|
||||
af_format = af_fmt_from_planar(af_format);
|
||||
for (int n = 0; mp_to_alsa_format[n][0] != AF_FORMAT_UNKNOWN; n++) {
|
||||
if (mp_to_alsa_format[n][0] == af_format)
|
||||
return mp_to_alsa_format[n][1];
|
||||
@@ -304,51 +285,59 @@ static const char *select_chmap(struct ao *ao)
|
||||
return "default";
|
||||
}
|
||||
|
||||
static int try_open_device(struct ao *ao, const char *device, int open_mode,
|
||||
int try_ac3)
|
||||
static int map_iec958_srate(int srate)
|
||||
{
|
||||
switch (srate) {
|
||||
case 44100: return IEC958_AES3_CON_FS_44100;
|
||||
case 48000: return IEC958_AES3_CON_FS_48000;
|
||||
case 32000: return IEC958_AES3_CON_FS_32000;
|
||||
case 22050: return IEC958_AES3_CON_FS_22050;
|
||||
case 24000: return IEC958_AES3_CON_FS_24000;
|
||||
case 88200: return IEC958_AES3_CON_FS_88200;
|
||||
case 768000: return IEC958_AES3_CON_FS_768000;
|
||||
case 96000: return IEC958_AES3_CON_FS_96000;
|
||||
case 176400: return IEC958_AES3_CON_FS_176400;
|
||||
case 192000: return IEC958_AES3_CON_FS_192000;
|
||||
default: return IEC958_AES3_CON_FS_NOTID;
|
||||
}
|
||||
}
|
||||
|
||||
static int try_open_device(struct ao *ao, const char *device, int open_mode)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int err, len;
|
||||
char *ac3_device, *args;
|
||||
|
||||
if (try_ac3) {
|
||||
if (AF_FORMAT_IS_IEC61937(ao->format)) {
|
||||
void *tmp = talloc_new(NULL);
|
||||
/* to set the non-audio bit, use AES0=6 */
|
||||
len = strlen(device);
|
||||
ac3_device = malloc(len + 7 + 1);
|
||||
if (!ac3_device)
|
||||
return -ENOMEM;
|
||||
strcpy(ac3_device, device);
|
||||
args = strchr(ac3_device, ':');
|
||||
if (!args) {
|
||||
char *params = talloc_asprintf(tmp,
|
||||
"AES0=%d,AES1=%d,AES2=0,AES3=%d",
|
||||
IEC958_AES0_NONAUDIO | IEC958_AES0_PRO_EMPHASIS_NONE,
|
||||
IEC958_AES1_CON_ORIGINAL | IEC958_AES1_CON_PCM_CODER,
|
||||
map_iec958_srate(ao->samplerate));
|
||||
const char *ac3_device = device;
|
||||
int len = strlen(device);
|
||||
char *end = strchr(device, ':');
|
||||
if (!end) {
|
||||
/* no existing parameters: add it behind device name */
|
||||
strcat(ac3_device, ":AES0=6");
|
||||
ac3_device = talloc_asprintf(tmp, "%s:%s", device, params);
|
||||
} else if (end[1] == '\0') {
|
||||
/* ":" but no parameters */
|
||||
ac3_device = talloc_asprintf(tmp, "%s%s", device, params);
|
||||
} else if (end[1] == '{' && device[len - 1] == '}') {
|
||||
/* parameters in config syntax: add it inside the { } block */
|
||||
ac3_device = talloc_asprintf(tmp, "%.*s %s}", len - 1, device, params);
|
||||
} else {
|
||||
do {
|
||||
++args;
|
||||
} while (isspace(*args));
|
||||
if (*args == '\0') {
|
||||
/* ":" but no parameters */
|
||||
strcat(ac3_device, "AES0=6");
|
||||
} else if (*args != '{') {
|
||||
/* a simple list of parameters: add it at the end of the list */
|
||||
strcat(ac3_device, ",AES0=6");
|
||||
} else {
|
||||
/* parameters in config syntax: add it inside the { } block */
|
||||
do {
|
||||
--len;
|
||||
} while (len > 0 && isspace(ac3_device[len]));
|
||||
if (ac3_device[len] == '}')
|
||||
strcpy(ac3_device + len, " AES0=6}");
|
||||
}
|
||||
/* a simple list of parameters: add it at the end of the list */
|
||||
ac3_device = talloc_asprintf(tmp, "%s,%s", device, params);
|
||||
}
|
||||
err = snd_pcm_open
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, open_mode);
|
||||
free(ac3_device);
|
||||
int err = snd_pcm_open
|
||||
(&p->alsa, ac3_device, SND_PCM_STREAM_PLAYBACK, open_mode);
|
||||
talloc_free(tmp);
|
||||
if (!err)
|
||||
return 0;
|
||||
}
|
||||
return snd_pcm_open
|
||||
(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, open_mode);
|
||||
|
||||
return snd_pcm_open(&p->alsa, device, SND_PCM_STREAM_PLAYBACK, open_mode);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -364,9 +353,6 @@ static int init(struct ao *ao)
|
||||
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
MP_VERBOSE(ao, "requested format: %d Hz, %d channels, %x\n",
|
||||
ao->samplerate, ao->channels.num, ao->format);
|
||||
|
||||
p->prepause_frames = 0;
|
||||
p->delay_before_pause = 0;
|
||||
|
||||
@@ -383,8 +369,7 @@ static int init(struct ao *ao)
|
||||
ao->channels.num);
|
||||
} else {
|
||||
device = select_chmap(ao);
|
||||
if (strcmp(device, "default") != 0 && ao->format == AF_FORMAT_FLOAT_NE)
|
||||
{
|
||||
if (strcmp(device, "default") != 0 && (ao->format & AF_FORMAT_F)) {
|
||||
// hack - use the converter plugin (why the heck?)
|
||||
device = talloc_asprintf(ao, "plug:%s", device);
|
||||
}
|
||||
@@ -397,17 +382,15 @@ static int init(struct ao *ao)
|
||||
p->can_pause = 1;
|
||||
|
||||
MP_VERBOSE(ao, "using ALSA version: %s\n", snd_asoundlib_version());
|
||||
snd_lib_error_set_handler(alsa_error_handler);
|
||||
|
||||
int open_mode = p->cfg_block ? 0 : SND_PCM_NONBLOCK;
|
||||
int isac3 = AF_FORMAT_IS_IEC61937(ao->format);
|
||||
//modes = 0, SND_PCM_NONBLOCK, SND_PCM_ASYNC
|
||||
err = try_open_device(ao, device, open_mode, isac3);
|
||||
err = try_open_device(ao, device, open_mode);
|
||||
if (err < 0) {
|
||||
if (err != -EBUSY && !p->cfg_block) {
|
||||
MP_WARN(ao, "Open in nonblock-mode "
|
||||
"failed, trying to open in block-mode.\n");
|
||||
err = try_open_device(ao, device, 0, isac3);
|
||||
err = try_open_device(ao, device, 0);
|
||||
}
|
||||
CHECK_ALSA_ERROR("Playback open error");
|
||||
}
|
||||
@@ -429,20 +412,16 @@ static int init(struct ao *ao)
|
||||
err = snd_pcm_hw_params_any(p->alsa, alsa_hwparams);
|
||||
CHECK_ALSA_ERROR("Unable to get initial parameters");
|
||||
|
||||
err = snd_pcm_hw_params_set_access
|
||||
(p->alsa, alsa_hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
|
||||
p->alsa_fmt = find_alsa_format(ao->format);
|
||||
if (p->alsa_fmt == SND_PCM_FORMAT_UNKNOWN) {
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16;
|
||||
ao->format = AF_FORMAT_S16_NE;
|
||||
ao->format = AF_FORMAT_S16;
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_test_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
if (err < 0) {
|
||||
MP_INFO(ao, "Format %s is not supported by hardware, trying default.\n",
|
||||
af_fmt2str_short(ao->format));
|
||||
af_fmt_to_str(ao->format));
|
||||
p->alsa_fmt = SND_PCM_FORMAT_S16_LE;
|
||||
if (AF_FORMAT_IS_AC3(ao->format))
|
||||
ao->format = AF_FORMAT_AC3_LE;
|
||||
@@ -455,6 +434,17 @@ static int init(struct ao *ao)
|
||||
err = snd_pcm_hw_params_set_format(p->alsa, alsa_hwparams, p->alsa_fmt);
|
||||
CHECK_ALSA_ERROR("Unable to set format");
|
||||
|
||||
snd_pcm_access_t access = af_fmt_is_planar(ao->format)
|
||||
? SND_PCM_ACCESS_RW_NONINTERLEAVED
|
||||
: SND_PCM_ACCESS_RW_INTERLEAVED;
|
||||
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
|
||||
if (err < 0 && af_fmt_is_planar(ao->format)) {
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
access = SND_PCM_ACCESS_RW_INTERLEAVED;
|
||||
err = snd_pcm_hw_params_set_access(p->alsa, alsa_hwparams, access);
|
||||
}
|
||||
CHECK_ALSA_ERROR("Unable to set access type");
|
||||
|
||||
int num_channels = ao->channels.num;
|
||||
err = snd_pcm_hw_params_set_channels_near
|
||||
(p->alsa, alsa_hwparams, &num_channels);
|
||||
@@ -465,19 +455,17 @@ static int init(struct ao *ao)
|
||||
mp_chmap_from_channels_alsa(&ao->channels, num_channels);
|
||||
}
|
||||
|
||||
/* workaround for buggy rate plugin (should be fixed in ALSA 1.0.11)
|
||||
prefer our own resampler, since that allows users to choose the resampler,
|
||||
even per file if desired */
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
// Some ALSA drivers have broken delay reporting, so disable the ALSA
|
||||
// resampling plugin by default.
|
||||
if (!p->cfg_resample) {
|
||||
err = snd_pcm_hw_params_set_rate_resample(p->alsa, alsa_hwparams, 0);
|
||||
CHECK_ALSA_ERROR("Unable to disable resampling");
|
||||
}
|
||||
|
||||
err = snd_pcm_hw_params_set_rate_near
|
||||
(p->alsa, alsa_hwparams, &ao->samplerate, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to set samplerate-2");
|
||||
|
||||
p->bytes_per_sample = af_fmt2bits(ao->format) / 8;
|
||||
p->bytes_per_sample *= ao->channels.num;
|
||||
|
||||
err = snd_pcm_hw_params_set_buffer_time_near
|
||||
(p->alsa, alsa_hwparams, &(unsigned int){BUFFER_TIME}, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to set buffer time near");
|
||||
@@ -496,14 +484,14 @@ static int init(struct ao *ao)
|
||||
err = snd_pcm_hw_params_get_buffer_size(alsa_hwparams, &bufsize);
|
||||
CHECK_ALSA_ERROR("Unable to get buffersize");
|
||||
|
||||
p->buffersize = bufsize * p->bytes_per_sample;
|
||||
MP_VERBOSE(ao, "got buffersize=%i\n", p->buffersize);
|
||||
p->buffersize = bufsize;
|
||||
MP_VERBOSE(ao, "got buffersize=%i samples\n", p->buffersize);
|
||||
|
||||
err = snd_pcm_hw_params_get_period_size(alsa_hwparams, &chunk_size, NULL);
|
||||
CHECK_ALSA_ERROR("Unable to get period size");
|
||||
|
||||
MP_VERBOSE(ao, "got period size %li\n", chunk_size);
|
||||
p->outburst = chunk_size * p->bytes_per_sample;
|
||||
p->outburst = chunk_size;
|
||||
|
||||
/* setting software parameters */
|
||||
err = snd_pcm_sw_params_current(p->alsa, alsa_swparams);
|
||||
@@ -534,8 +522,8 @@ static int init(struct ao *ao)
|
||||
|
||||
p->can_pause = snd_pcm_hw_params_can_pause(alsa_hwparams);
|
||||
|
||||
MP_VERBOSE(ao, "opened: %d Hz/%d channels/%d bpf/%d bytes buffer/%s\n",
|
||||
ao->samplerate, ao->channels.num, (int)p->bytes_per_sample,
|
||||
MP_VERBOSE(ao, "opened: %d Hz/%d channels/%d bps/%d samples buffer/%s\n",
|
||||
ao->samplerate, ao->channels.num, af_fmt2bits(ao->format),
|
||||
p->buffersize, snd_pcm_format_description(p->alsa_fmt));
|
||||
|
||||
return 0;
|
||||
@@ -565,7 +553,6 @@ static void uninit(struct ao *ao, bool immed)
|
||||
|
||||
alsa_error:
|
||||
p->alsa = NULL;
|
||||
snd_lib_error_set_handler(NULL);
|
||||
}
|
||||
|
||||
static void audio_pause(struct ao *ao)
|
||||
@@ -598,9 +585,11 @@ static void audio_resume(struct ao *ao)
|
||||
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_SUSPENDED) {
|
||||
MP_INFO(ao, "PCM in suspend mode, trying to resume.\n");
|
||||
|
||||
while ((err = snd_pcm_resume(p->alsa)) == -EAGAIN)
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
if (p->can_pause) {
|
||||
err = snd_pcm_pause(p->alsa, 0);
|
||||
CHECK_ALSA_ERROR("pcm resume error");
|
||||
@@ -608,11 +597,8 @@ static void audio_resume(struct ao *ao)
|
||||
MP_VERBOSE(ao, "resume not supported by hardware\n");
|
||||
err = snd_pcm_prepare(p->alsa);
|
||||
CHECK_ALSA_ERROR("pcm prepare error");
|
||||
if (p->prepause_frames) {
|
||||
void *silence = calloc(p->prepause_frames, p->bytes_per_sample);
|
||||
play(ao, silence, p->prepause_frames * p->bytes_per_sample, 0);
|
||||
free(silence);
|
||||
}
|
||||
if (p->prepause_frames)
|
||||
ao_play_silence(ao, p->prepause_frames);
|
||||
}
|
||||
|
||||
alsa_error: ;
|
||||
@@ -634,42 +620,29 @@ static void reset(struct ao *ao)
|
||||
alsa_error: ;
|
||||
}
|
||||
|
||||
/*
|
||||
plays 'len' bytes of 'data'
|
||||
returns: number of bytes played
|
||||
modified last at 29.06.02 by jp
|
||||
thanxs for marius <marius@rospot.com> for giving us the light ;)
|
||||
*/
|
||||
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int num_frames;
|
||||
snd_pcm_sframes_t res = 0;
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
len = len / p->outburst * p->outburst;
|
||||
num_frames = len / p->bytes_per_sample;
|
||||
samples = samples / p->outburst * p->outburst;
|
||||
|
||||
if (!p->alsa) {
|
||||
MP_ERR(ao, "Device configuration error.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (num_frames == 0)
|
||||
if (samples == 0)
|
||||
return 0;
|
||||
|
||||
do {
|
||||
res = snd_pcm_writei(p->alsa, data, num_frames);
|
||||
if (af_fmt_is_planar(ao->format)) {
|
||||
res = snd_pcm_writen(p->alsa, data, samples);
|
||||
} else {
|
||||
res = snd_pcm_writei(p->alsa, data[0], samples);
|
||||
}
|
||||
|
||||
if (res == -EINTR) {
|
||||
/* nothing to do */
|
||||
res = 0;
|
||||
} else if (res == -ESTRPIPE) { /* suspend */
|
||||
MP_INFO(ao, "PCM in suspend mode, trying to resume.\n");
|
||||
while ((res = snd_pcm_resume(p->alsa)) == -EAGAIN)
|
||||
sleep(1);
|
||||
}
|
||||
if (res < 0) {
|
||||
audio_resume(ao);
|
||||
} else if (res < 0) {
|
||||
MP_ERR(ao, "Write error: %s\n", snd_strerror(res));
|
||||
res = snd_pcm_prepare(p->alsa);
|
||||
int err = res;
|
||||
@@ -678,10 +651,10 @@ static int play(struct ao *ao, void *data, int len, int flags)
|
||||
}
|
||||
} while (res == 0);
|
||||
|
||||
return res < 0 ? 0 : res * p->bytes_per_sample;
|
||||
return res < 0 ? -1 : res;
|
||||
|
||||
alsa_error:
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* how many byes are free in the buffer */
|
||||
@@ -696,7 +669,7 @@ static int get_space(struct ao *ao)
|
||||
err = snd_pcm_status(p->alsa, status);
|
||||
CHECK_ALSA_ERROR("cannot get pcm status");
|
||||
|
||||
unsigned space = snd_pcm_status_get_avail(status) * p->bytes_per_sample;
|
||||
unsigned space = snd_pcm_status_get_avail(status);
|
||||
if (space > p->buffersize) // Buffer underrun?
|
||||
space = p->buffersize;
|
||||
return space;
|
||||
@@ -709,34 +682,27 @@ alsa_error:
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (p->alsa) {
|
||||
snd_pcm_sframes_t delay;
|
||||
snd_pcm_sframes_t delay;
|
||||
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
|
||||
return p->delay_before_pause;
|
||||
if (snd_pcm_state(p->alsa) == SND_PCM_STATE_PAUSED)
|
||||
return p->delay_before_pause;
|
||||
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
return 0;
|
||||
|
||||
if (delay < 0) {
|
||||
/* underrun - move the application pointer forward to catch up */
|
||||
snd_pcm_forward(p->alsa, -delay);
|
||||
delay = 0;
|
||||
}
|
||||
return (float)delay / (float)ao->samplerate;
|
||||
} else
|
||||
if (snd_pcm_delay(p->alsa, &delay) < 0)
|
||||
return 0;
|
||||
|
||||
if (delay < 0) {
|
||||
/* underrun - move the application pointer forward to catch up */
|
||||
snd_pcm_forward(p->alsa, -delay);
|
||||
delay = 0;
|
||||
}
|
||||
return (float)delay / (float)ao->samplerate;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_alsa = {
|
||||
.info = &(const struct ao_info) {
|
||||
"ALSA-0.9.x-1.x audio output",
|
||||
"alsa",
|
||||
"Alex Beregszaszi, Zsolt Barat <joy@streamminister.de>",
|
||||
"under development"
|
||||
},
|
||||
.description = "ALSA-0.9.x-1.x audio output",
|
||||
.name = "alsa",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
@@ -754,6 +720,7 @@ const struct ao_driver audio_out_alsa = {
|
||||
.cfg_mixer_index = 0,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("resample", cfg_resample, 0),
|
||||
OPT_STRING("device", cfg_device, 0),
|
||||
OPT_FLAG("block", cfg_block, 0),
|
||||
OPT_STRING("mixer-device", cfg_mixer_device, 0),
|
||||
|
||||
+19
-16
@@ -4,6 +4,9 @@
|
||||
* original copyright (C) Timothy J. Wood - Aug 2000
|
||||
* ported to MPlayer libao2 by Dan Christiansen
|
||||
*
|
||||
* Chris Roccati
|
||||
* Stefano Pigozzi
|
||||
*
|
||||
* The S/PDIF part of the code is based on the auhal audio output
|
||||
* module from VideoLAN:
|
||||
* Copyright (c) 2006 Derk-Jan Hartman <hartman at videolan dot org>
|
||||
@@ -36,9 +39,9 @@
|
||||
#include "ao.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "mpvcore/mp_ring.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "options/m_option.h"
|
||||
#include "misc/ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao_coreaudio_properties.h"
|
||||
#include "audio/out/ao_coreaudio_utils.h"
|
||||
|
||||
@@ -151,9 +154,9 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
if (p->is_digital) {
|
||||
struct priv_d *d = p->digital;
|
||||
// Digital output has no volume adjust.
|
||||
int vol = d->muted ? 0 : 100;
|
||||
int digitalvol = d->muted ? 0 : 100;
|
||||
*control_vol = (ao_control_vol_t) {
|
||||
.left = vol, .right = vol,
|
||||
.left = digitalvol, .right = digitalvol,
|
||||
};
|
||||
return CONTROL_TRUE;
|
||||
}
|
||||
@@ -213,7 +216,7 @@ static void print_list(struct ao *ao)
|
||||
|
||||
for (int i = 0; i < n_devs; i++) {
|
||||
char *name;
|
||||
OSStatus err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &name);
|
||||
err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &name);
|
||||
|
||||
if (err == noErr)
|
||||
talloc_steal(devs, name);
|
||||
@@ -268,7 +271,7 @@ static int init(struct ao *ao)
|
||||
selected_device = p->opt_device_id;
|
||||
}
|
||||
|
||||
if (mp_msg_test_log(ao->log, MSGL_V)) {
|
||||
if (mp_msg_test(ao->log, MSGL_V)) {
|
||||
char *name;
|
||||
err = CA_GET_STR(selected_device, kAudioObjectPropertyName, &name);
|
||||
CHECK_CA_ERROR("could not get selected audio device name");
|
||||
@@ -282,6 +285,8 @@ static int init(struct ao *ao)
|
||||
// Save selected device id
|
||||
p->device = selected_device;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
bool supports_digital = false;
|
||||
/* Probe whether device support S/PDIF stream output if input is AC3 stream. */
|
||||
if (AF_FORMAT_IS_AC3(ao->format)) {
|
||||
@@ -573,10 +578,12 @@ coreaudio_error:
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void *output_samples, int num_bytes, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct priv_d *d = p->digital;
|
||||
void *output_samples = data[0];
|
||||
int num_bytes = samples * ao->sstride;
|
||||
|
||||
// Check whether we need to reset the digital output stream.
|
||||
if (p->is_digital && d->stream_asbd_changed) {
|
||||
@@ -594,7 +601,7 @@ static int play(struct ao *ao, void *output_samples, int num_bytes, int flags)
|
||||
int wrote = mp_ring_write(p->buffer, output_samples, num_bytes);
|
||||
audio_resume(ao);
|
||||
|
||||
return wrote;
|
||||
return wrote / ao->sstride;
|
||||
}
|
||||
|
||||
static void reset(struct ao *ao)
|
||||
@@ -607,7 +614,7 @@ static void reset(struct ao *ao)
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
return mp_ring_available(p->buffer);
|
||||
return mp_ring_available(p->buffer) / ao->sstride;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
@@ -697,12 +704,8 @@ static void audio_resume(struct ao *ao)
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_coreaudio = {
|
||||
.info = &(const struct ao_info) {
|
||||
"CoreAudio (OS X Audio Output)",
|
||||
"coreaudio",
|
||||
"Timothy J. Wood, Dan Christiansen, Chris Roccati & Stefano Pigozzi",
|
||||
"",
|
||||
},
|
||||
.description = "CoreAudio (OS X Audio Output)",
|
||||
.name = "coreaudio",
|
||||
.uninit = uninit,
|
||||
.init = init,
|
||||
.play = play,
|
||||
|
||||
@@ -57,7 +57,7 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
|
||||
if (code == noErr) return true;
|
||||
|
||||
char *error_string = fourcc_repr(NULL, code);
|
||||
mp_msg_log(ao->log, level, "%s (%s)\n", message, error_string);
|
||||
mp_msg(ao->log, level, "%s (%s)\n", message, error_string);
|
||||
talloc_free(error_string);
|
||||
|
||||
return false;
|
||||
@@ -336,6 +336,47 @@ bool ca_change_format(struct ao *ao, AudioStreamID stream,
|
||||
return format_set;
|
||||
}
|
||||
|
||||
static const int speaker_map[][2] = {
|
||||
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
|
||||
{ kAudioChannelLabel_Right, MP_SPEAKER_ID_FR },
|
||||
{ kAudioChannelLabel_Center, MP_SPEAKER_ID_FC },
|
||||
{ kAudioChannelLabel_LFEScreen, MP_SPEAKER_ID_LFE },
|
||||
{ kAudioChannelLabel_LeftSurround, MP_SPEAKER_ID_BL },
|
||||
{ kAudioChannelLabel_RightSurround, MP_SPEAKER_ID_BR },
|
||||
{ kAudioChannelLabel_LeftCenter, MP_SPEAKER_ID_FLC },
|
||||
{ kAudioChannelLabel_RightCenter, MP_SPEAKER_ID_FRC },
|
||||
{ kAudioChannelLabel_CenterSurround, MP_SPEAKER_ID_BC },
|
||||
{ kAudioChannelLabel_LeftSurroundDirect, MP_SPEAKER_ID_SL },
|
||||
{ kAudioChannelLabel_RightSurroundDirect, MP_SPEAKER_ID_SR },
|
||||
{ kAudioChannelLabel_TopCenterSurround, MP_SPEAKER_ID_TC },
|
||||
{ kAudioChannelLabel_VerticalHeightLeft, MP_SPEAKER_ID_TFL },
|
||||
{ kAudioChannelLabel_VerticalHeightCenter, MP_SPEAKER_ID_TFC },
|
||||
{ kAudioChannelLabel_VerticalHeightRight, MP_SPEAKER_ID_TFR },
|
||||
{ kAudioChannelLabel_TopBackLeft, MP_SPEAKER_ID_TBL },
|
||||
{ kAudioChannelLabel_TopBackCenter, MP_SPEAKER_ID_TBC },
|
||||
{ kAudioChannelLabel_TopBackRight, MP_SPEAKER_ID_TBR },
|
||||
|
||||
// unofficial extensions
|
||||
{ kAudioChannelLabel_RearSurroundLeft, MP_SPEAKER_ID_SDL },
|
||||
{ kAudioChannelLabel_RearSurroundRight, MP_SPEAKER_ID_SDR },
|
||||
{ kAudioChannelLabel_LeftWide, MP_SPEAKER_ID_WL },
|
||||
{ kAudioChannelLabel_RightWide, MP_SPEAKER_ID_WR },
|
||||
{ kAudioChannelLabel_LFE2, MP_SPEAKER_ID_LFE2 },
|
||||
|
||||
{ kAudioChannelLabel_HeadphonesLeft, MP_SPEAKER_ID_DL },
|
||||
{ kAudioChannelLabel_HeadphonesRight, MP_SPEAKER_ID_DR },
|
||||
|
||||
{ kAudioChannelLabel_Unknown, -1 },
|
||||
};
|
||||
|
||||
static int ca_label_to_mp_speaker_id(AudioChannelLabel label)
|
||||
{
|
||||
for (int i = 0; speaker_map[i][0] != kAudioChannelLabel_Unknown; i++)
|
||||
if (speaker_map[i][0] == label)
|
||||
return speaker_map[i][1];
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool ca_bitmap_from_ch_desc(struct ao *ao, AudioChannelLayout *layout,
|
||||
uint32_t *bitmap)
|
||||
{
|
||||
@@ -355,14 +396,13 @@ static bool ca_bitmap_from_ch_desc(struct ao *ao, AudioChannelLayout *layout,
|
||||
|
||||
for (int j=0; j < ch_num && all_channels_valid; j++) {
|
||||
AudioChannelLabel label = layout->mChannelDescriptions[j].mChannelLabel;
|
||||
if (label == kAudioChannelLabel_UseCoordinates ||
|
||||
label == kAudioChannelLabel_Unknown ||
|
||||
label > kAudioChannelLabel_TopBackRight) {
|
||||
const int mp_speaker_id = ca_label_to_mp_speaker_id(label);
|
||||
if (mp_speaker_id < 0) {
|
||||
MP_VERBOSE(ao, "channel label=%d unusable to build channel "
|
||||
"bitmap, skipping layout\n", label);
|
||||
all_channels_valid = false;
|
||||
} else {
|
||||
*bitmap |= 1ULL << (label - 1);
|
||||
*bitmap |= 1ULL << mp_speaker_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/out/ao.h"
|
||||
|
||||
#define CA_CFSTR_ENCODING kCFStringEncodingASCII
|
||||
|
||||
+13
-16
@@ -39,9 +39,9 @@
|
||||
#include "audio/format.h"
|
||||
#include "ao.h"
|
||||
#include "audio/reorder_ch.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
/**
|
||||
\todo use the definitions from the win32 api headers when they define these
|
||||
@@ -388,11 +388,11 @@ static int init(struct ao *ao)
|
||||
WAVEFORMATEXTENSIBLE wformat;
|
||||
DSBUFFERDESC dsbpridesc;
|
||||
DSBUFFERDESC dsbdesc;
|
||||
int format = ao->format;
|
||||
int format = af_fmt_from_planar(ao->format);
|
||||
int rate = ao->samplerate;
|
||||
|
||||
if (AF_FORMAT_IS_AC3(format))
|
||||
format = AF_FORMAT_AC3_NE;
|
||||
format = AF_FORMAT_AC3;
|
||||
else {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
@@ -400,14 +400,14 @@ static int init(struct ao *ao)
|
||||
return -1;
|
||||
}
|
||||
switch (format) {
|
||||
case AF_FORMAT_AC3_NE:
|
||||
case AF_FORMAT_AC3:
|
||||
case AF_FORMAT_S24_LE:
|
||||
case AF_FORMAT_S16_LE:
|
||||
case AF_FORMAT_U8:
|
||||
break;
|
||||
default:
|
||||
MP_VERBOSE(ao, "format %s not supported defaulting to Signed 16-bit Little-Endian\n",
|
||||
af_fmt2str_short(format));
|
||||
af_fmt_to_str(format));
|
||||
format = AF_FORMAT_S16_LE;
|
||||
}
|
||||
//set our audio parameters
|
||||
@@ -416,7 +416,7 @@ static int init(struct ao *ao)
|
||||
ao->bps = ao->channels.num * rate * (af_fmt2bits(format) >> 3);
|
||||
int buffersize = ao->bps; // space for 1 sec
|
||||
MP_VERBOSE(ao, "Samplerate:%iHz Channels:%i Format:%s\n", rate,
|
||||
ao->channels.num, af_fmt2str_short(format));
|
||||
ao->channels.num, af_fmt_to_str(format));
|
||||
MP_VERBOSE(ao, "Buffersize:%d bytes (%d msec)\n",
|
||||
buffersize, buffersize / ao->bps * 1000);
|
||||
|
||||
@@ -596,7 +596,7 @@ static int get_space(struct ao *ao)
|
||||
int space = check_free_buffer_size(ao);
|
||||
if (space < p->min_free_space)
|
||||
return 0;
|
||||
return space - p->min_free_space;
|
||||
return (space - p->min_free_space) / ao->sstride;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -606,9 +606,10 @@ static int get_space(struct ao *ao)
|
||||
\param flags currently unused
|
||||
\return number of played bytes
|
||||
*/
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int len = samples * ao->sstride;
|
||||
|
||||
int space = check_free_buffer_size(ao);
|
||||
if (space < len)
|
||||
@@ -616,7 +617,7 @@ static int play(struct ao *ao, void *data, int len, int flags)
|
||||
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
len = (len / p->outburst) * p->outburst;
|
||||
return write_buffer(ao, data, len);
|
||||
return write_buffer(ao, data[0], len) / ao->sstride;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -634,12 +635,8 @@ static float get_delay(struct ao *ao)
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_dsound = {
|
||||
.info = &(const struct ao_info) {
|
||||
"Windows DirectSound audio output",
|
||||
"dsound",
|
||||
"Gabor Szecsi <deje@miki.hu>",
|
||||
""
|
||||
},
|
||||
.description = "Windows DirectSound audio output",
|
||||
.name = "dsound",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
+188
-154
@@ -4,6 +4,8 @@
|
||||
* Copyleft 2001 by Felix Bünemann (atmosfear@users.sf.net)
|
||||
* and Reimar Döffinger (Reimar.Doeffinger@stud.uni-karlsruhe.de)
|
||||
*
|
||||
* Copyleft 2013 by William Light <wrl@illest.net> for the mpv project
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* MPlayer is free software; you can redistribute it and/or modify
|
||||
@@ -27,31 +29,30 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "ao.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
#include "mpvcore/mp_ring.h"
|
||||
#include "misc/ring.h"
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
//! maximum number of channels supported, avoids lots of mallocs
|
||||
#define MAX_CHANS MP_NUM_CHANNELS
|
||||
|
||||
//! size of one chunk, if this is too small MPlayer will start to "stutter"
|
||||
//! after a short time of playback
|
||||
#define CHUNK_SIZE (24 * 1024)
|
||||
#define CHUNK_SIZE (8 * 1024)
|
||||
//! number of "virtual" chunks the buffer consists of
|
||||
#define NUM_CHUNKS 8
|
||||
|
||||
struct port_ring {
|
||||
jack_port_t *port;
|
||||
struct mp_ring *ring;
|
||||
};
|
||||
|
||||
struct priv {
|
||||
jack_port_t * ports[MAX_CHANS];
|
||||
int num_ports; // Number of used ports == number of channels
|
||||
jack_client_t *client;
|
||||
int outburst;
|
||||
float jack_latency;
|
||||
char *cfg_port;
|
||||
char *cfg_client_name;
|
||||
@@ -60,76 +61,49 @@ struct priv {
|
||||
int autostart;
|
||||
int stdlayout;
|
||||
volatile int paused;
|
||||
volatile int underrun; // signals if an underrun occured
|
||||
volatile int underrun;
|
||||
volatile float callback_interval;
|
||||
volatile float callback_time;
|
||||
struct mp_ring *ring; // buffer for audio data
|
||||
|
||||
int num_ports;
|
||||
struct port_ring ports[MP_NUM_CHANNELS];
|
||||
};
|
||||
|
||||
static void silence(float **bufs, int cnt, int num_bufs);
|
||||
|
||||
struct deinterleave {
|
||||
float **bufs;
|
||||
int num_bufs;
|
||||
int cur_buf;
|
||||
int pos;
|
||||
};
|
||||
|
||||
static void deinterleave(void *info, void *src, int len)
|
||||
{
|
||||
struct deinterleave *di = info;
|
||||
float *s = src;
|
||||
int i;
|
||||
len /= sizeof(float);
|
||||
for (i = 0; i < len; i++) {
|
||||
di->bufs[di->cur_buf++][di->pos] = s[i];
|
||||
if (di->cur_buf >= di->num_bufs) {
|
||||
di->cur_buf = 0;
|
||||
di->pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief read data from buffer and splitting it into channels
|
||||
* \param bufs num_bufs float buffers, each will contain the data of one channel
|
||||
* \param cnt number of samples to read per channel
|
||||
* \param num_bufs number of channels to split the data into
|
||||
* \return number of samples read per channel, equals cnt unless there was too
|
||||
* little data in the buffer
|
||||
*
|
||||
* Assumes the data in the buffer is of type float, the number of bytes
|
||||
* read is res * num_bufs * sizeof(float), where res is the return value.
|
||||
* If there is not enough data in the buffer remaining parts will be filled
|
||||
* with silence.
|
||||
*/
|
||||
static int read_buffer(struct mp_ring *ring, float **bufs, int cnt, int num_bufs)
|
||||
{
|
||||
struct deinterleave di = {
|
||||
bufs, num_bufs, 0, 0
|
||||
};
|
||||
int buffered = mp_ring_buffered(ring);
|
||||
if (cnt * sizeof(float) * num_bufs > buffered) {
|
||||
silence(bufs, cnt, num_bufs);
|
||||
cnt = buffered / sizeof(float) / num_bufs;
|
||||
}
|
||||
mp_ring_read_cb(ring, &di, cnt * num_bufs * sizeof(float), deinterleave);
|
||||
return cnt;
|
||||
}
|
||||
|
||||
// end ring buffer stuff
|
||||
|
||||
/**
|
||||
* \brief fill the buffers with silence
|
||||
* \param bufs num_bufs float buffers, each will contain the data of one channel
|
||||
* \param cnt number of samples in each buffer
|
||||
* \param num_bufs number of buffers
|
||||
*/
|
||||
static void silence(float **bufs, int cnt, int num_bufs)
|
||||
static void
|
||||
silence(float *buf, jack_nframes_t nframes)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < num_bufs; i++)
|
||||
memset(bufs[i], 0, cnt * sizeof(float));
|
||||
memset(buf, 0, nframes * sizeof(*buf));
|
||||
}
|
||||
|
||||
static int
|
||||
process_port(struct ao *ao, struct port_ring *pr, jack_nframes_t nframes)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int buffered;
|
||||
float *buf;
|
||||
|
||||
buf = jack_port_get_buffer(pr->port, nframes);
|
||||
|
||||
if (p->paused || p->underrun) {
|
||||
silence(buf, nframes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
buffered = mp_ring_buffered(pr->ring) / sizeof(float);
|
||||
if (buffered < nframes) {
|
||||
mp_ring_read(pr->ring, (void *) buf, buffered * sizeof(float));
|
||||
silence(&buf[buffered], nframes - buffered);
|
||||
return 1;
|
||||
}
|
||||
|
||||
mp_ring_read(pr->ring, (void *) buf, nframes * sizeof(float));
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,18 +114,23 @@ static void silence(float **bufs, int cnt, int num_bufs)
|
||||
*
|
||||
* Write silence into buffers if paused or an underrun occured
|
||||
*/
|
||||
static int outputaudio(jack_nframes_t nframes, void *arg)
|
||||
static int
|
||||
process(jack_nframes_t nframes, void *arg)
|
||||
{
|
||||
struct ao *ao = arg;
|
||||
struct priv *p = ao->priv;
|
||||
float *bufs[MAX_CHANS];
|
||||
int i;
|
||||
for (i = 0; i < p->num_ports; i++)
|
||||
bufs[i] = jack_port_get_buffer(p->ports[i], nframes);
|
||||
if (p->paused || p->underrun || !p->ring)
|
||||
silence(bufs, nframes, p->num_ports);
|
||||
else if (read_buffer(p->ring, bufs, nframes, p->num_ports) < nframes)
|
||||
int i, underrun;
|
||||
|
||||
underrun = 0;
|
||||
|
||||
for (i = 0; i < p->num_ports; i++) {
|
||||
if (process_port(ao, &p->ports[i], nframes))
|
||||
underrun = 1;
|
||||
}
|
||||
|
||||
if (underrun)
|
||||
p->underrun = 1;
|
||||
|
||||
if (p->estimate) {
|
||||
float now = mp_time_us() / 1000000.0;
|
||||
float diff = p->callback_time + p->callback_interval - now;
|
||||
@@ -161,117 +140,163 @@ static int outputaudio(jack_nframes_t nframes, void *arg)
|
||||
p->callback_time = now;
|
||||
p->callback_interval = (float)nframes / (float)ao->samplerate;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
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;
|
||||
const char **matching_ports = NULL;
|
||||
int port_flags = JackPortIsInput;
|
||||
int i;
|
||||
|
||||
if (!port_name)
|
||||
port_flags |= JackPortIsPhysical;
|
||||
|
||||
matching_ports = jack_get_ports(p->client, port_name, NULL, port_flags);
|
||||
|
||||
if (!matching_ports || !matching_ports[0]) {
|
||||
MP_FATAL(ao, "no ports to connect to\n");
|
||||
goto err_get_ports;
|
||||
}
|
||||
|
||||
for (i = 0; i < p->num_ports && matching_ports[i]; i++) {
|
||||
if (jack_connect(p->client, jack_port_name(p->ports[i].port),
|
||||
matching_ports[i])) {
|
||||
MP_FATAL(ao, "connecting failed\n");
|
||||
goto err_connect;
|
||||
}
|
||||
}
|
||||
|
||||
free(matching_ports);
|
||||
return 0;
|
||||
|
||||
err_connect:
|
||||
free(matching_ports);
|
||||
err_get_ports:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
create_ports(struct ao *ao, int nports)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
struct port_ring *pr;
|
||||
char pname[30];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nports; i++) {
|
||||
pr = &p->ports[i];
|
||||
|
||||
snprintf(pname, sizeof(pname), "out_%d", i);
|
||||
pr->port = jack_port_register(p->client, pname, JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
|
||||
if (!pr->port) {
|
||||
MP_FATAL(ao, "not enough ports available\n");
|
||||
goto err_port_register;
|
||||
}
|
||||
|
||||
pr->ring = mp_ring_new(p, NUM_CHUNKS * CHUNK_SIZE);
|
||||
}
|
||||
|
||||
p->num_ports = nports;
|
||||
return 0;
|
||||
|
||||
err_port_register:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
const char **matching_ports = NULL;
|
||||
char *port_name = p->cfg_port && p->cfg_port[0] ? p->cfg_port : NULL;
|
||||
jack_options_t open_options = JackNullOption;
|
||||
int port_flags = JackPortIsInput;
|
||||
int i;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
jack_options_t open_options;
|
||||
|
||||
if (p->stdlayout == 0) {
|
||||
ao->format = AF_FORMAT_FLOATP;
|
||||
|
||||
switch (p->stdlayout) {
|
||||
case 0:
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
} else if (p->stdlayout == 1) {
|
||||
break;
|
||||
|
||||
case 1:
|
||||
mp_chmap_sel_add_alsa_def(&sel);
|
||||
} else {
|
||||
break;
|
||||
|
||||
default:
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
}
|
||||
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
goto err_out;
|
||||
goto err_chmap;
|
||||
|
||||
open_options = JackNullOption;
|
||||
if (!p->autostart)
|
||||
open_options |= JackNoStartServer;
|
||||
|
||||
p->client = jack_client_open(p->cfg_client_name, open_options, NULL);
|
||||
if (!p->client) {
|
||||
MP_FATAL(ao, "cannot open server\n");
|
||||
goto err_out;
|
||||
}
|
||||
jack_set_process_callback(p->client, outputaudio, ao);
|
||||
|
||||
// list matching ports if connections should be made
|
||||
if (p->connect) {
|
||||
if (!port_name)
|
||||
port_flags |= JackPortIsPhysical;
|
||||
matching_ports = jack_get_ports(p->client, port_name, NULL, port_flags);
|
||||
if (!matching_ports || !matching_ports[0]) {
|
||||
MP_FATAL(ao, "no physical ports available\n");
|
||||
goto err_out;
|
||||
}
|
||||
i = 1;
|
||||
p->num_ports = ao->channels.num;
|
||||
while (matching_ports[i])
|
||||
i++;
|
||||
if (p->num_ports > i)
|
||||
p->num_ports = i;
|
||||
goto err_client_open;
|
||||
}
|
||||
|
||||
// create out output ports
|
||||
for (i = 0; i < p->num_ports; i++) {
|
||||
char pname[30];
|
||||
snprintf(pname, 30, "out_%d", i);
|
||||
p->ports[i] =
|
||||
jack_port_register(p->client, pname, JACK_DEFAULT_AUDIO_TYPE,
|
||||
JackPortIsOutput, 0);
|
||||
if (!p->ports[i]) {
|
||||
MP_FATAL(ao, "not enough ports available\n");
|
||||
goto err_out;
|
||||
}
|
||||
}
|
||||
if (create_ports(ao, ao->channels.num))
|
||||
goto err_create_ports;
|
||||
|
||||
jack_set_process_callback(p->client, process, ao);
|
||||
|
||||
if (jack_activate(p->client)) {
|
||||
MP_FATAL(ao, "activate failed\n");
|
||||
goto err_out;
|
||||
}
|
||||
for (i = 0; i < p->num_ports; i++) {
|
||||
if (jack_connect(p->client, jack_port_name(p->ports[i]),
|
||||
matching_ports[i]))
|
||||
{
|
||||
MP_FATAL(ao, "connecting failed\n");
|
||||
goto err_out;
|
||||
}
|
||||
goto err_activate;
|
||||
}
|
||||
|
||||
ao->samplerate = jack_get_sample_rate(p->client);
|
||||
|
||||
if (p->connect)
|
||||
if (connect_to_outports(ao))
|
||||
goto err_connect;
|
||||
|
||||
jack_latency_range_t jack_latency_range;
|
||||
jack_port_get_latency_range(p->ports[0], JackPlaybackLatency,
|
||||
jack_port_get_latency_range(p->ports[0].port, JackPlaybackLatency,
|
||||
&jack_latency_range);
|
||||
p->jack_latency = (float)(jack_latency_range.max + jack_get_buffer_size(p->client))
|
||||
/ (float)ao->samplerate;
|
||||
p->callback_interval = 0;
|
||||
|
||||
if (!ao_chmap_sel_get_def(ao, &sel, &ao->channels, p->num_ports))
|
||||
goto err_out;
|
||||
goto err_chmap_sel_get_def;
|
||||
|
||||
ao->format = AF_FORMAT_FLOAT_NE;
|
||||
int unitsize = ao->channels.num * sizeof(float);
|
||||
p->outburst = (CHUNK_SIZE + unitsize - 1) / unitsize * unitsize;
|
||||
p->ring = mp_ring_new(p, NUM_CHUNKS * p->outburst);
|
||||
free(matching_ports);
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
free(matching_ports);
|
||||
if (p->client)
|
||||
jack_client_close(p->client);
|
||||
err_chmap_sel_get_def:
|
||||
err_connect:
|
||||
jack_deactivate(p->client);
|
||||
err_activate:
|
||||
err_create_ports:
|
||||
jack_client_close(p->client);
|
||||
err_client_open:
|
||||
err_chmap:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int buffered = mp_ring_buffered(p->ring); // could be less
|
||||
int buffered = mp_ring_buffered(p->ports[0].ring); // could be less
|
||||
float in_jack = p->jack_latency;
|
||||
|
||||
if (p->estimate && p->callback_interval > 0) {
|
||||
float elapsed = mp_time_us() / 1000000.0 - p->callback_time;
|
||||
in_jack += p->callback_interval - elapsed;
|
||||
if (in_jack < 0)
|
||||
in_jack = 0;
|
||||
}
|
||||
|
||||
return (float)buffered / (float)ao->bps + in_jack;
|
||||
}
|
||||
|
||||
@@ -281,8 +306,12 @@ static float get_delay(struct ao *ao)
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int i;
|
||||
p->paused = 1;
|
||||
mp_ring_reset(p->ring);
|
||||
|
||||
for (i = 0; i < p->num_ports; i++)
|
||||
mp_ring_reset(p->ports[i].ring);
|
||||
|
||||
p->paused = 0;
|
||||
}
|
||||
|
||||
@@ -290,10 +319,10 @@ static void reset(struct ao *ao)
|
||||
static void uninit(struct ao *ao, bool immed)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
|
||||
if (!immed)
|
||||
mp_sleep_us(get_delay(ao) * 1000 * 1000);
|
||||
// HACK, make sure jack doesn't loop-output dirty buffers
|
||||
reset(ao);
|
||||
|
||||
mp_sleep_us(100 * 1000);
|
||||
jack_client_close(p->client);
|
||||
}
|
||||
@@ -319,30 +348,35 @@ static void audio_resume(struct ao *ao)
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
return mp_ring_available(p->ring);
|
||||
return mp_ring_available(p->ports[0].ring) / ao->sstride;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief write data into buffer and reset underrun flag
|
||||
*/
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
if (!(flags & AOPLAY_FINAL_CHUNK))
|
||||
len -= len % p->outburst;
|
||||
struct port_ring *pr;
|
||||
int i, len, ret;
|
||||
|
||||
len = samples * ao->sstride;
|
||||
ret = 0;
|
||||
|
||||
for (i = 0; i < p->num_ports; i++) {
|
||||
pr = &p->ports[i];
|
||||
ret = mp_ring_write(pr->ring, data[i], len);
|
||||
}
|
||||
|
||||
p->underrun = 0;
|
||||
return mp_ring_write(p->ring, data, len);
|
||||
return ret / ao->sstride;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_jack = {
|
||||
.info = &(const struct ao_info) {
|
||||
"JACK audio output",
|
||||
"jack",
|
||||
"Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>",
|
||||
"based on ao_sdl.c"
|
||||
},
|
||||
.description = "JACK audio output",
|
||||
.name = "jack",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.get_space = get_space,
|
||||
@@ -364,7 +398,7 @@ const struct ao_driver audio_out_jack = {
|
||||
OPT_FLAG("autostart", autostart, 0),
|
||||
OPT_FLAG("connect", connect, 0),
|
||||
OPT_CHOICE("std-channel-layout", stdlayout, 0,
|
||||
({"waveext", 0}, {"alsa", 1}, {"any", 2})),
|
||||
({"waveext", 0}, {"alsa", 1}, {"any", 2})),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
+96
-288
@@ -22,36 +22,31 @@
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include <libavutil/common.h>
|
||||
#include <libavutil/audioconvert.h>
|
||||
|
||||
#include "compat/libav.h"
|
||||
#include "config.h"
|
||||
#include "mpvcore/options.h"
|
||||
#include "mpvcore/mp_common.h"
|
||||
#include "options/options.h"
|
||||
#include "common/common.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/reorder_ch.h"
|
||||
#include "audio/fmt-conversion.h"
|
||||
#include "talloc.h"
|
||||
#include "ao.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "mpvcore/encode_lavc.h"
|
||||
|
||||
static const char *sample_padding_signed = "\x00\x00\x00\x00";
|
||||
static const char *sample_padding_u8 = "\x80";
|
||||
static const char *sample_padding_float = "\x00\x00\x00\x00";
|
||||
#include "common/encode_lavc.h"
|
||||
|
||||
struct priv {
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size;
|
||||
AVStream *stream;
|
||||
bool planarize;
|
||||
int pcmhack;
|
||||
int aframesize;
|
||||
int aframecount;
|
||||
int offset;
|
||||
int offset_left;
|
||||
int64_t savepts;
|
||||
int framecount;
|
||||
int64_t lastpts;
|
||||
@@ -63,13 +58,44 @@ struct priv {
|
||||
int worst_time_base_is_stream;
|
||||
};
|
||||
|
||||
static void select_format(struct ao *ao, AVCodec *codec)
|
||||
{
|
||||
int best_score = INT_MIN;
|
||||
int best_format = 0;
|
||||
|
||||
// Check the encoder's list of supported formats.
|
||||
for (const enum AVSampleFormat *sampleformat = codec->sample_fmts;
|
||||
sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
|
||||
++sampleformat)
|
||||
{
|
||||
int fmt = af_from_avformat(*sampleformat);
|
||||
if (!fmt) {
|
||||
MP_WARN(ao, "unsupported lavc format %s",
|
||||
av_get_sample_fmt_name(*sampleformat));
|
||||
continue;
|
||||
}
|
||||
int score = af_format_conversion_score(fmt, ao->format);
|
||||
if (score > best_score) {
|
||||
best_score = score;
|
||||
best_format = fmt;
|
||||
}
|
||||
}
|
||||
|
||||
if (best_format) {
|
||||
ao->format = best_format;
|
||||
} else {
|
||||
MP_ERR(ao, "sample format not found\n"); // shouldn't happen
|
||||
}
|
||||
}
|
||||
|
||||
// open & setup audio device
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = talloc_zero(ao, struct priv);
|
||||
const enum AVSampleFormat *sampleformat;
|
||||
AVCodec *codec;
|
||||
|
||||
ao->priv = ac;
|
||||
|
||||
if (!encode_lavc_available(ao->encode_lavc_ctx)) {
|
||||
MP_ERR(ao, "the option --o (output file) must be specified\n");
|
||||
return -1;
|
||||
@@ -106,141 +132,10 @@ static int init(struct ao *ao)
|
||||
|
||||
ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_NONE;
|
||||
|
||||
{
|
||||
// first check if the selected format is somewhere in the list of
|
||||
// supported formats by the codec
|
||||
for (sampleformat = codec->sample_fmts;
|
||||
sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
|
||||
++sampleformat) {
|
||||
switch (*sampleformat) {
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_U8P:
|
||||
if (ao->format == AF_FORMAT_U8)
|
||||
goto out_search;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
if (ao->format == AF_FORMAT_S16_BE)
|
||||
goto out_search;
|
||||
if (ao->format == AF_FORMAT_S16_LE)
|
||||
goto out_search;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
if (ao->format == AF_FORMAT_S32_BE)
|
||||
goto out_search;
|
||||
if (ao->format == AF_FORMAT_S32_LE)
|
||||
goto out_search;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
case AV_SAMPLE_FMT_FLTP:
|
||||
if (ao->format == AF_FORMAT_FLOAT_BE)
|
||||
goto out_search;
|
||||
if (ao->format == AF_FORMAT_FLOAT_LE)
|
||||
goto out_search;
|
||||
break;
|
||||
// FIXME do we need support for AV_SAMPLE_FORMAT_DBL/DBLP?
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
out_search:
|
||||
;
|
||||
}
|
||||
|
||||
if (!sampleformat || *sampleformat == AV_SAMPLE_FMT_NONE) {
|
||||
// if the selected format is not supported, we have to pick the first
|
||||
// one we CAN support
|
||||
// note: not needing to select endianness here, as the switch() below
|
||||
// does that anyway for us
|
||||
for (sampleformat = codec->sample_fmts;
|
||||
sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
|
||||
++sampleformat) {
|
||||
switch (*sampleformat) {
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
case AV_SAMPLE_FMT_U8P:
|
||||
ao->format = AF_FORMAT_U8;
|
||||
goto out_takefirst;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
case AV_SAMPLE_FMT_S16P:
|
||||
ao->format = AF_FORMAT_S16_NE;
|
||||
goto out_takefirst;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
case AV_SAMPLE_FMT_S32P:
|
||||
ao->format = AF_FORMAT_S32_NE;
|
||||
goto out_takefirst;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
case AV_SAMPLE_FMT_FLTP:
|
||||
ao->format = AF_FORMAT_FLOAT_NE;
|
||||
goto out_takefirst;
|
||||
// FIXME do we need support for AV_SAMPLE_FORMAT_DBL/DBLP?
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
out_takefirst:
|
||||
;
|
||||
}
|
||||
|
||||
switch (ao->format) {
|
||||
// now that we have chosen a format, set up the fields for it, boldly
|
||||
// switching endianness if needed (mplayer code will convert for us
|
||||
// anyway, but ffmpeg always expects native endianness)
|
||||
case AF_FORMAT_U8:
|
||||
ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_U8;
|
||||
ac->sample_size = 1;
|
||||
ac->sample_padding = sample_padding_u8;
|
||||
ao->format = AF_FORMAT_U8;
|
||||
break;
|
||||
default:
|
||||
case AF_FORMAT_S16_BE:
|
||||
case AF_FORMAT_S16_LE:
|
||||
ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
ac->sample_size = 2;
|
||||
ac->sample_padding = sample_padding_signed;
|
||||
ao->format = AF_FORMAT_S16_NE;
|
||||
break;
|
||||
case AF_FORMAT_S32_BE:
|
||||
case AF_FORMAT_S32_LE:
|
||||
ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_S32;
|
||||
ac->sample_size = 4;
|
||||
ac->sample_padding = sample_padding_signed;
|
||||
ao->format = AF_FORMAT_S32_NE;
|
||||
break;
|
||||
case AF_FORMAT_FLOAT_BE:
|
||||
case AF_FORMAT_FLOAT_LE:
|
||||
ac->stream->codec->sample_fmt = AV_SAMPLE_FMT_FLT;
|
||||
ac->sample_size = 4;
|
||||
ac->sample_padding = sample_padding_float;
|
||||
ao->format = AF_FORMAT_FLOAT_NE;
|
||||
break;
|
||||
}
|
||||
|
||||
// detect if we have to planarize
|
||||
ac->planarize = false;
|
||||
{
|
||||
bool found_format = false;
|
||||
bool found_planar_format = false;
|
||||
for (sampleformat = codec->sample_fmts;
|
||||
sampleformat && *sampleformat != AV_SAMPLE_FMT_NONE;
|
||||
++sampleformat) {
|
||||
if (*sampleformat == ac->stream->codec->sample_fmt)
|
||||
found_format = true;
|
||||
if (*sampleformat ==
|
||||
av_get_planar_sample_fmt(ac->stream->codec->sample_fmt))
|
||||
found_planar_format = true;
|
||||
}
|
||||
if (!found_format && found_planar_format) {
|
||||
ac->stream->codec->sample_fmt =
|
||||
av_get_planar_sample_fmt(ac->stream->codec->sample_fmt);
|
||||
ac->planarize = true;
|
||||
}
|
||||
if (!found_format && !found_planar_format) {
|
||||
// shouldn't happen
|
||||
MP_ERR(ao, "sample format not found\n");
|
||||
}
|
||||
}
|
||||
select_format(ao, codec);
|
||||
|
||||
ac->sample_size = af_fmt2bits(ao->format) / 8;
|
||||
ac->stream->codec->sample_fmt = af_to_avformat(ao->format);
|
||||
ac->stream->codec->bits_per_raw_sample = ac->sample_size * 8;
|
||||
|
||||
if (encode_lavc_open_codec(ao->encode_lavc_ctx, ac->stream) < 0)
|
||||
@@ -270,33 +165,14 @@ out_takefirst:
|
||||
|
||||
ac->savepts = MP_NOPTS_VALUE;
|
||||
ac->lastpts = MP_NOPTS_VALUE;
|
||||
ac->offset = ac->stream->codec->sample_rate *
|
||||
encode_lavc_getoffset(ao->encode_lavc_ctx, ac->stream);
|
||||
ac->offset_left = ac->offset;
|
||||
|
||||
ao->untimed = true;
|
||||
ao->priv = ac;
|
||||
|
||||
if (ac->planarize)
|
||||
MP_WARN(ao, "need to planarize audio data\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fill_with_padding(void *buf, int cnt, int sz, const void *padding)
|
||||
{
|
||||
int i;
|
||||
if (sz == 1) {
|
||||
memset(buf, cnt, *(char *)padding);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < cnt; ++i)
|
||||
memcpy((char *) buf + i * sz, padding, sz);
|
||||
}
|
||||
|
||||
// close audio device
|
||||
static int encode(struct ao *ao, double apts, void *data);
|
||||
static int play(struct ao *ao, void *data, int len, int flags);
|
||||
static int encode(struct ao *ao, double apts, void **data);
|
||||
static void uninit(struct ao *ao, bool cut_audio)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
@@ -308,29 +184,10 @@ static void uninit(struct ao *ao, bool cut_audio)
|
||||
}
|
||||
|
||||
if (ac->buffer) {
|
||||
if (ao->buffer.len > 0) {
|
||||
// TRICK: append aframesize-1 samples to the end, then play() will
|
||||
// encode all it can
|
||||
size_t extralen =
|
||||
(ac->aframesize - 1) * ao->channels.num * ac->sample_size;
|
||||
void *paddingbuf = talloc_size(ao, ao->buffer.len + extralen);
|
||||
memcpy(paddingbuf, ao->buffer.start, ao->buffer.len);
|
||||
fill_with_padding((char *) paddingbuf + ao->buffer.len,
|
||||
extralen / ac->sample_size,
|
||||
ac->sample_size, ac->sample_padding);
|
||||
int written = play(ao, paddingbuf, ao->buffer.len + extralen, 0);
|
||||
if (written < ao->buffer.len) {
|
||||
MP_ERR(ao, "did not write enough data at the end\n");
|
||||
}
|
||||
talloc_free(paddingbuf);
|
||||
ao->buffer.len = 0;
|
||||
}
|
||||
|
||||
double outpts = ac->expected_next_pts;
|
||||
if (!ectx->options->rawts && ectx->options->copyts)
|
||||
outpts += ectx->discontinuity_pts_offset;
|
||||
outpts += encode_lavc_getoffset(ectx, ac->stream);
|
||||
|
||||
while (encode(ao, outpts, NULL) > 0) ;
|
||||
}
|
||||
|
||||
@@ -342,11 +199,11 @@ static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
|
||||
return ac->aframesize * ac->sample_size * ao->channels.num * ac->framecount;
|
||||
return ac->aframesize * ac->framecount;
|
||||
}
|
||||
|
||||
// must get exactly ac->aframesize amount of data
|
||||
static int encode(struct ao *ao, double apts, void *data)
|
||||
static int encode(struct ao *ao, double apts, void **data)
|
||||
{
|
||||
AVFrame *frame;
|
||||
AVPacket packet;
|
||||
@@ -369,22 +226,11 @@ static int encode(struct ao *ao, double apts, void *data)
|
||||
frame = avcodec_alloc_frame();
|
||||
frame->nb_samples = ac->aframesize;
|
||||
|
||||
if (ac->planarize) {
|
||||
void *data2 = talloc_size(ao, ac->aframesize * ao->channels.num *
|
||||
ac->sample_size);
|
||||
reorder_to_planar(data2, data, ac->sample_size, ao->channels.num,
|
||||
ac->aframesize);
|
||||
data = data2;
|
||||
}
|
||||
assert(ao->channels.num <= AV_NUM_DATA_POINTERS);
|
||||
for (int n = 0; n < ao->channels.num; n++)
|
||||
frame->extended_data[n] = data[n];
|
||||
|
||||
size_t audiolen = ac->aframesize * ao->channels.num * ac->sample_size;
|
||||
if (avcodec_fill_audio_frame(frame, ao->channels.num,
|
||||
ac->stream->codec->sample_fmt, data,
|
||||
audiolen, 1))
|
||||
{
|
||||
MP_ERR(ao, "error filling\n");
|
||||
return -1;
|
||||
}
|
||||
frame->linesize[0] = frame->nb_samples * ao->sstride;
|
||||
|
||||
if (ectx->options->rawts || ectx->options->copyts) {
|
||||
// real audio pts
|
||||
@@ -414,11 +260,6 @@ static int encode(struct ao *ao, double apts, void *data)
|
||||
}
|
||||
|
||||
avcodec_free_frame(&frame);
|
||||
|
||||
if (ac->planarize) {
|
||||
talloc_free(data);
|
||||
data = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -471,26 +312,47 @@ static int encode(struct ao *ao, double apts, void *data)
|
||||
return packet.size;
|
||||
}
|
||||
|
||||
// plays 'len' bytes of 'data'
|
||||
// it should round it down to frame sizes
|
||||
// return: number of bytes played
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
// this should round samples down to frame sizes
|
||||
// return: number of samples played
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *ac = ao->priv;
|
||||
struct encode_lavc_context *ectx = ao->encode_lavc_ctx;
|
||||
int bufpos = 0;
|
||||
int64_t ptsoffset;
|
||||
void *paddingbuf = NULL;
|
||||
double nextpts;
|
||||
double pts = ao->pts;
|
||||
double outpts;
|
||||
|
||||
len /= ac->sample_size * ao->channels.num;
|
||||
|
||||
if (!encode_lavc_start(ectx)) {
|
||||
MP_WARN(ao, "not ready yet for encoding audio\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t num_planes = af_fmt_is_planar(ao->format) ? ao->channels.num : 1;
|
||||
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
int written = 0;
|
||||
if (samples > 0) {
|
||||
void *tmp = talloc_new(NULL);
|
||||
size_t bytelen = samples * ao->sstride;
|
||||
size_t extralen = (ac->aframesize - 1) * ao->sstride;
|
||||
void *padded[MP_NUM_CHANNELS];
|
||||
for (int n = 0; n < num_planes; n++) {
|
||||
padded[n] = talloc_size(tmp, bytelen + extralen);
|
||||
memcpy(padded[n], data[n], bytelen);
|
||||
af_fill_silence((char *)padded[n] + bytelen, extralen, ao->format);
|
||||
}
|
||||
// No danger of recursion, because AOPLAY_FINAL_CHUNK not set
|
||||
written = play(ao, padded, (bytelen + extralen) / ao->sstride, 0);
|
||||
if (written < samples) {
|
||||
MP_ERR(ao, "did not write enough data at the end\n");
|
||||
}
|
||||
talloc_free(tmp);
|
||||
}
|
||||
|
||||
return FFMIN(written, samples);
|
||||
}
|
||||
|
||||
if (pts == MP_NOPTS_VALUE) {
|
||||
MP_WARN(ao, "frame without pts, please report; synthesizing pts instead\n");
|
||||
// synthesize pts from previous expected next pts
|
||||
@@ -537,63 +399,10 @@ static int play(struct ao *ao, void *data, int len, int flags)
|
||||
// Absurd, as this range MUST contain at least one multiple of B.
|
||||
}
|
||||
|
||||
ptsoffset = ac->offset;
|
||||
// this basically just edits ao->apts for syncing purposes
|
||||
|
||||
if (ectx->options->copyts || ectx->options->rawts) {
|
||||
// we do not send time sync data to the video side,
|
||||
// but we always need the exact pts, even if zero
|
||||
} else {
|
||||
// here we must "simulate" the pts editing
|
||||
// 1. if we have to skip stuff, we skip it
|
||||
// 2. if we have to add samples, we add them
|
||||
// 3. we must still adjust ptsoffset appropriately for AV sync!
|
||||
// invariant:
|
||||
// if no partial skipping is done, the first frame gets ao->apts passed as pts!
|
||||
|
||||
if (ac->offset_left < 0) {
|
||||
if (ac->offset_left <= -len) {
|
||||
// skip whole frame
|
||||
ac->offset_left += len;
|
||||
return len * ac->sample_size * ao->channels.num;
|
||||
} else {
|
||||
// skip part of this frame, buffer/encode the rest
|
||||
bufpos -= ac->offset_left;
|
||||
ptsoffset += ac->offset_left;
|
||||
ac->offset_left = 0;
|
||||
}
|
||||
} else if (ac->offset_left > 0) {
|
||||
// make a temporary buffer, filled with zeroes at the start
|
||||
// (don't worry, only happens once)
|
||||
|
||||
paddingbuf = talloc_size(ac, ac->sample_size * ao->channels.num *
|
||||
(ac->offset_left + len));
|
||||
fill_with_padding(paddingbuf, ac->offset_left, ac->sample_size,
|
||||
ac->sample_padding);
|
||||
data = (char *) paddingbuf + ac->sample_size * ao->channels.num *
|
||||
ac->offset_left;
|
||||
bufpos -= ac->offset_left; // yes, negative!
|
||||
ptsoffset += ac->offset_left;
|
||||
ac->offset_left = 0;
|
||||
|
||||
// now adjust the bufpos so the final value of bufpos is positive!
|
||||
/*
|
||||
int cnt = (len - bufpos) / ac->aframesize;
|
||||
int finalbufpos = bufpos + cnt * ac->aframesize;
|
||||
*/
|
||||
int finalbufpos = len - (len - bufpos) % ac->aframesize;
|
||||
if (finalbufpos < 0) {
|
||||
MP_WARN(ao, "cannot attain the "
|
||||
"exact requested audio sync; shifting by %d frames\n",
|
||||
-finalbufpos);
|
||||
bufpos -= finalbufpos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fix and apply the discontinuity pts offset.
|
||||
if (!ectx->options->rawts && ectx->options->copyts) {
|
||||
// fix the discontinuity pts offset
|
||||
nextpts = pts + ptsoffset / (double) ao->samplerate;
|
||||
nextpts = pts;
|
||||
if (ectx->discontinuity_pts_offset == MP_NOPTS_VALUE) {
|
||||
ectx->discontinuity_pts_offset = ectx->next_in_pts - nextpts;
|
||||
}
|
||||
@@ -606,39 +415,38 @@ static int play(struct ao *ao, void *data, int len, int flags)
|
||||
|
||||
outpts = pts + ectx->discontinuity_pts_offset;
|
||||
}
|
||||
else
|
||||
else {
|
||||
outpts = pts;
|
||||
}
|
||||
|
||||
while (len - bufpos >= ac->aframesize) {
|
||||
encode(ao,
|
||||
outpts + (bufpos + ptsoffset) / (double) ao->samplerate + encode_lavc_getoffset(ectx, ac->stream),
|
||||
(char *) data + ac->sample_size * bufpos * ao->channels.num);
|
||||
// Shift pts by the pts offset first.
|
||||
outpts += encode_lavc_getoffset(ectx, ac->stream);
|
||||
|
||||
while (samples - bufpos >= ac->aframesize) {
|
||||
void *start[MP_NUM_CHANNELS];
|
||||
for (int n = 0; n < num_planes; n++)
|
||||
start[n] = (char *)data[n] + bufpos * ao->sstride;
|
||||
encode(ao, outpts + bufpos / (double) ao->samplerate, start);
|
||||
bufpos += ac->aframesize;
|
||||
}
|
||||
|
||||
talloc_free(paddingbuf);
|
||||
|
||||
// calculate expected pts of next audio frame
|
||||
ac->expected_next_pts = pts + (bufpos + ptsoffset) / (double) ao->samplerate;
|
||||
// Calculate expected pts of next audio frame (input side).
|
||||
ac->expected_next_pts = pts + bufpos / (double) ao->samplerate;
|
||||
|
||||
// Set next allowed input pts value (input side).
|
||||
if (!ectx->options->rawts && ectx->options->copyts) {
|
||||
// set next allowed output pts value
|
||||
nextpts = ac->expected_next_pts + ectx->discontinuity_pts_offset;
|
||||
if (nextpts > ectx->next_in_pts)
|
||||
ectx->next_in_pts = nextpts;
|
||||
}
|
||||
|
||||
return bufpos * ac->sample_size * ao->channels.num;
|
||||
return bufpos;
|
||||
}
|
||||
|
||||
const struct ao_driver audio_out_lavc = {
|
||||
.encode = true,
|
||||
.info = &(const struct ao_info) {
|
||||
"audio encoding using libavcodec",
|
||||
"lavc",
|
||||
"Rudolf Polzer <divVerent@xonotic.org>",
|
||||
""
|
||||
},
|
||||
.description = "audio encoding using libavcodec",
|
||||
.name = "lavc",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.get_space = get_space,
|
||||
|
||||
+90
-31
@@ -18,6 +18,11 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Note: this does much more than just ignoring audio output. It simulates
|
||||
* (to some degree) an ideal AO.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@@ -25,41 +30,66 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "audio/format.h"
|
||||
#include "ao.h"
|
||||
|
||||
struct priv {
|
||||
bool paused;
|
||||
double last_time;
|
||||
float buffered_bytes;
|
||||
int buffersize;
|
||||
int outburst;
|
||||
bool playing_final;
|
||||
float buffered; // samples
|
||||
int buffersize; // samples
|
||||
|
||||
int untimed;
|
||||
float bufferlen; // seconds
|
||||
|
||||
// Minimal unit of audio samples that can be written at once. If play() is
|
||||
// called with sizes not aligned to this, a rounded size will be returned.
|
||||
// (This is not needed by the AO API, but many AOs behave this way.)
|
||||
int outburst; // samples
|
||||
};
|
||||
|
||||
static void drain(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
if (ao->untimed) {
|
||||
priv->buffered = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (priv->paused)
|
||||
return;
|
||||
|
||||
double now = mp_time_sec();
|
||||
priv->buffered_bytes -= (now - priv->last_time) * ao->bps;
|
||||
if (priv->buffered_bytes < 0)
|
||||
priv->buffered_bytes = 0;
|
||||
if (priv->buffered > 0) {
|
||||
priv->buffered -= (now - priv->last_time) * ao->samplerate;
|
||||
if (priv->buffered < 0) {
|
||||
if (!priv->playing_final)
|
||||
MP_ERR(ao, "buffer underrun\n");
|
||||
priv->buffered = 0;
|
||||
}
|
||||
}
|
||||
priv->last_time = now;
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = talloc_zero(ao, struct priv);
|
||||
ao->priv = priv;
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
ao->untimed = priv->untimed;
|
||||
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_any(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
return -1;
|
||||
|
||||
int samplesize = af_fmt2bits(ao->format) / 8;
|
||||
priv->outburst = 256 * ao->channels.num * samplesize;
|
||||
// A "buffer" for about 0.2 seconds of audio
|
||||
priv->buffersize = (int)(ao->samplerate * 0.2 / 256 + 1) * priv->outburst;
|
||||
// A "buffer" for this many seconds of audio
|
||||
int bursts = (int)(ao->samplerate * priv->bufferlen + 1) / priv->outburst;
|
||||
priv->buffersize = priv->outburst * bursts;
|
||||
|
||||
priv->last_time = mp_time_sec();
|
||||
|
||||
return 0;
|
||||
@@ -68,25 +98,36 @@ static int init(struct ao *ao)
|
||||
// close audio device
|
||||
static void uninit(struct ao *ao, bool cut_audio)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
if (!cut_audio && !priv->paused)
|
||||
mp_sleep_us(1000.0 * 1000.0 * priv->buffered / ao->samplerate);
|
||||
}
|
||||
|
||||
// stop playing and empty buffers (for seeking/pause)
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
priv->buffered_bytes = 0;
|
||||
priv->buffered = 0;
|
||||
priv->playing_final = false;
|
||||
}
|
||||
|
||||
// stop playing, keep buffers (for pause)
|
||||
static void pause(struct ao *ao)
|
||||
{
|
||||
// for now, just call reset();
|
||||
reset(ao);
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
drain(ao);
|
||||
priv->paused = true;
|
||||
}
|
||||
|
||||
// resume playing, after audio_pause()
|
||||
// resume playing, after pause()
|
||||
static void resume(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
drain(ao);
|
||||
priv->paused = false;
|
||||
priv->last_time = mp_time_sec();
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
@@ -94,18 +135,28 @@ static int get_space(struct ao *ao)
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
drain(ao);
|
||||
return priv->buffersize - priv->buffered_bytes;
|
||||
return priv->buffersize - priv->buffered;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
int accepted;
|
||||
|
||||
int maxbursts = (priv->buffersize - priv->buffered_bytes) / priv->outburst;
|
||||
int playbursts = len / priv->outburst;
|
||||
int bursts = playbursts > maxbursts ? maxbursts : playbursts;
|
||||
priv->buffered_bytes += bursts * priv->outburst;
|
||||
return bursts * priv->outburst;
|
||||
resume(ao);
|
||||
|
||||
priv->playing_final = flags & AOPLAY_FINAL_CHUNK;
|
||||
if (priv->playing_final) {
|
||||
// Last audio chunk - don't round to outburst.
|
||||
accepted = MPMIN(priv->buffersize - priv->buffered, samples);
|
||||
} else {
|
||||
int maxbursts = (priv->buffersize - priv->buffered) / priv->outburst;
|
||||
int playbursts = samples / priv->outburst;
|
||||
int bursts = playbursts > maxbursts ? maxbursts : playbursts;
|
||||
accepted = bursts * priv->outburst;
|
||||
}
|
||||
priv->buffered += accepted;
|
||||
return accepted;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
@@ -113,16 +164,14 @@ static float get_delay(struct ao *ao)
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
drain(ao);
|
||||
return priv->buffered_bytes / ao->bps;
|
||||
return priv->buffered / (double)ao->samplerate;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_null = {
|
||||
.info = &(const struct ao_info) {
|
||||
"Null audio output",
|
||||
"null",
|
||||
"Tobias Diedrich <ranma+mplayer@tdiedrich.de>",
|
||||
"",
|
||||
},
|
||||
.description = "Null audio output",
|
||||
.name = "null",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
@@ -131,5 +180,15 @@ const struct ao_driver audio_out_null = {
|
||||
.get_delay = get_delay,
|
||||
.pause = pause,
|
||||
.resume = resume,
|
||||
.priv_size = sizeof(struct priv),
|
||||
.priv_defaults = &(const struct priv) {
|
||||
.bufferlen = 0.2,
|
||||
.outburst = 256,
|
||||
},
|
||||
.options = (const struct m_option[]) {
|
||||
OPT_FLAG("untimed", untimed, 0),
|
||||
OPT_FLOATRANGE("buffer", bufferlen, 0, 0, 100),
|
||||
OPT_INTRANGE("outburst", outburst, 0, 1, 100000),
|
||||
{0}
|
||||
},
|
||||
};
|
||||
|
||||
+21
-31
@@ -35,22 +35,22 @@
|
||||
#include <AL/alext.h>
|
||||
#endif
|
||||
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "ao.h"
|
||||
#include "audio/format.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "options/m_option.h"
|
||||
|
||||
#define MAX_CHANS MP_NUM_CHANNELS
|
||||
#define NUM_BUF 128
|
||||
#define CHUNK_SIZE 512
|
||||
#define CHUNK_SAMPLES (CHUNK_SIZE / 2)
|
||||
static ALuint buffers[MAX_CHANS][NUM_BUF];
|
||||
static ALuint sources[MAX_CHANS];
|
||||
|
||||
static int cur_buf[MAX_CHANS];
|
||||
static int unqueue_buf[MAX_CHANS];
|
||||
static int16_t *tmpbuf;
|
||||
|
||||
static struct ao *ao_data;
|
||||
|
||||
@@ -79,18 +79,18 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
|
||||
static int validate_device_opt(const m_option_t *opt, struct bstr name,
|
||||
struct bstr param)
|
||||
static int validate_device_opt(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param)
|
||||
{
|
||||
if (bstr_equals0(param, "help")) {
|
||||
if (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") != AL_TRUE) {
|
||||
mp_msg(MSGT_AO, MSGL_FATAL, "Device listing not supported.\n");
|
||||
mp_fatal(log, "Device listing not supported.\n");
|
||||
return M_OPT_EXIT;
|
||||
}
|
||||
const char *list = alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER);
|
||||
mp_msg(MSGT_AO, MSGL_INFO, "OpenAL devices:\n");
|
||||
mp_info(log, "OpenAL devices:\n");
|
||||
while (list && *list) {
|
||||
mp_msg(MSGT_AO, MSGL_INFO, " '%s'\n", list);
|
||||
mp_info(log, " '%s'\n", list);
|
||||
list = list + strlen(list) + 1;
|
||||
}
|
||||
return M_OPT_EXIT - 1;
|
||||
@@ -169,8 +169,7 @@ static int init(struct ao *ao)
|
||||
alcGetIntegerv(dev, ALC_FREQUENCY, 1, &freq);
|
||||
if (alcGetError(dev) == ALC_NO_ERROR && freq)
|
||||
ao->samplerate = freq;
|
||||
ao->format = AF_FORMAT_S16_NE;
|
||||
tmpbuf = malloc(CHUNK_SIZE);
|
||||
ao->format = AF_FORMAT_S16P;
|
||||
return 0;
|
||||
|
||||
err_out:
|
||||
@@ -182,7 +181,6 @@ static void uninit(struct ao *ao, bool immed)
|
||||
{
|
||||
ALCcontext *ctx = alcGetCurrentContext();
|
||||
ALCdevice *dev = alcGetContextsDevice(ctx);
|
||||
free(tmpbuf);
|
||||
if (!immed) {
|
||||
ALint state;
|
||||
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
|
||||
@@ -251,34 +249,30 @@ static int get_space(struct ao *ao)
|
||||
queued = NUM_BUF - queued - 3;
|
||||
if (queued < 0)
|
||||
return 0;
|
||||
return queued * CHUNK_SIZE * ao->channels.num;
|
||||
return queued * CHUNK_SAMPLES;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief write data into buffer and reset underrun flag
|
||||
*/
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
ALint state;
|
||||
int i, j, k;
|
||||
int ch;
|
||||
int16_t *d = data;
|
||||
len /= ao->channels.num * CHUNK_SIZE;
|
||||
for (i = 0; i < len; i++) {
|
||||
for (ch = 0; ch < ao->channels.num; ch++) {
|
||||
for (j = 0, k = ch; j < CHUNK_SIZE / 2; j++, k += ao->channels.num)
|
||||
tmpbuf[j] = d[k];
|
||||
alBufferData(buffers[ch][cur_buf[ch]], AL_FORMAT_MONO16, tmpbuf,
|
||||
int num = samples / CHUNK_SAMPLES;
|
||||
for (int i = 0; i < num; i++) {
|
||||
for (int ch = 0; ch < ao->channels.num; ch++) {
|
||||
int16_t *d = data[ch];
|
||||
d += i * CHUNK_SAMPLES;
|
||||
alBufferData(buffers[ch][cur_buf[ch]], AL_FORMAT_MONO16, d,
|
||||
CHUNK_SIZE, ao->samplerate);
|
||||
alSourceQueueBuffers(sources[ch], 1, &buffers[ch][cur_buf[ch]]);
|
||||
cur_buf[ch] = (cur_buf[ch] + 1) % NUM_BUF;
|
||||
}
|
||||
d += ao->channels.num * CHUNK_SIZE / 2;
|
||||
}
|
||||
alGetSourcei(sources[0], AL_SOURCE_STATE, &state);
|
||||
if (state != AL_PLAYING) // checked here in case of an underrun
|
||||
alSourcePlayv(ao->channels.num, sources);
|
||||
return len * ao->channels.num * CHUNK_SIZE;
|
||||
return num * CHUNK_SAMPLES;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
@@ -286,18 +280,14 @@ static float get_delay(struct ao *ao)
|
||||
ALint queued;
|
||||
unqueue_buffers();
|
||||
alGetSourcei(sources[0], AL_BUFFERS_QUEUED, &queued);
|
||||
return queued * CHUNK_SIZE / 2 / (float)ao->samplerate;
|
||||
return queued * CHUNK_SAMPLES / (float)ao->samplerate;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_openal = {
|
||||
.info = &(const struct ao_info) {
|
||||
"OpenAL audio output",
|
||||
"openal",
|
||||
"Reimar Döffinger <Reimar.Doeffinger@stud.uni-karlsruhe.de>",
|
||||
""
|
||||
},
|
||||
.description = "OpenAL audio output",
|
||||
.name = "openal",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
+87
-44
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* Original author: A'rpi
|
||||
* Support for >2 output channels added 2001-11-25
|
||||
* - Steve Davies <steve@daviesfam.org>
|
||||
*
|
||||
@@ -34,13 +35,13 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "mpvcore/options.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "options/options.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#ifdef HAVE_SYS_SOUNDCARD_H
|
||||
#if HAVE_SYS_SOUNDCARD_H
|
||||
#include <sys/soundcard.h>
|
||||
#else
|
||||
#ifdef HAVE_SOUNDCARD_H
|
||||
#if HAVE_SOUNDCARD_H
|
||||
#include <soundcard.h>
|
||||
#endif
|
||||
#endif
|
||||
@@ -65,6 +66,19 @@ struct priv {
|
||||
|
||||
static const char *mixer_channels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
|
||||
|
||||
/* like alsa except for 6.1 and 7.1, from pcm/matrix_map.h */
|
||||
static const struct mp_chmap oss_layouts[MP_NUM_CHANNELS + 1] = {
|
||||
{0}, // empty
|
||||
MP_CHMAP_INIT_MONO, // mono
|
||||
MP_CHMAP2(FL, FR), // stereo
|
||||
MP_CHMAP3(FL, FR, LFE), // 2.1
|
||||
MP_CHMAP4(FL, FR, BL, BR), // 4.0
|
||||
MP_CHMAP5(FL, FR, BL, BR, FC), // 5.0
|
||||
MP_CHMAP6(FL, FR, BL, BR, FC, LFE), // 5.1
|
||||
MP_CHMAP7(FL, FR, BL, BR, FC, LFE, BC), // 6.1
|
||||
MP_CHMAP8(FL, FR, BL, BR, FC, LFE, SL, SR), // 7.1
|
||||
};
|
||||
|
||||
static int format_table[][2] = {
|
||||
{AFMT_U8, AF_FORMAT_U8},
|
||||
{AFMT_S8, AF_FORMAT_S8},
|
||||
@@ -75,6 +89,18 @@ static int format_table[][2] = {
|
||||
#ifdef AFMT_S24_PACKED
|
||||
{AFMT_S24_PACKED, AF_FORMAT_S24_LE},
|
||||
#endif
|
||||
#ifdef AFMT_U24_LE
|
||||
{AFMT_U24_LE, AF_FORMAT_U24_LE},
|
||||
#endif
|
||||
#ifdef AFMT_U24_BE
|
||||
{AFMT_U24_BE, AF_FORMAT_U24_BE},
|
||||
#endif
|
||||
#ifdef AFMT_S24_LE
|
||||
{AFMT_S24_LE, AF_FORMAT_S24_LE},
|
||||
#endif
|
||||
#ifdef AFMT_S24_BE
|
||||
{AFMT_S24_BE, AF_FORMAT_S24_BE},
|
||||
#endif
|
||||
#ifdef AFMT_U32_LE
|
||||
{AFMT_U32_LE, AF_FORMAT_U32_LE},
|
||||
#endif
|
||||
@@ -88,14 +114,14 @@ static int format_table[][2] = {
|
||||
{AFMT_S32_BE, AF_FORMAT_S32_BE},
|
||||
#endif
|
||||
#ifdef AFMT_FLOAT
|
||||
{AFMT_FLOAT, AF_FORMAT_FLOAT_NE},
|
||||
{AFMT_FLOAT, AF_FORMAT_FLOAT},
|
||||
#endif
|
||||
// SPECIALS
|
||||
#ifdef AFMT_MPEG
|
||||
{AFMT_MPEG, AF_FORMAT_MPEG2},
|
||||
#endif
|
||||
#ifdef AFMT_AC3
|
||||
{AFMT_AC3, AF_FORMAT_AC3_NE},
|
||||
{AFMT_AC3, AF_FORMAT_AC3},
|
||||
#endif
|
||||
{-1, -1}
|
||||
};
|
||||
@@ -195,13 +221,14 @@ static int init(struct ao *ao)
|
||||
struct priv *p = ao->priv;
|
||||
int oss_format;
|
||||
|
||||
#ifdef SNDCTL_DSP_GETPLAYVOL
|
||||
ao->no_persistent_volume = true;
|
||||
#endif
|
||||
|
||||
const char *mchan = NULL;
|
||||
if (p->cfg_oss_mixer_channel && p->cfg_oss_mixer_channel[0])
|
||||
mchan = p->cfg_oss_mixer_channel;
|
||||
|
||||
MP_VERBOSE(ao, "%d Hz %d chans %s\n", ao->samplerate,
|
||||
ao->channels.num, af_fmt2str_short(ao->format));
|
||||
|
||||
if (mchan) {
|
||||
int fd, devs, i;
|
||||
|
||||
@@ -259,31 +286,49 @@ static int init(struct ao *ao)
|
||||
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
if (AF_FORMAT_IS_AC3(ao->format)) {
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
||||
}
|
||||
|
||||
ac3_retry:
|
||||
if (AF_FORMAT_IS_AC3(ao->format))
|
||||
ao->format = AF_FORMAT_AC3_NE;
|
||||
ao->format = AF_FORMAT_AC3;
|
||||
oss_format = format2oss(ao->format);
|
||||
if (oss_format == -1) {
|
||||
MP_VERBOSE(ao, "Unknown/not supported internal format: %s\n",
|
||||
af_fmt2str_short(ao->format));
|
||||
af_fmt_to_str(ao->format));
|
||||
#if defined(AFMT_S32_LE) && defined(AFMT_S32_BE)
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
oss_format = AFMT_S32_BE;
|
||||
#else
|
||||
oss_format = AFMT_S32_LE;
|
||||
#endif
|
||||
ao->format = AF_FORMAT_S32;
|
||||
#elif defined(AFMT_S24_LE) && defined(AFMT_S24_BE)
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
oss_format = AFMT_S24_BE;
|
||||
#else
|
||||
oss_format = AFMT_S24_LE;
|
||||
#endif
|
||||
ao->format = AF_FORMAT_S24;
|
||||
#else
|
||||
#if BYTE_ORDER == BIG_ENDIAN
|
||||
oss_format = AFMT_S16_BE;
|
||||
#else
|
||||
oss_format = AFMT_S16_LE;
|
||||
#endif
|
||||
ao->format = AF_FORMAT_S16_NE;
|
||||
ao->format = AF_FORMAT_S16;
|
||||
#endif
|
||||
}
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &oss_format) < 0 ||
|
||||
oss_format != format2oss(ao->format))
|
||||
{
|
||||
MP_WARN(ao, "Can't set audio device %s to %s output, trying %s...\n",
|
||||
p->dsp, af_fmt2str_short(ao->format),
|
||||
af_fmt2str_short(AF_FORMAT_S16_NE));
|
||||
ao->format = AF_FORMAT_S16_NE;
|
||||
p->dsp, af_fmt_to_str(ao->format),
|
||||
af_fmt_to_str(AF_FORMAT_S16));
|
||||
ao->format = AF_FORMAT_S16;
|
||||
goto ac3_retry;
|
||||
}
|
||||
|
||||
@@ -293,11 +338,12 @@ ac3_retry:
|
||||
return -1;
|
||||
}
|
||||
|
||||
MP_VERBOSE(ao, "sample format: %s\n", af_fmt2str_short(ao->format));
|
||||
MP_VERBOSE(ao, "sample format: %s\n", af_fmt_to_str(ao->format));
|
||||
|
||||
if (!AF_FORMAT_IS_AC3(ao->format)) {
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_alsa_def(&sel);
|
||||
for (int n = 0; n < MP_NUM_CHANNELS + 1; n++)
|
||||
mp_chmap_sel_add_map(&sel, &oss_layouts[n]);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
return -1;
|
||||
int reqchannels = ao->channels.num;
|
||||
@@ -348,7 +394,7 @@ ac3_retry:
|
||||
// Measuring buffer size:
|
||||
void *data;
|
||||
p->buffersize = 0;
|
||||
#ifdef HAVE_AUDIO_SELECT
|
||||
#if HAVE_AUDIO_SELECT
|
||||
data = malloc(p->outburst);
|
||||
memset(data, 0, p->outburst);
|
||||
while (p->buffersize < 0x40000) {
|
||||
@@ -366,7 +412,7 @@ ac3_retry:
|
||||
free(data);
|
||||
if (p->buffersize == 0) {
|
||||
MP_ERR(ao, "*** Your audio driver DOES NOT support select() ***\n");
|
||||
MP_ERR(ao, "Recompile mpv with #undef HAVE_AUDIO_SELECT in config.h!\n");
|
||||
MP_ERR(ao, "Recompile mpv with #define HAVE_AUDIO_SELECT 0 in config.h!\n");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
@@ -398,21 +444,23 @@ static void uninit(struct ao *ao, bool immed)
|
||||
p->audio_fd = -1;
|
||||
}
|
||||
|
||||
#ifndef SNDCTL_DSP_RESET
|
||||
static void close_device(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
#ifdef SNDCTL_DSP_RESET
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
||||
#endif
|
||||
close(p->audio_fd);
|
||||
p->audio_fd = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// stop playing and empty buffers (for seeking/pause)
|
||||
static void reset(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int oss_format;
|
||||
#ifdef SNDCTL_DSP_RESET
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
||||
#else
|
||||
close_device(ao);
|
||||
p->audio_fd = open(p->dsp, O_WRONLY);
|
||||
if (p->audio_fd < 0) {
|
||||
@@ -423,6 +471,7 @@ static void reset(struct ao *ao)
|
||||
|
||||
#if defined(FD_CLOEXEC) && defined(F_SETFD)
|
||||
fcntl(p->audio_fd, F_SETFD, FD_CLOEXEC);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
oss_format = format2oss(ao->format);
|
||||
@@ -430,10 +479,11 @@ static void reset(struct ao *ao)
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SETFMT, &oss_format);
|
||||
if (!AF_FORMAT_IS_AC3(ao->format)) {
|
||||
int c = ao->channels.num;
|
||||
if (ao->channels.num > 2)
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &ao->channels.num);
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_CHANNELS, &c);
|
||||
else {
|
||||
int c = ao->channels.num - 1;
|
||||
c--;
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_STEREO, &c);
|
||||
}
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_SPEED, &ao->samplerate);
|
||||
@@ -450,12 +500,12 @@ static int get_space(struct ao *ao)
|
||||
if (ioctl(p->audio_fd, SNDCTL_DSP_GETOSPACE, &p->zz) != -1) {
|
||||
// calculate exact buffer space:
|
||||
playsize = p->zz.fragments * p->zz.fragsize;
|
||||
return playsize;
|
||||
return playsize / ao->sstride;
|
||||
}
|
||||
#endif
|
||||
|
||||
// check buffer
|
||||
#ifdef HAVE_AUDIO_SELECT
|
||||
#if HAVE_AUDIO_SELECT
|
||||
{
|
||||
fd_set rfds;
|
||||
struct timeval tv;
|
||||
@@ -468,14 +518,14 @@ static int get_space(struct ao *ao)
|
||||
}
|
||||
#endif
|
||||
|
||||
return p->outburst;
|
||||
return p->outburst / ao->sstride;
|
||||
}
|
||||
|
||||
// stop playing, keep buffers (for pause)
|
||||
static void audio_pause(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
p->prepause_space = get_space(ao);
|
||||
p->prepause_space = get_space(ao) * ao->sstride;
|
||||
#ifdef SNDCTL_DSP_RESET
|
||||
ioctl(p->audio_fd, SNDCTL_DSP_RESET, NULL);
|
||||
#else
|
||||
@@ -486,33 +536,30 @@ static void audio_pause(struct ao *ao)
|
||||
// plays 'len' bytes of 'data'
|
||||
// it should round it down to outburst*n
|
||||
// return: number of bytes played
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int len = samples * ao->sstride;
|
||||
if (len == 0)
|
||||
return len;
|
||||
if (len > p->outburst || !(flags & AOPLAY_FINAL_CHUNK)) {
|
||||
len /= p->outburst;
|
||||
len *= p->outburst;
|
||||
}
|
||||
len = write(p->audio_fd, data, len);
|
||||
return len;
|
||||
len = write(p->audio_fd, data[0], len);
|
||||
return len / ao->sstride;
|
||||
}
|
||||
|
||||
// resume playing, after audio_pause()
|
||||
static void audio_resume(struct ao *ao)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int fillcnt;
|
||||
#ifndef SNDCTL_DSP_RESET
|
||||
reset(ao);
|
||||
#endif
|
||||
fillcnt = get_space(ao) - p->prepause_space;
|
||||
if (fillcnt > 0 && !(ao->format & AF_FORMAT_SPECIAL_MASK)) {
|
||||
void *silence = calloc(fillcnt, 1);
|
||||
play(ao, silence, fillcnt, 0);
|
||||
free(silence);
|
||||
}
|
||||
int fillframes = get_space(ao) - p->prepause_space / ao->sstride;
|
||||
if (fillframes > 0)
|
||||
ao_play_silence(ao, fillframes);
|
||||
}
|
||||
|
||||
// return: delay in seconds between first and last sample in buffer
|
||||
@@ -542,12 +589,8 @@ static float get_delay(struct ao *ao)
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_oss = {
|
||||
.info = &(const struct ao_info) {
|
||||
"OSS/ioctl audio output",
|
||||
"oss",
|
||||
"A'rpi",
|
||||
""
|
||||
},
|
||||
.description = "OSS/ioctl audio output",
|
||||
.name = "oss",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
+14
-13
@@ -3,6 +3,8 @@
|
||||
*
|
||||
* This file is part of MPlayer.
|
||||
*
|
||||
* Original author: Atmosfear
|
||||
*
|
||||
* 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
|
||||
@@ -28,11 +30,11 @@
|
||||
|
||||
#include "talloc.h"
|
||||
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "options/m_option.h"
|
||||
#include "audio/format.h"
|
||||
#include "audio/reorder_ch.h"
|
||||
#include "ao.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#ifdef __MINGW32__
|
||||
// for GetFileType to detect pipes
|
||||
@@ -116,6 +118,9 @@ static int init(struct ao *ao)
|
||||
if (!priv->outputfilename)
|
||||
priv->outputfilename =
|
||||
talloc_strdup(priv, priv->waveheader ? "audiodump.wav" : "audiodump.pcm");
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
if (priv->waveheader) {
|
||||
// WAV files must have one of the following formats
|
||||
|
||||
@@ -125,7 +130,6 @@ static int init(struct ao *ao)
|
||||
case AF_FORMAT_S24_LE:
|
||||
case AF_FORMAT_S32_LE:
|
||||
case AF_FORMAT_FLOAT_LE:
|
||||
case AF_FORMAT_AC3_BE:
|
||||
case AF_FORMAT_AC3_LE:
|
||||
break;
|
||||
default:
|
||||
@@ -144,7 +148,7 @@ static int init(struct ao *ao)
|
||||
MP_INFO(ao, "File: %s (%s)\nPCM: Samplerate: %d Hz Channels: %d Format: %s\n",
|
||||
priv->outputfilename,
|
||||
priv->waveheader ? "WAVE" : "RAW PCM", ao->samplerate,
|
||||
ao->channels.num, af_fmt2str_short(ao->format));
|
||||
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");
|
||||
|
||||
@@ -192,24 +196,21 @@ static int get_space(struct ao *ao)
|
||||
return 65536;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
int len = samples * ao->sstride;
|
||||
|
||||
fwrite(data, len, 1, priv->fp);
|
||||
fwrite(data[0], len, 1, priv->fp);
|
||||
priv->data_length += len;
|
||||
return len;
|
||||
return len / ao->sstride;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_pcm = {
|
||||
.info = &(const struct ao_info) {
|
||||
"RAW PCM/WAVE file writer audio output",
|
||||
"pcm",
|
||||
"Atmosfear",
|
||||
"",
|
||||
},
|
||||
.description = "RAW PCM/WAVE file writer audio output",
|
||||
.name = "pcm",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.get_space = get_space,
|
||||
|
||||
+41
-44
@@ -26,10 +26,10 @@
|
||||
#include <portaudio.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "options/m_option.h"
|
||||
#include "audio/format.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "mpvcore/mp_ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "misc/ring.h"
|
||||
#include "ao.h"
|
||||
|
||||
struct priv {
|
||||
@@ -56,30 +56,30 @@ struct format_map {
|
||||
|
||||
static const struct format_map format_maps[] = {
|
||||
// first entry is the default format
|
||||
{AF_FORMAT_S16_NE, paInt16},
|
||||
{AF_FORMAT_S24_NE, paInt24},
|
||||
{AF_FORMAT_S32_NE, paInt32},
|
||||
{AF_FORMAT_S8, paInt8},
|
||||
{AF_FORMAT_U8, paUInt8},
|
||||
{AF_FORMAT_FLOAT_NE, paFloat32},
|
||||
{AF_FORMAT_S16, paInt16},
|
||||
{AF_FORMAT_S24, paInt24},
|
||||
{AF_FORMAT_S32, paInt32},
|
||||
{AF_FORMAT_S8, paInt8},
|
||||
{AF_FORMAT_U8, paUInt8},
|
||||
{AF_FORMAT_FLOAT, paFloat32},
|
||||
{AF_FORMAT_UNKNOWN, 0}
|
||||
};
|
||||
|
||||
static bool check_pa_ret(int ret)
|
||||
static bool check_pa_ret(struct mp_log *log, int ret)
|
||||
{
|
||||
if (ret < 0) {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "[ao/portaudio] %s\n",
|
||||
Pa_GetErrorText(ret));
|
||||
mp_err(log, "%s\n", Pa_GetErrorText(ret));
|
||||
if (ret == paUnanticipatedHostError) {
|
||||
const PaHostErrorInfo* hosterr = Pa_GetLastHostErrorInfo();
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "[ao/portaudio] Host error: %s\n",
|
||||
hosterr->errorText);
|
||||
mp_err(log, "Host error: %s\n", hosterr->errorText);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#define CHECK_PA_RET(ret) check_pa_ret(ao->log, (ret))
|
||||
|
||||
static int seconds_to_bytes(struct ao *ao, double seconds)
|
||||
{
|
||||
return af_fmt_seconds_to_bytes(ao->format, seconds, ao->channels.num,
|
||||
@@ -93,23 +93,23 @@ static int to_int(const char *s, int return_on_error)
|
||||
return (s[0] && !endptr[0]) ? res : return_on_error;
|
||||
}
|
||||
|
||||
static int find_device(const char *name)
|
||||
static int find_device(struct mp_log *log, const char *name)
|
||||
{
|
||||
int found = paNoDevice;
|
||||
if (!name)
|
||||
return found;
|
||||
int help = strcmp(name, "help") == 0;
|
||||
int count = Pa_GetDeviceCount();
|
||||
check_pa_ret(count);
|
||||
check_pa_ret(log, count);
|
||||
int index = to_int(name, -1);
|
||||
if (help)
|
||||
mp_msg(MSGT_AO, MSGL_INFO, "PortAudio devices:\n");
|
||||
mp_info(log, "PortAudio devices:\n");
|
||||
for (int n = 0; n < count; n++) {
|
||||
const PaDeviceInfo* info = Pa_GetDeviceInfo(n);
|
||||
if (help) {
|
||||
if (info->maxOutputChannels < 1)
|
||||
continue;
|
||||
mp_msg(MSGT_AO, MSGL_INFO, " %d '%s', %d channels, latency: %.2f "
|
||||
mp_info(log, " %d '%s', %d channels, latency: %.2f "
|
||||
"ms, sample rate: %.0f\n", n, info->name,
|
||||
info->maxOutputChannels,
|
||||
info->defaultHighOutputLatency * 1000,
|
||||
@@ -121,21 +121,20 @@ static int find_device(const char *name)
|
||||
}
|
||||
}
|
||||
if (found == paNoDevice && !help)
|
||||
mp_msg(MSGT_AO, MSGL_WARN, "[ao/portaudio] Device '%s' not found!\n",
|
||||
name);
|
||||
mp_warn(log, "Device '%s' not found!\n", name);
|
||||
return found;
|
||||
}
|
||||
|
||||
static int validate_device_opt(const m_option_t *opt, struct bstr name,
|
||||
struct bstr param)
|
||||
static int validate_device_opt(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param)
|
||||
{
|
||||
// Note: we do not check whether the device actually exist, because this
|
||||
// might break elaborate configs with several AOs trying several
|
||||
// devices. We do it merely for making "help" special.
|
||||
if (bstr_equals0(param, "help")) {
|
||||
if (!check_pa_ret(Pa_Initialize()))
|
||||
if (!check_pa_ret(log, Pa_Initialize()))
|
||||
return M_OPT_EXIT;
|
||||
find_device("help");
|
||||
find_device(log, "help");
|
||||
Pa_Terminate();
|
||||
return M_OPT_EXIT - 1;
|
||||
}
|
||||
@@ -205,9 +204,9 @@ static void uninit(struct ao *ao, bool cut_audio)
|
||||
|
||||
pthread_mutex_unlock(&priv->ring_mutex);
|
||||
|
||||
check_pa_ret(Pa_StopStream(priv->stream));
|
||||
CHECK_PA_RET(Pa_StopStream(priv->stream));
|
||||
}
|
||||
check_pa_ret(Pa_CloseStream(priv->stream));
|
||||
CHECK_PA_RET(Pa_CloseStream(priv->stream));
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&priv->ring_mutex);
|
||||
@@ -218,14 +217,14 @@ static int init(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
if (!check_pa_ret(Pa_Initialize()))
|
||||
if (!CHECK_PA_RET(Pa_Initialize()))
|
||||
return -1;
|
||||
|
||||
pthread_mutex_init(&priv->ring_mutex, NULL);
|
||||
|
||||
int pa_device = Pa_GetDefaultOutputDevice();
|
||||
if (priv->cfg_device && priv->cfg_device[0])
|
||||
pa_device = find_device(priv->cfg_device);
|
||||
pa_device = find_device(ao->log, priv->cfg_device);
|
||||
if (pa_device == paNoDevice)
|
||||
goto error_exit;
|
||||
|
||||
@@ -242,6 +241,8 @@ static int init(struct ao *ao)
|
||||
= Pa_GetDeviceInfo(pa_device)->defaultHighOutputLatency,
|
||||
};
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
const struct format_map *fmt = format_maps;
|
||||
while (fmt->pa_format) {
|
||||
if (fmt->mp_format == ao->format) {
|
||||
@@ -262,9 +263,9 @@ static int init(struct ao *ao)
|
||||
priv->framelen = ao->channels.num * (af_fmt2bits(ao->format) / 8);
|
||||
ao->bps = ao->samplerate * priv->framelen;
|
||||
|
||||
if (!check_pa_ret(Pa_IsFormatSupported(NULL, &sp, ao->samplerate)))
|
||||
if (!CHECK_PA_RET(Pa_IsFormatSupported(NULL, &sp, ao->samplerate)))
|
||||
goto error_exit;
|
||||
if (!check_pa_ret(Pa_OpenStream(&priv->stream, NULL, &sp, ao->samplerate,
|
||||
if (!CHECK_PA_RET(Pa_OpenStream(&priv->stream, NULL, &sp, ao->samplerate,
|
||||
paFramesPerBufferUnspecified, paNoFlag,
|
||||
stream_callback, ao)))
|
||||
goto error_exit;
|
||||
@@ -278,22 +279,22 @@ error_exit:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
pthread_mutex_lock(&priv->ring_mutex);
|
||||
|
||||
int write_len = mp_ring_write(priv->ring, data, len);
|
||||
int write_len = mp_ring_write(priv->ring, data[0], samples * ao->sstride);
|
||||
if (flags & AOPLAY_FINAL_CHUNK)
|
||||
priv->play_remaining = true;
|
||||
|
||||
pthread_mutex_unlock(&priv->ring_mutex);
|
||||
|
||||
if (Pa_IsStreamStopped(priv->stream) == 1)
|
||||
check_pa_ret(Pa_StartStream(priv->stream));
|
||||
CHECK_PA_RET(Pa_StartStream(priv->stream));
|
||||
|
||||
return write_len;
|
||||
return write_len / ao->sstride;
|
||||
}
|
||||
|
||||
static int get_space(struct ao *ao)
|
||||
@@ -306,7 +307,7 @@ static int get_space(struct ao *ao)
|
||||
|
||||
pthread_mutex_unlock(&priv->ring_mutex);
|
||||
|
||||
return free;
|
||||
return free / ao->sstride;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
@@ -331,7 +332,7 @@ static void reset(struct ao *ao)
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
if (Pa_IsStreamStopped(priv->stream) != 1)
|
||||
check_pa_ret(Pa_AbortStream(priv->stream));
|
||||
CHECK_PA_RET(Pa_AbortStream(priv->stream));
|
||||
|
||||
pthread_mutex_lock(&priv->ring_mutex);
|
||||
|
||||
@@ -347,7 +348,7 @@ static void pause(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
check_pa_ret(Pa_AbortStream(priv->stream));
|
||||
CHECK_PA_RET(Pa_AbortStream(priv->stream));
|
||||
|
||||
double stream_time = Pa_GetStreamTime(priv->stream);
|
||||
|
||||
@@ -366,18 +367,14 @@ static void resume(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
|
||||
check_pa_ret(Pa_StartStream(priv->stream));
|
||||
CHECK_PA_RET(Pa_StartStream(priv->stream));
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_portaudio = {
|
||||
.info = &(const struct ao_info) {
|
||||
"PortAudio",
|
||||
"portaudio",
|
||||
"wm4",
|
||||
"",
|
||||
},
|
||||
.description = "PortAudio",
|
||||
.name = "portaudio",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
|
||||
+29
-15
@@ -28,9 +28,9 @@
|
||||
|
||||
#include "config.h"
|
||||
#include "audio/format.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "common/msg.h"
|
||||
#include "ao.h"
|
||||
#include "mpvcore/input/input.h"
|
||||
#include "input/input.h"
|
||||
|
||||
#define PULSE_CLIENT_NAME "mpv"
|
||||
|
||||
@@ -147,7 +147,6 @@ static const struct format_map {
|
||||
};
|
||||
|
||||
static const int speaker_map[][2] = {
|
||||
{PA_CHANNEL_POSITION_MONO, MP_SPEAKER_ID_FC},
|
||||
{PA_CHANNEL_POSITION_FRONT_LEFT, MP_SPEAKER_ID_FL},
|
||||
{PA_CHANNEL_POSITION_FRONT_RIGHT, MP_SPEAKER_ID_FR},
|
||||
{PA_CHANNEL_POSITION_FRONT_CENTER, MP_SPEAKER_ID_FC},
|
||||
@@ -174,6 +173,10 @@ static bool chmap_pa_from_mp(pa_channel_map *dst, struct mp_chmap *src)
|
||||
if (src->num > PA_CHANNELS_MAX)
|
||||
return false;
|
||||
dst->channels = src->num;
|
||||
if (mp_chmap_equals(src, &(const struct mp_chmap)MP_CHMAP_INIT_MONO)) {
|
||||
dst->map[0] = PA_CHANNEL_POSITION_MONO;
|
||||
return true;
|
||||
}
|
||||
for (int n = 0; n < src->num; n++) {
|
||||
int mp_speaker = src->speaker[n];
|
||||
int pa_speaker = PA_CHANNEL_POSITION_INVALID;
|
||||
@@ -283,6 +286,8 @@ static int init(struct ao *ao)
|
||||
ss.channels = ao->channels.num;
|
||||
ss.rate = ao->samplerate;
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
const struct format_map *fmt_map = format_maps;
|
||||
while (fmt_map->mp_format != ao->format) {
|
||||
if (fmt_map->mp_format == AF_FORMAT_UNKNOWN) {
|
||||
@@ -372,14 +377,14 @@ static void cork(struct ao *ao, bool pause)
|
||||
}
|
||||
|
||||
// Play the specified data to the pulseaudio server
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
if (pa_stream_write(priv->stream, data, len, NULL, 0,
|
||||
if (pa_stream_write(priv->stream, data[0], samples * ao->sstride, NULL, 0,
|
||||
PA_SEEK_RELATIVE) < 0) {
|
||||
GENERIC_ERR_MSG("pa_stream_write() failed");
|
||||
len = -1;
|
||||
samples = -1;
|
||||
}
|
||||
if (flags & AOPLAY_FINAL_CHUNK) {
|
||||
// Force start in case the stream was too short for prebuf
|
||||
@@ -387,7 +392,7 @@ static int play(struct ao *ao, void *data, int len, int flags)
|
||||
pa_operation_unref(op);
|
||||
}
|
||||
pa_threaded_mainloop_unlock(priv->mainloop);
|
||||
return len;
|
||||
return samples;
|
||||
}
|
||||
|
||||
// Reset the audio stream, i.e. flush the playback buffer on the server side
|
||||
@@ -422,14 +427,14 @@ static void resume(struct ao *ao)
|
||||
cork(ao, false);
|
||||
}
|
||||
|
||||
// Return number of bytes that may be written to the server without blocking
|
||||
// Return number of samples that may be written to the server without blocking
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
size_t space = pa_stream_writable_size(priv->stream);
|
||||
pa_threaded_mainloop_unlock(priv->mainloop);
|
||||
return space;
|
||||
return space / ao->sstride;
|
||||
}
|
||||
|
||||
// Return the current latency in seconds
|
||||
@@ -579,6 +584,19 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
pa_threaded_mainloop_unlock(priv->mainloop);
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
case AOCONTROL_UPDATE_STREAM_TITLE: {
|
||||
char *title = (char *)arg;
|
||||
pa_threaded_mainloop_lock(priv->mainloop);
|
||||
if (!waitop(priv, pa_stream_set_name(priv->stream, title,
|
||||
success_cb, ao)))
|
||||
{
|
||||
GENERIC_ERR_MSG("pa_stream_set_name() failed");
|
||||
return CONTROL_ERROR;
|
||||
}
|
||||
return CONTROL_OK;
|
||||
}
|
||||
|
||||
default:
|
||||
return CONTROL_UNKNOWN;
|
||||
}
|
||||
@@ -587,12 +605,8 @@ static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_pulse = {
|
||||
.info = &(const struct ao_info) {
|
||||
"PulseAudio audio output",
|
||||
"pulse",
|
||||
"Lennart Poettering",
|
||||
"",
|
||||
},
|
||||
.description = "PulseAudio audio output",
|
||||
.name = "pulse",
|
||||
.control = control,
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
|
||||
+10
-12
@@ -29,15 +29,15 @@
|
||||
|
||||
#include "talloc.h"
|
||||
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/timer.h"
|
||||
#include "audio/format.h"
|
||||
#include "ao.h"
|
||||
|
||||
struct priv {
|
||||
rsound_t *rd;
|
||||
const char *host;
|
||||
const char *port;
|
||||
char *host;
|
||||
char *port;
|
||||
};
|
||||
|
||||
static int set_format(struct ao *ao)
|
||||
@@ -114,6 +114,8 @@ static int init(struct ao *ao)
|
||||
rsd_set_param(priv->rd, RSD_SAMPLERATE, &ao->samplerate);
|
||||
rsd_set_param(priv->rd, RSD_CHANNELS, &ao->channels.num);
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
int rsd_format = set_format(ao);
|
||||
rsd_set_param(priv->rd, RSD_FORMAT, &rsd_format);
|
||||
|
||||
@@ -161,13 +163,13 @@ static void audio_resume(struct ao *ao)
|
||||
static int get_space(struct ao *ao)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
return rsd_get_avail(priv->rd);
|
||||
return rsd_get_avail(priv->rd) / ao->sstride;
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
return rsd_write(priv->rd, data, len);
|
||||
return rsd_write(priv->rd, data[0], samples * ao->sstride) / ao->sstride;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
@@ -179,12 +181,8 @@ static float get_delay(struct ao *ao)
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_rsound = {
|
||||
.info = &(const struct ao_info) {
|
||||
.name = "RSound output driver",
|
||||
.short_name = "rsound",
|
||||
.author = "Hans-Kristian Arntzen",
|
||||
.comment = "",
|
||||
},
|
||||
.description = "RSound output driver",
|
||||
.name = "rsound",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.reset = reset,
|
||||
|
||||
+11
-12
@@ -23,8 +23,8 @@
|
||||
#include "audio/format.h"
|
||||
#include "talloc.h"
|
||||
#include "ao.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "common/msg.h"
|
||||
#include "options/m_option.h"
|
||||
#include "osdep/timer.h"
|
||||
|
||||
#include <libavutil/fifo.h>
|
||||
@@ -144,6 +144,8 @@ static int init(struct ao *ao)
|
||||
return -1;
|
||||
}
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
SDL_AudioSpec desired, obtained;
|
||||
|
||||
switch (ao->format) {
|
||||
@@ -259,7 +261,7 @@ static int get_space(struct ao *ao)
|
||||
SDL_LockMutex(priv->buffer_mutex);
|
||||
int space = av_fifo_space(priv->buffer);
|
||||
SDL_UnlockMutex(priv->buffer_mutex);
|
||||
return space;
|
||||
return space / ao->sstride;
|
||||
}
|
||||
|
||||
static void pause(struct ao *ao)
|
||||
@@ -290,20 +292,21 @@ static void resume(struct ao *ao)
|
||||
do_resume(ao);
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *priv = ao->priv;
|
||||
int len = samples * ao->sstride;
|
||||
SDL_LockMutex(priv->buffer_mutex);
|
||||
int free = av_fifo_space(priv->buffer);
|
||||
if (len > free) len = free;
|
||||
av_fifo_generic_write(priv->buffer, data, len, NULL);
|
||||
av_fifo_generic_write(priv->buffer, data[0], len, NULL);
|
||||
SDL_CondSignal(priv->underrun_cond);
|
||||
SDL_UnlockMutex(priv->buffer_mutex);
|
||||
if (priv->unpause) {
|
||||
priv->unpause = 0;
|
||||
do_resume(ao);
|
||||
}
|
||||
return len;
|
||||
return len / ao->sstride;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
@@ -347,12 +350,8 @@ static float get_delay(struct ao *ao)
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_sdl = {
|
||||
.info = &(const struct ao_info) {
|
||||
"SDL Audio",
|
||||
"sdl",
|
||||
"Rudolf Polzer <divVerent@xonotic.org>",
|
||||
""
|
||||
},
|
||||
.description = "SDL Audio",
|
||||
.name = "sdl",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.get_space = get_space,
|
||||
|
||||
+11
-12
@@ -22,8 +22,8 @@
|
||||
#include <errno.h>
|
||||
#include <sndio.h>
|
||||
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "options/m_option.h"
|
||||
#include "common/msg.h"
|
||||
|
||||
#include "audio/format.h"
|
||||
#include "ao.h"
|
||||
@@ -130,6 +130,9 @@ static int init(struct ao *ao)
|
||||
MP_ERR(ao, "can't open sndio %s\n", p->dev);
|
||||
goto error;
|
||||
}
|
||||
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
|
||||
sio_initpar(&p->par);
|
||||
for (i = 0, ap = af_to_par;; i++, ap++) {
|
||||
if (i == sizeof(af_to_par) / sizeof(struct af_to_par)) {
|
||||
@@ -239,16 +242,16 @@ static void reset(struct ao *ao)
|
||||
/*
|
||||
* play given number of bytes until sio_write() blocks
|
||||
*/
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
struct priv *p = ao->priv;
|
||||
int n;
|
||||
|
||||
n = sio_write(p->hdl, data, len);
|
||||
n = sio_write(p->hdl, data[0], samples * ao->sstride);
|
||||
p->delay += n;
|
||||
if (flags & AOPLAY_FINAL_CHUNK)
|
||||
reset(ao);
|
||||
return n;
|
||||
return n / ao->sstride;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -268,7 +271,7 @@ static int get_space(struct ao *ao)
|
||||
; /* nothing */
|
||||
sio_revents(p->hdl, p->pfd);
|
||||
|
||||
return p->par.bufsz * p->par.pchan * p->par.bps - p->delay;
|
||||
return (p->par.bufsz * p->par.pchan * p->par.bps - p->delay) / ao->sstride;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -317,12 +320,8 @@ static void audio_resume(struct ao *ao)
|
||||
#define OPT_BASE_STRUCT struct priv
|
||||
|
||||
const struct ao_driver audio_out_sndio = {
|
||||
.info = &(const struct ao_info) {
|
||||
"sndio audio output",
|
||||
"sndio",
|
||||
"Alexandre Ratchov <alex@caoua.org>, Christian Neukirchen <chneukirchen@gmail.com>",
|
||||
"under development"
|
||||
},
|
||||
.description = "sndio audio output",
|
||||
.name = "sndio",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
+92
-138
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
* This file is part of mpv.
|
||||
*
|
||||
* Original author: Jonathan Yong <10walls@gmail.com>
|
||||
*
|
||||
* mpv is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
@@ -27,18 +29,13 @@
|
||||
#include <mmdeviceapi.h>
|
||||
#include <avrt.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "mpvcore/m_option.h"
|
||||
#include "mpvcore/m_config.h"
|
||||
#include "options/m_option.h"
|
||||
#include "options/m_config.h"
|
||||
#include "audio/format.h"
|
||||
#include "mpvcore/mp_msg.h"
|
||||
#include "mpvcore/mp_ring.h"
|
||||
#include "common/msg.h"
|
||||
#include "misc/ring.h"
|
||||
#include "ao.h"
|
||||
|
||||
#ifndef BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE
|
||||
#define BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE 0x00000001
|
||||
#endif
|
||||
|
||||
#ifndef PKEY_Device_FriendlyName
|
||||
DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName,
|
||||
0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20,
|
||||
@@ -48,7 +45,7 @@ DEFINE_PROPERTYKEY(PKEY_Device_DeviceDesc,
|
||||
0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2);
|
||||
#endif
|
||||
|
||||
#define RING_BUFFER_COUNT 64
|
||||
#define RING_BUFFER_COUNT 8
|
||||
|
||||
/* 20 millisecond buffer? */
|
||||
#define BUFFER_TIME 20000000.0
|
||||
@@ -71,6 +68,7 @@ union WAVEFMT {
|
||||
};
|
||||
|
||||
typedef struct wasapi_state {
|
||||
struct mp_log *log;
|
||||
HANDLE threadLoop;
|
||||
|
||||
/* Init phase */
|
||||
@@ -85,6 +83,7 @@ typedef struct wasapi_state {
|
||||
/* Play */
|
||||
HANDLE hPlay;
|
||||
int is_playing;
|
||||
int final_chunk;
|
||||
|
||||
/* Reset */
|
||||
HANDLE hReset;
|
||||
@@ -98,9 +97,6 @@ typedef struct wasapi_state {
|
||||
DWORD vol_hw_support, status;
|
||||
float audio_volume;
|
||||
|
||||
/* Prints, for in case line buffers are disabled */
|
||||
CRITICAL_SECTION print_lock;
|
||||
|
||||
/* Buffers */
|
||||
struct mp_ring *ringbuff;
|
||||
size_t buffer_block_size; /* Size of each block in bytes */
|
||||
@@ -136,17 +132,6 @@ static int fill_VistaBlob(wasapi_state *state)
|
||||
{
|
||||
if (!state)
|
||||
return 1;
|
||||
HMODULE hkernel32 = GetModuleHandleW(L"kernel32.dll");
|
||||
if (!hkernel32)
|
||||
return 1;
|
||||
WINBOOL (WINAPI *pSetDllDirectory)(LPCWSTR lpPathName) =
|
||||
(WINBOOL (WINAPI *)(LPCWSTR))GetProcAddress(hkernel32, "SetDllDirectoryW");
|
||||
WINBOOL (WINAPI *pSetSearchPathMode)(DWORD Flags) =
|
||||
(WINBOOL (WINAPI *)(DWORD))GetProcAddress(hkernel32, "SetSearchPathMode");
|
||||
if (pSetSearchPathMode)
|
||||
pSetDllDirectory(L""); /* Attempt to use safe search paths */
|
||||
if (pSetSearchPathMode)
|
||||
pSetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE);
|
||||
state->VistaBlob.hAvrt = LoadLibraryW(L"avrt.dll");
|
||||
if (!state->VistaBlob.hAvrt)
|
||||
goto exit_label;
|
||||
@@ -160,8 +145,6 @@ static int fill_VistaBlob(wasapi_state *state)
|
||||
exit_label:
|
||||
if (state->VistaBlob.hAvrt)
|
||||
FreeLibrary(state->VistaBlob.hAvrt);
|
||||
if (pSetSearchPathMode)
|
||||
pSetDllDirectory(NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -294,8 +277,8 @@ static int set_ao_format(struct wasapi_state *state,
|
||||
wformat.Format.wBitsPerSample, wformat.SubFormat.Data1 == 3);
|
||||
|
||||
if (wformat.SubFormat.Data1 != 1 && wformat.SubFormat.Data1 != 3) {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: unknown SubFormat %"PRIu32"\n",
|
||||
(uint32_t)wformat.SubFormat.Data1);
|
||||
MP_ERR(ao, "unknown SubFormat %"PRIu32"\n",
|
||||
(uint32_t)wformat.SubFormat.Data1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -321,10 +304,8 @@ static int try_format(struct wasapi_state *state,
|
||||
|
||||
int af_format = format_set_bits(ao->format, bits, bits == 32);
|
||||
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: trying %dch %s @ %dhz\n",
|
||||
channels.num, af_fmt2str_short(af_format), samplerate);
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_VERBOSE(ao, "trying %dch %s @ %dhz\n",
|
||||
channels.num, af_fmt_to_str(af_format), samplerate);
|
||||
|
||||
union WAVEFMT u;
|
||||
u.extensible = &wformat;
|
||||
@@ -347,20 +328,16 @@ static int try_format(struct wasapi_state *state,
|
||||
|
||||
if (hr == S_FALSE) {
|
||||
if (set_ao_format(state, ao, wformat)) {
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: accepted as %dch %s @ %dhz\n",
|
||||
ao->channels.num, af_fmt2str_short(ao->format), ao->samplerate);
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_VERBOSE(ao, "accepted as %dch %s @ %dhz\n",
|
||||
ao->channels.num, af_fmt_to_str(ao->format), ao->samplerate);
|
||||
|
||||
return 1;
|
||||
}
|
||||
} if (hr == S_OK || (!state->opt_exclusive && hr == AUDCLNT_E_UNSUPPORTED_FORMAT)) {
|
||||
// AUDCLNT_E_UNSUPPORTED_FORMAT here means "works in shared, doesn't in exclusive"
|
||||
if (set_ao_format(state, ao, wformat)) {
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: %dch %s @ %dhz accepted\n",
|
||||
ao->channels.num, af_fmt2str_short(af_format), samplerate);
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_VERBOSE(ao, "%dch %s @ %dhz accepted\n",
|
||||
ao->channels.num, af_fmt_to_str(af_format), samplerate);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -414,10 +391,8 @@ static int try_passthrough(struct wasapi_state *state,
|
||||
union WAVEFMT u;
|
||||
u.extensible = &wformat;
|
||||
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: trying passthrough for %s...\n",
|
||||
af_fmt2str_short((ao->format&~AF_FORMAT_END_MASK) | AF_FORMAT_LE));
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_VERBOSE(ao, "trying passthrough for %s...\n",
|
||||
af_fmt_to_str((ao->format&~AF_FORMAT_END_MASK) | AF_FORMAT_LE));
|
||||
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(state->pAudioClient,
|
||||
state->share_mode,
|
||||
@@ -438,12 +413,10 @@ static int find_formats(struct ao *const ao)
|
||||
if (try_passthrough(state, ao))
|
||||
return 0;
|
||||
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: couldn't use passthrough!");
|
||||
MP_ERR(ao, "couldn't use passthrough!");
|
||||
if (!state->opt_exclusive)
|
||||
mp_msg(MSGT_AO, MSGL_ERR, " (try exclusive mode)");
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "\n");
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_ERR(ao, " (try exclusive mode)");
|
||||
MP_ERR(ao, "\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -462,9 +435,7 @@ static int find_formats(struct ao *const ao)
|
||||
return 0;
|
||||
}
|
||||
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_WARN, "ao-wasapi: couldn't use default mix format!\n");
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_WARN(ao, "couldn't use default mix format!\n");
|
||||
}
|
||||
|
||||
/* Exclusive mode, we have to guess. */
|
||||
@@ -535,9 +506,7 @@ static int find_formats(struct ao *const ao)
|
||||
bits = start_bits;
|
||||
mp_chmap_from_channels(&ao->channels, 2);
|
||||
} else {
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: couldn't find acceptable audio format!\n");
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_ERR(ao, "couldn't find acceptable audio format!\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
@@ -564,12 +533,8 @@ reinit:
|
||||
NULL);
|
||||
/* something about buffer sizes on Win7, fixme might loop forever */
|
||||
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(
|
||||
MSGT_AO, MSGL_V,
|
||||
"ao-wasapi: IAudioClient::Initialize negotiation failed with %s, used %lld * 100ns\n",
|
||||
explain_err(hr), state->defaultRequestedDuration);
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_VERBOSE(state, "IAudioClient::Initialize negotiation failed with %s, used %lld * 100ns\n",
|
||||
explain_err(hr), state->defaultRequestedDuration);
|
||||
if (offset > 10.0)
|
||||
goto exit_label; /* is 10 enough to break out of the loop?*/
|
||||
IAudioClient_GetBufferSize(state->pAudioClient, &state->bufferFrameCount);
|
||||
@@ -601,18 +566,12 @@ reinit:
|
||||
state->bufferFrameCount;
|
||||
state->hTask =
|
||||
state->VistaBlob.pAvSetMmThreadCharacteristicsW(L"Pro Audio", &state->taskIndex);
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_V,
|
||||
"ao-wasapi: fix_format OK, using %lld byte buffer block size!\n",
|
||||
(long long) state->buffer_block_size);
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_VERBOSE(state, "fix_format OK, using %lld byte buffer block size!\n",
|
||||
(long long) state->buffer_block_size);
|
||||
return 0;
|
||||
exit_label:
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_ERR,
|
||||
"ao-wasapi: fix_format fails with %s, failed to determine buffer block size!\n",
|
||||
MP_ERR(state, "fix_format fails with %s, failed to determine buffer block size!\n",
|
||||
explain_err(hr));
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
SetEvent(state->fatal_error);
|
||||
return 1;
|
||||
}
|
||||
@@ -721,7 +680,9 @@ end:
|
||||
return found;
|
||||
}
|
||||
|
||||
static HRESULT enumerate_with_state(char *header, int status, int with_id) {
|
||||
static HRESULT enumerate_with_state(struct mp_log *log, char *header,
|
||||
int status, int with_id)
|
||||
{
|
||||
HRESULT hr;
|
||||
IMMDeviceEnumerator *pEnumerator = NULL;
|
||||
IMMDeviceCollection *pDevices = NULL;
|
||||
@@ -748,7 +709,7 @@ static HRESULT enumerate_with_state(char *header, int status, int with_id) {
|
||||
int count;
|
||||
IMMDeviceCollection_GetCount(pDevices, &count);
|
||||
if (count > 0) {
|
||||
mp_msg(MSGT_AO, MSGL_INFO, "ao-wasapi: %s\n", header);
|
||||
mp_info(log, "%s\n", header);
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
@@ -763,11 +724,9 @@ static HRESULT enumerate_with_state(char *header, int status, int with_id) {
|
||||
mark = " (default)";
|
||||
|
||||
if (with_id) {
|
||||
mp_msg(MSGT_AO, MSGL_INFO, "ao-wasapi: Device #%d: %s, ID: %s%s\n",
|
||||
i, name, id, mark);
|
||||
mp_info(log, "Device #%d: %s, ID: %s%s\n", i, name, id, mark);
|
||||
} else {
|
||||
mp_msg(MSGT_AO, MSGL_INFO, "ao-wasapi: %s, ID: %s%s\n",
|
||||
name, id, mark);
|
||||
mp_info(log, "%s, ID: %s%s\n", name, id, mark);
|
||||
}
|
||||
|
||||
free(name);
|
||||
@@ -787,24 +746,27 @@ exit_label:
|
||||
return hr;
|
||||
}
|
||||
|
||||
static int enumerate_devices(void) {
|
||||
static int enumerate_devices(struct mp_log *log)
|
||||
{
|
||||
HRESULT hr;
|
||||
CoInitialize(NULL);
|
||||
|
||||
hr = enumerate_with_state("Active devices:", DEVICE_STATE_ACTIVE, 1);
|
||||
hr = enumerate_with_state(log, "Active devices:", DEVICE_STATE_ACTIVE, 1);
|
||||
EXIT_ON_ERROR(hr);
|
||||
hr = enumerate_with_state("Unplugged devices:", DEVICE_STATE_UNPLUGGED, 0);
|
||||
hr = enumerate_with_state(log, "Unplugged devices:", DEVICE_STATE_UNPLUGGED, 0);
|
||||
EXIT_ON_ERROR(hr);
|
||||
CoUninitialize();
|
||||
return 0;
|
||||
exit_label:
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "Error enumerating devices: HRESULT %08"PRIx32" \"%s\"\n",
|
||||
(uint32_t)hr, explain_err(hr));
|
||||
mp_err(log, "Error enumerating devices: HRESULT %08"PRIx32" \"%s\"\n",
|
||||
(uint32_t)hr, explain_err(hr));
|
||||
CoUninitialize();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static HRESULT find_and_load_device(IMMDevice **ppDevice, char *search) {
|
||||
static HRESULT find_and_load_device(struct ao *ao, IMMDevice **ppDevice,
|
||||
char *search)
|
||||
{
|
||||
HRESULT hr;
|
||||
IMMDeviceEnumerator *pEnumerator = NULL;
|
||||
IMMDeviceCollection *pDevices = NULL;
|
||||
@@ -834,16 +796,16 @@ static HRESULT find_and_load_device(IMMDevice **ppDevice, char *search) {
|
||||
IMMDeviceCollection_GetCount(pDevices, &count);
|
||||
|
||||
if (devno >= count) {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: no device #%d!\n", devno);
|
||||
MP_ERR(ao, "no device #%d!\n", devno);
|
||||
} else {
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: finding device #%d\n", devno);
|
||||
MP_VERBOSE(ao, "finding device #%d\n", devno);
|
||||
hr = IMMDeviceCollection_Item(pDevices, devno, &pTempDevice);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
hr = IMMDevice_GetId(pTempDevice, &deviceID);
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: found device #%d\n", devno);
|
||||
MP_VERBOSE(ao, "found device #%d\n", devno);
|
||||
}
|
||||
} else {
|
||||
hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eRender,
|
||||
@@ -854,7 +816,7 @@ static HRESULT find_and_load_device(IMMDevice **ppDevice, char *search) {
|
||||
int count;
|
||||
IMMDeviceCollection_GetCount(pDevices, &count);
|
||||
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: finding device %s\n", devid);
|
||||
MP_VERBOSE(ao, "finding device %s\n", devid);
|
||||
|
||||
IMMDevice *prevDevice = NULL;
|
||||
|
||||
@@ -872,14 +834,14 @@ static HRESULT find_and_load_device(IMMDevice **ppDevice, char *search) {
|
||||
if (deviceID) {
|
||||
char *name;
|
||||
if (!search_err) {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: multiple matching devices found!\n");
|
||||
MP_ERR(ao, "multiple matching devices found!\n");
|
||||
name = get_device_name(prevDevice);
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: %s\n", name);
|
||||
MP_ERR(ao, "%s\n", name);
|
||||
free(name);
|
||||
search_err = 1;
|
||||
}
|
||||
name = get_device_name(pTempDevice);
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: %s\n", name);
|
||||
MP_ERR(ao, "%s\n", name);
|
||||
free(name);
|
||||
}
|
||||
hr = IMMDevice_GetId(pTempDevice, &deviceID);
|
||||
@@ -891,7 +853,7 @@ static HRESULT find_and_load_device(IMMDevice **ppDevice, char *search) {
|
||||
}
|
||||
|
||||
if (deviceID == NULL) {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: could not find device %s!\n", devid);
|
||||
MP_ERR(ao, "could not find device %s!\n", devid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -901,12 +863,12 @@ static HRESULT find_and_load_device(IMMDevice **ppDevice, char *search) {
|
||||
if (deviceID == NULL || search_err) {
|
||||
hr = E_NOTFOUND;
|
||||
} else {
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: loading device %S\n", deviceID);
|
||||
MP_VERBOSE(ao, "loading device %S\n", deviceID);
|
||||
|
||||
hr = IMMDeviceEnumerator_GetDevice(pEnumerator, deviceID, ppDevice);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: could not load requested device!\n");
|
||||
MP_ERR(ao, "could not load requested device!\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,14 +879,15 @@ exit_label:
|
||||
return hr;
|
||||
}
|
||||
|
||||
static int validate_device(const m_option_t *opt, struct bstr name,
|
||||
struct bstr param) {
|
||||
static int validate_device(struct mp_log *log, const m_option_t *opt,
|
||||
struct bstr name, struct bstr param)
|
||||
{
|
||||
if (bstr_equals0(param, "help")) {
|
||||
enumerate_devices();
|
||||
enumerate_devices(log);
|
||||
return M_OPT_EXIT;
|
||||
}
|
||||
|
||||
mp_msg(MSGT_AO, MSGL_DBG2, "ao-wasapi: validating device=%s\n", param.start);
|
||||
mp_dbg(log, "validating device=%s\n", param.start);
|
||||
|
||||
char *end;
|
||||
int devno = (int) strtol(param.start, &end, 10);
|
||||
@@ -933,8 +896,7 @@ static int validate_device(const m_option_t *opt, struct bstr name,
|
||||
if ((end == (void*)param.start || *end) && devno < 0)
|
||||
ret = M_OPT_OUT_OF_RANGE;
|
||||
|
||||
mp_msg(MSGT_AO, MSGL_DBG2, "ao-wasapi: device=%s %svalid\n",
|
||||
param.start, ret == 1 ? "" : "not ");
|
||||
mp_dbg(log, "device=%s %svalid\n", param.start, ret == 1 ? "" : "not ");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -956,15 +918,15 @@ static int thread_init(struct ao *ao)
|
||||
SAFE_RELEASE(pEnumerator, IMMDeviceEnumerator_Release(pEnumerator));
|
||||
|
||||
char *id = get_device_id(state->pDevice);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: default device ID: %s\n", id);
|
||||
MP_VERBOSE(ao, "default device ID: %s\n", id);
|
||||
free(id);
|
||||
} else {
|
||||
hr = find_and_load_device(&state->pDevice, state->opt_device);
|
||||
hr = find_and_load_device(ao, &state->pDevice, state->opt_device);
|
||||
}
|
||||
EXIT_ON_ERROR(hr);
|
||||
|
||||
char *name = get_device_name(state->pDevice);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: device loaded: %s\n", name);
|
||||
MP_VERBOSE(ao, "device loaded: %s\n", name);
|
||||
free(name);
|
||||
|
||||
hr = IMMDeviceActivator_Activate(state->pDevice, &IID_IAudioClient,
|
||||
@@ -982,9 +944,7 @@ static int thread_init(struct ao *ao)
|
||||
if (state->init_ret)
|
||||
goto exit_label;
|
||||
if (!fix_format(state)) { /* now that we're sure what format to use */
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: thread_init OK!\n");
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_VERBOSE(ao, "thread_init OK!\n");
|
||||
SetEvent(state->init_done);
|
||||
return state->init_ret;
|
||||
}
|
||||
@@ -1030,6 +990,7 @@ static void thread_feed(wasapi_state *state,int force_feed)
|
||||
/* should be smaller than buffer block size by now */
|
||||
memset(pData,0,client_buffer);
|
||||
mp_ring_read(state->ringbuff, (unsigned char *)pData, client_buffer);
|
||||
state->final_chunk = 0;
|
||||
} else {
|
||||
/* buffer underrun?! abort */
|
||||
hr = IAudioRenderClient_ReleaseBuffer(state->pRenderClient,
|
||||
@@ -1043,15 +1004,13 @@ static void thread_feed(wasapi_state *state,int force_feed)
|
||||
EXIT_ON_ERROR(hr);
|
||||
return;
|
||||
exit_label:
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: thread_feed fails with %"PRIx32"!\n", (uint32_t)hr);
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_ERR(state, "thread_feed fails with %"PRIx32"!\n", (uint32_t)hr);
|
||||
return;
|
||||
}
|
||||
|
||||
static void thread_play(wasapi_state *state)
|
||||
{
|
||||
thread_feed(state, 0);
|
||||
thread_feed(state, state->final_chunk);
|
||||
state->is_playing = 1;
|
||||
IAudioClient_Start(state->pAudioClient);
|
||||
return;
|
||||
@@ -1121,9 +1080,7 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
HANDLE playcontrol[] =
|
||||
{state->hUninit, state->hPause, state->hReset, state->hGetvol,
|
||||
state->hSetvol, state->hPlay, state->hFeed, NULL};
|
||||
EnterCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: Entering dispatch loop!\n");
|
||||
LeaveCriticalSection(&state->print_lock);
|
||||
MP_VERBOSE(ao, "Entering dispatch loop!\n");
|
||||
while (1) { /* watch events, poll at least every 2 seconds */
|
||||
waitstatus = WaitForMultipleObjects(7, playcontrol, FALSE, 2000);
|
||||
switch (waitstatus) {
|
||||
@@ -1152,7 +1109,7 @@ static DWORD __stdcall ThreadLoop(void *lpParameter)
|
||||
case (WAIT_OBJECT_0 + 6): /* feed */
|
||||
if (state->is_playing)
|
||||
feedwatch = 1;
|
||||
thread_feed(state, 0);
|
||||
thread_feed(state, state->final_chunk);
|
||||
break;
|
||||
case WAIT_TIMEOUT: /* Did our feed die? */
|
||||
if (feedwatch)
|
||||
@@ -1195,11 +1152,12 @@ static int get_space(struct ao *ao)
|
||||
if (!ao || !ao->priv)
|
||||
return -1;
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
return mp_ring_available(state->ringbuff);
|
||||
return mp_ring_available(state->ringbuff) / ao->sstride;
|
||||
}
|
||||
|
||||
static void reset_buffers(struct wasapi_state *state)
|
||||
{
|
||||
state->final_chunk = 0;
|
||||
mp_ring_reset(state->ringbuff);
|
||||
}
|
||||
|
||||
@@ -1210,11 +1168,11 @@ static int setup_buffers(struct wasapi_state *state)
|
||||
return !state->ringbuff;
|
||||
}
|
||||
|
||||
static void uninit(struct ao *ao, bool immed)
|
||||
static void uninit(struct ao *ao, bool block)
|
||||
{
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: uninit!\n");
|
||||
MP_VERBOSE(ao, "uninit!\n");
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
state->immed = immed;
|
||||
state->immed = !block;
|
||||
SetEvent(state->hUninit);
|
||||
/* wait up to 10 seconds */
|
||||
if (WaitForSingleObject(state->threadLoop, 10000) == WAIT_TIMEOUT)
|
||||
@@ -1222,22 +1180,23 @@ static void uninit(struct ao *ao, bool immed)
|
||||
if (state->VistaBlob.hAvrt)
|
||||
FreeLibrary(state->VistaBlob.hAvrt);
|
||||
closehandles(ao);
|
||||
DeleteCriticalSection(&state->print_lock);
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: uninit END!\n");
|
||||
MP_VERBOSE(ao, "uninit END!\n");
|
||||
}
|
||||
|
||||
static int init(struct ao *ao)
|
||||
{
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: init!\n");
|
||||
MP_VERBOSE(ao, "init!\n");
|
||||
ao->format = af_fmt_from_planar(ao->format);
|
||||
struct mp_chmap_sel sel = {0};
|
||||
mp_chmap_sel_add_waveext(&sel);
|
||||
if (!ao_chmap_sel_adjust(ao, &sel, &ao->channels))
|
||||
return -1;
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
state->log = ao->log;
|
||||
fill_VistaBlob(state);
|
||||
|
||||
if (state->opt_list) {
|
||||
enumerate_devices();
|
||||
enumerate_devices(state->log);
|
||||
}
|
||||
|
||||
if (state->opt_exclusive) {
|
||||
@@ -1256,7 +1215,6 @@ static int init(struct ao *ao)
|
||||
state->hUninit = CreateEventW(NULL, FALSE, FALSE, NULL);
|
||||
state->fatal_error = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||
state->hFeed = CreateEvent(NULL, FALSE, FALSE, NULL); /* for wasapi event mode */
|
||||
InitializeCriticalSection(&state->print_lock);
|
||||
if (!state->init_done || !state->fatal_error || !state->hPlay ||
|
||||
!state->hPause || !state->hFeed || !state->hReset || !state->hGetvol ||
|
||||
!state->hSetvol || !state->hDoneVol)
|
||||
@@ -1269,18 +1227,18 @@ static int init(struct ao *ao)
|
||||
state->threadLoop = (HANDLE)CreateThread(NULL, 0, &ThreadLoop, ao, 0, NULL);
|
||||
if (!state->threadLoop) {
|
||||
/* failed to init thread */
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: fail to create thread!\n");
|
||||
MP_ERR(ao, "fail to create thread!\n");
|
||||
return -1;
|
||||
}
|
||||
WaitForSingleObject(state->init_done, INFINITE); /* wait on init complete */
|
||||
if (state->init_ret) {
|
||||
if (!ao->probing) {
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: thread_init failed!\n");
|
||||
MP_ERR(ao, "thread_init failed!\n");
|
||||
}
|
||||
} else {
|
||||
mp_msg(MSGT_AO, MSGL_V, "ao-wasapi: Init Done!\n");
|
||||
MP_VERBOSE(ao, "Init Done!\n");
|
||||
if (setup_buffers(state))
|
||||
mp_msg(MSGT_AO, MSGL_ERR, "ao-wasapi: buffer setup failed!\n");
|
||||
MP_ERR(ao, "buffer setup failed!\n");
|
||||
}
|
||||
return state->init_ret;
|
||||
}
|
||||
@@ -1336,25 +1294,25 @@ static void reset(struct ao *ao)
|
||||
reset_buffers(state);
|
||||
}
|
||||
|
||||
static int play(struct ao *ao, void *data, int len, int flags)
|
||||
static int play(struct ao *ao, void **data, int samples, int flags)
|
||||
{
|
||||
int ret = 0;
|
||||
if (!ao || !ao->priv)
|
||||
return ret;
|
||||
return 0;
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
if (WaitForSingleObject(state->fatal_error, 0) == WAIT_OBJECT_0) {
|
||||
/* something bad happened */
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = mp_ring_write(state->ringbuff, data, len);
|
||||
int ret = mp_ring_write(state->ringbuff, data[0], samples * ao->sstride);
|
||||
|
||||
state->final_chunk |= flags & AOPLAY_FINAL_CHUNK;
|
||||
if (!state->is_playing) {
|
||||
/* start playing */
|
||||
state->is_playing = 1;
|
||||
SetEvent(state->hPlay);
|
||||
}
|
||||
return ret;
|
||||
return ret / ao->sstride;
|
||||
}
|
||||
|
||||
static float get_delay(struct ao *ao)
|
||||
@@ -1362,19 +1320,15 @@ static float get_delay(struct ao *ao)
|
||||
if (!ao || !ao->priv)
|
||||
return -1.0f;
|
||||
struct wasapi_state *state = (struct wasapi_state *)ao->priv;
|
||||
return (float)(RING_BUFFER_COUNT * state->buffer_block_size - get_space(ao)) /
|
||||
return (float)(RING_BUFFER_COUNT * state->buffer_block_size - get_space(ao) * ao->sstride) /
|
||||
(float)state->format.Format.nAvgBytesPerSec;
|
||||
}
|
||||
|
||||
#define OPT_BASE_STRUCT struct wasapi_state
|
||||
|
||||
const struct ao_driver audio_out_wasapi = {
|
||||
.info = &(const struct ao_info) {
|
||||
"Windows WASAPI audio output (event mode)",
|
||||
"wasapi",
|
||||
"Jonathan Yong <10walls@gmail.com>",
|
||||
""
|
||||
},
|
||||
.description = "Windows WASAPI audio output (event mode)",
|
||||
.name = "wasapi",
|
||||
.init = init,
|
||||
.uninit = uninit,
|
||||
.control = control,
|
||||
|
||||
@@ -27,74 +27,6 @@
|
||||
#include "chmap.h"
|
||||
#include "reorder_ch.h"
|
||||
|
||||
static inline void reorder_to_planar_(void *restrict out, const void *restrict in,
|
||||
size_t size, size_t nchan, size_t nmemb)
|
||||
{
|
||||
size_t i, c;
|
||||
char *outptr = (char *) out;
|
||||
size_t instep = nchan * size;
|
||||
|
||||
for (c = 0; c < nchan; ++c) {
|
||||
const char *inptr = ((const char *) in) + c * size;
|
||||
for (i = 0; i < nmemb; ++i, inptr += instep, outptr += size) {
|
||||
memcpy(outptr, inptr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void reorder_to_planar(void *restrict out, const void *restrict in,
|
||||
size_t size, size_t nchan, size_t nmemb)
|
||||
{
|
||||
// special case for mono (nothing to do...)
|
||||
if (nchan == 1)
|
||||
memcpy(out, in, size * nchan * nmemb);
|
||||
// these calls exist to insert an inline copy of to_planar_ here with known
|
||||
// value of size to help the compiler replace the memcpy calls by mov
|
||||
// instructions
|
||||
else if (size == 1)
|
||||
reorder_to_planar_(out, in, 1, nchan, nmemb);
|
||||
else if (size == 2)
|
||||
reorder_to_planar_(out, in, 2, nchan, nmemb);
|
||||
else if (size == 4)
|
||||
reorder_to_planar_(out, in, 4, nchan, nmemb);
|
||||
// general case (calls memcpy a lot, should actually never happen, but
|
||||
// stays here for correctness purposes)
|
||||
else
|
||||
reorder_to_planar_(out, in, size, nchan, nmemb);
|
||||
}
|
||||
|
||||
static inline void reorder_to_packed_(uint8_t *out, uint8_t **in,
|
||||
size_t size, size_t nchan, size_t nmemb)
|
||||
{
|
||||
size_t outstep = nchan * size;
|
||||
|
||||
for (size_t c = 0; c < nchan; ++c) {
|
||||
char *outptr = out + c * size;
|
||||
char *inptr = in[c];
|
||||
for (size_t i = 0; i < nmemb; ++i, outptr += outstep, inptr += size) {
|
||||
memcpy(outptr, inptr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// out = destination array of packed samples of given size, nmemb frames
|
||||
// in[channel] = source array of samples for the given channel
|
||||
void reorder_to_packed(uint8_t *out, uint8_t **in,
|
||||
size_t size, size_t nchan, size_t nmemb)
|
||||
{
|
||||
if (nchan == 1)
|
||||
memcpy(out, in, size * nchan * nmemb);
|
||||
// See reorder_to_planar() why this is done this way
|
||||
else if (size == 1)
|
||||
reorder_to_packed_(out, in, 1, nchan, nmemb);
|
||||
else if (size == 2)
|
||||
reorder_to_packed_(out, in, 2, nchan, nmemb);
|
||||
else if (size == 4)
|
||||
reorder_to_packed_(out, in, 4, nchan, nmemb);
|
||||
else
|
||||
reorder_to_packed_(out, in, size, nchan, nmemb);
|
||||
}
|
||||
|
||||
#define MAX_SAMPLESIZE 8
|
||||
|
||||
static void reorder_channels_(uint8_t *restrict data, int *restrict ch_order,
|
||||
|
||||
@@ -25,11 +25,6 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
void reorder_to_planar(void *restrict out, const void *restrict in,
|
||||
size_t size, size_t nchan, size_t nmemb);
|
||||
void reorder_to_packed(uint8_t *out, uint8_t **in,
|
||||
size_t size, size_t nchan, size_t nmemb);
|
||||
|
||||
void reorder_channels(void *restrict data, int *restrict ch_order,
|
||||
size_t sample_size, size_t num_ch, size_t num_frames);
|
||||
|
||||
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# This script simply downloads waf to the current directory
|
||||
|
||||
from __future__ import print_function
|
||||
import os, sys, stat, hashlib, subprocess
|
||||
|
||||
WAFRELEASE = "waf-1.7.13"
|
||||
WAFURL = "https://waf.googlecode.com/files/" + WAFRELEASE
|
||||
SHA256HASH = "03cc750049350ee01cdbc584b70924e333fcc17ba4a2d04648dab1535538a873"
|
||||
|
||||
if os.path.exists("waf"):
|
||||
wafver = subprocess.check_output(['./waf', '--version']).decode()
|
||||
if WAFRELEASE.split('-')[1] == wafver.split(' ')[1]:
|
||||
print("Found 'waf', skipping download.")
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
from urllib.request import urlopen
|
||||
except:
|
||||
from urllib2 import urlopen
|
||||
|
||||
print("Downloading %s..." % WAFURL)
|
||||
waf = urlopen(WAFURL).read()
|
||||
|
||||
if SHA256HASH == hashlib.sha256(waf).hexdigest():
|
||||
with open("waf", "wb") as wf:
|
||||
wf.write(waf)
|
||||
|
||||
os.chmod("waf", os.stat("waf").st_mode | stat.S_IXUSR)
|
||||
print("Checksum verified.")
|
||||
else:
|
||||
print("The checksum of the downloaded file does not match!")
|
||||
print("Please download and verify the file manually.")
|
||||
|
||||
sys.exit(1)
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
#include "talloc.h"
|
||||
|
||||
#include "mpvcore/bstr.h"
|
||||
#include "bstr/bstr.h"
|
||||
|
||||
int bstrcmp(struct bstr str1, struct bstr str2)
|
||||
{
|
||||
Loaded 100 of 454 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user