Compare commits
914 Commits
v0.15.10
...
v0.16_alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
653c4792b5 | ||
|
|
5cb061ebdf | ||
|
|
12f4225d6b | ||
|
|
50862a1dd1 | ||
|
|
013ebb638a | ||
|
|
bedb82bf4d | ||
|
|
cbb1ab58cd | ||
|
|
b01235e330 | ||
|
|
8341a9f7b2 | ||
|
|
b233c145fa | ||
|
|
9de5bb9e23 | ||
|
|
0c5305c51f | ||
|
|
63c9a20f96 | ||
|
|
b40c0811f4 | ||
|
|
838790fc2d | ||
|
|
1ff2d5b689 | ||
|
|
e4b7a113fd | ||
|
|
7820ebb82e | ||
|
|
31ab0b3df1 | ||
|
|
e598922133 | ||
|
|
e21ad70f3f | ||
|
|
a81cb932c2 | ||
|
|
3d66a4fee8 | ||
|
|
dea5601e79 | ||
|
|
43a840552f | ||
|
|
2f16f8e9f7 | ||
|
|
4364b30c42 | ||
|
|
c0da938d4f | ||
|
|
fb19aa355e | ||
|
|
6135419ac3 | ||
|
|
0fec8e0864 | ||
|
|
1f976d6e54 | ||
|
|
a4908dca42 | ||
|
|
8b055c3127 | ||
|
|
172182b18f | ||
|
|
898a13f196 | ||
|
|
2bc5161e95 | ||
|
|
e2e8d0d2f3 | ||
|
|
b2e3c0757b | ||
|
|
b97e92468f | ||
|
|
56bf4ede18 | ||
|
|
0fc0196dba | ||
|
|
1492324c76 | ||
|
|
49bc317fb8 | ||
|
|
375a09d6f6 | ||
|
|
0265c34bed | ||
|
|
a1882f48be | ||
|
|
c3569814bd | ||
|
|
814daac5ba | ||
|
|
0d03bdce6d | ||
|
|
768be22f7c | ||
|
|
ec89ce5a8a | ||
|
|
34415bf0b6 | ||
|
|
0a0c78674f | ||
|
|
1bffdabe41 | ||
|
|
77e6810c14 | ||
|
|
5ebe33653c | ||
|
|
8e3eace289 | ||
|
|
284659034d | ||
|
|
9550c87327 | ||
|
|
e223e8a5b5 | ||
|
|
4d6d372a5b | ||
|
|
0aeec90590 | ||
|
|
cfcd84655c | ||
|
|
5092eaf1cc | ||
|
|
9328558fc7 | ||
|
|
026bd15872 | ||
|
|
7cca55549b | ||
|
|
c7e89ea3a3 | ||
|
|
002b283433 | ||
|
|
5a2820ca3d | ||
|
|
9eb292c1e5 | ||
|
|
5216cfb3c8 | ||
|
|
373d1843a8 | ||
|
|
82ee278f53 | ||
|
|
c27fc26ecd | ||
|
|
fba13bd5d2 | ||
|
|
68f75955d8 | ||
|
|
cd21cfc115 | ||
|
|
05703cf73b | ||
|
|
1ea10db953 | ||
|
|
eb6188f8c0 | ||
|
|
6bb7a7439d | ||
|
|
ca6110d92f | ||
|
|
a219d488d0 | ||
|
|
1746257492 | ||
|
|
07423c300e | ||
|
|
69fcdc6a07 | ||
|
|
dda5415def | ||
|
|
4347114455 | ||
|
|
9fb26b5617 | ||
|
|
442b327582 | ||
|
|
cff727644d | ||
|
|
7f80349494 | ||
|
|
a6ef696132 | ||
|
|
53f08a95eb | ||
|
|
d0d2d6ed1b | ||
|
|
4e83b79d2b | ||
|
|
37e69a89b9 | ||
|
|
6f28eb4c0a | ||
|
|
caf93d9a2c | ||
|
|
747e945d29 | ||
|
|
067d7212bf | ||
|
|
867b93a5b2 | ||
|
|
53647c8ba1 | ||
|
|
b571ba27a4 | ||
|
|
de8f2739c2 | ||
|
|
2c1c588868 | ||
|
|
e271f69a34 | ||
|
|
39105f2119 | ||
|
|
4e080a9b06 | ||
|
|
d55740808d | ||
|
|
6566038df9 | ||
|
|
f0faf00320 | ||
|
|
51c7577c8f | ||
|
|
be308c6657 | ||
|
|
1729a0b437 | ||
|
|
1b2fc2ae19 | ||
|
|
58a75f0b78 | ||
|
|
daf6e2b5da | ||
|
|
71da5b026c | ||
|
|
2f68ea635b | ||
|
|
ca08c4dbf4 | ||
|
|
8af52fa205 | ||
|
|
3b0bf856c4 | ||
|
|
28f5803d34 | ||
|
|
0b72299857 | ||
|
|
4a2302c2ed | ||
|
|
980201a665 | ||
|
|
d60bcd2869 | ||
|
|
017d61a1aa | ||
|
|
4028d03a6e | ||
|
|
44b771aca1 | ||
|
|
8c581eca4d | ||
|
|
8e815bec9f | ||
|
|
9dda53e1d2 | ||
|
|
65ad298460 | ||
|
|
e8310211e2 | ||
|
|
3709b9aa11 | ||
|
|
c5a291ad74 | ||
|
|
894573657d | ||
|
|
9cce1d749a | ||
|
|
ed0b48040c | ||
|
|
9aeed06964 | ||
|
|
eda46bc07f | ||
|
|
c727e86980 | ||
|
|
57b4013306 | ||
|
|
fc5a7a8774 | ||
|
|
8248f498b2 | ||
|
|
22f3ffb099 | ||
|
|
81bd8ef73c | ||
|
|
bedc172eab | ||
|
|
0a9b0dd070 | ||
|
|
6aa0f61e15 | ||
|
|
4461c3d5d1 | ||
|
|
fef46bcf49 | ||
|
|
fa2ff849c5 | ||
|
|
6b2b91ff01 | ||
|
|
733962db2f | ||
|
|
f824ecdfa1 | ||
|
|
6c831e04a2 | ||
|
|
67c48f66a6 | ||
|
|
5fa1c703de | ||
|
|
bead892e21 | ||
|
|
8341daca4a | ||
|
|
136759a298 | ||
|
|
0ac5b6e613 | ||
|
|
eb5208c4f9 | ||
|
|
d88c3c8462 | ||
|
|
5399a72ec1 | ||
|
|
d093fb2441 | ||
|
|
0140804ef9 | ||
|
|
ecb5f68ba8 | ||
|
|
55bf242635 | ||
|
|
7dba410c26 | ||
|
|
92e66e5ab2 | ||
|
|
54dedef9cf | ||
|
|
b8b2ef3de1 | ||
|
|
681128def4 | ||
|
|
f4f4a908bb | ||
|
|
7c37f2879b | ||
|
|
615ccd1070 | ||
|
|
6ed69e6395 | ||
|
|
f946a182a3 | ||
|
|
d3fcb81da0 | ||
|
|
7e42637b74 | ||
|
|
dbe93d4a6d | ||
|
|
20bc25cdcc | ||
|
|
13c0788334 | ||
|
|
672cf84a32 | ||
|
|
2834f7802f | ||
|
|
b135483abc | ||
|
|
ba36cbde06 | ||
|
|
f153681522 | ||
|
|
d73291959b | ||
|
|
01a675eb5f | ||
|
|
be5143c0f3 | ||
|
|
b7ee1f5c40 | ||
|
|
b619f5e73d | ||
|
|
e44f639002 | ||
|
|
1aa470e0bf | ||
|
|
b6a33691ff | ||
|
|
5e616b4ff3 | ||
|
|
d847593a8e | ||
|
|
1cd5700c37 | ||
|
|
957cf3c7b0 | ||
|
|
b6c8de6e01 | ||
|
|
21f5ed4a25 | ||
|
|
52fc168905 | ||
|
|
3bb03db097 | ||
|
|
ee88b8f8c4 | ||
|
|
7bfcb6151d | ||
|
|
3c549c5147 | ||
|
|
8b0fb1cc4d | ||
|
|
e3d62305f1 | ||
|
|
263a968c2b | ||
|
|
39b37d547c | ||
|
|
4cfcdd5e0f | ||
|
|
9d72a34109 | ||
|
|
18cdbfa70f | ||
|
|
7886fd711f | ||
|
|
3993ed6b81 | ||
|
|
abf974c5c5 | ||
|
|
97f80e04b0 | ||
|
|
944057c30b | ||
|
|
9c4d4bd6b0 | ||
|
|
a77fd83b15 | ||
|
|
ed78955f8d | ||
|
|
cab16f8e1e | ||
|
|
24db28072f | ||
|
|
e20d3d49b3 | ||
|
|
33641df09c | ||
|
|
294cce2710 | ||
|
|
9fb31aafb3 | ||
|
|
791ddc566a | ||
|
|
7d2b2e371c | ||
|
|
c3ecb5aa5e | ||
|
|
191e520999 | ||
|
|
dd5fdbc540 | ||
|
|
b39855731c | ||
|
|
e27dd7280f | ||
|
|
a9ff134e9f | ||
|
|
11fbb1c807 | ||
|
|
15998c0db9 | ||
|
|
cf3ada3d04 | ||
|
|
7df94b1718 | ||
|
|
c2d788f6de | ||
|
|
fa8992fc63 | ||
|
|
8d631b346a | ||
|
|
393bcd961a | ||
|
|
4d1eedbaa2 | ||
|
|
6a95898038 | ||
|
|
9d55b16998 | ||
|
|
c52f469c9c | ||
|
|
50c0c4b701 | ||
|
|
8446b70ddc | ||
|
|
635791d1cd | ||
|
|
e9beea072d | ||
|
|
e69bb3b337 | ||
|
|
06f86b4532 | ||
|
|
68ece2fef3 | ||
|
|
4953ea90c2 | ||
|
|
e7a515c8b1 | ||
|
|
e9b75d462c | ||
|
|
68017b1254 | ||
|
|
469c9b5def | ||
|
|
e686d19154 | ||
|
|
762565e9d1 | ||
|
|
41a4662c8c | ||
|
|
5842015b90 | ||
|
|
ae56901863 | ||
|
|
4e364854ab | ||
|
|
a3645984cd | ||
|
|
43cf4e97b9 | ||
|
|
795578ef95 | ||
|
|
96493e0333 | ||
|
|
48b49e2303 | ||
|
|
752dfb3d95 | ||
|
|
5e0117b444 | ||
|
|
c05e6a1275 | ||
|
|
b21e4d9a58 | ||
|
|
eeef501ed8 | ||
|
|
f4e9275f7c | ||
|
|
f672657388 | ||
|
|
32441175f4 | ||
|
|
059d1dc7f2 | ||
|
|
7fbb856eee | ||
|
|
bb2aa70ec6 | ||
|
|
8e34c59c82 | ||
|
|
766b9fd453 | ||
|
|
891dab7b91 | ||
|
|
7a24e496d5 | ||
|
|
7a62818ffd | ||
|
|
acb0ff1ea8 | ||
|
|
2acad9fe1e | ||
|
|
8914ebc964 | ||
|
|
5477c31160 | ||
|
|
acd3f8cd91 | ||
|
|
ca1fc13116 | ||
|
|
9cb7760c5e | ||
|
|
fb9bd53328 | ||
|
|
a0a26d3341 | ||
|
|
9d4b7ab113 | ||
|
|
8ac776c58b | ||
|
|
006f4be71c | ||
|
|
163e05f5a0 | ||
|
|
02526eda86 | ||
|
|
828a5f552f | ||
|
|
26841b6058 | ||
|
|
1abfcc56af | ||
|
|
da47afe7d1 | ||
|
|
96546c1a8a | ||
|
|
579a8a96ea | ||
|
|
79848e3414 | ||
|
|
87c861cae3 | ||
|
|
8f326a33ee | ||
|
|
a942384fbf | ||
|
|
de0cdee4aa | ||
|
|
550c9319e9 | ||
|
|
ae9c02b3a8 | ||
|
|
6f6d47dd20 | ||
|
|
030e61115c | ||
|
|
d6d5caae23 | ||
|
|
382691179f | ||
|
|
01c5cb985c | ||
|
|
8a5d2c3c83 | ||
|
|
2f8135ef8b | ||
|
|
9b9abff972 | ||
|
|
c69cc31de0 | ||
|
|
241e94936f | ||
|
|
1330274ffc | ||
|
|
0e183d3fa1 | ||
|
|
cd8f92c928 | ||
|
|
e58b4f773f | ||
|
|
604ca50b65 | ||
|
|
537e353546 | ||
|
|
f95d7b13da | ||
|
|
915182bcb8 | ||
|
|
d3b763a48c | ||
|
|
816b6ad4a7 | ||
|
|
3adfbfe36d | ||
|
|
9d3865cb95 | ||
|
|
05cde5810a | ||
|
|
6b96f5d566 | ||
|
|
7a2e07e124 | ||
|
|
aad05fd138 | ||
|
|
032c5376ad | ||
|
|
0cc3b98bd9 | ||
|
|
c157711eaf | ||
|
|
2632794578 | ||
|
|
3ddf7b620c | ||
|
|
131cb0598a | ||
|
|
9862521aec | ||
|
|
55fbb67cfb | ||
|
|
a0384aaead | ||
|
|
efc885a9dc | ||
|
|
333e11d0eb | ||
|
|
58da24b1cb | ||
|
|
bb4cef6a93 | ||
|
|
5787f73704 | ||
|
|
201316cd67 | ||
|
|
a038bca745 | ||
|
|
cf38505d8f | ||
|
|
af964e8929 | ||
|
|
554b2b0ed9 | ||
|
|
627975e897 | ||
|
|
6622d69fda | ||
|
|
216dff98d2 | ||
|
|
032354e65c | ||
|
|
115d26608b | ||
|
|
bad350bc18 | ||
|
|
870436a592 | ||
|
|
6a17233f78 | ||
|
|
b54bde6f2b | ||
|
|
d2051c7f50 | ||
|
|
bd29f7e3c8 | ||
|
|
c3bb81abec | ||
|
|
d469c8137f | ||
|
|
c374a7d3f4 | ||
|
|
3679d5bd7a | ||
|
|
c128f2dd7e | ||
|
|
843717d25c | ||
|
|
50ea6a4b5c | ||
|
|
5649f22322 | ||
|
|
b89281411f | ||
|
|
67c41033c1 | ||
|
|
59534b92d2 | ||
|
|
41a48b14e3 | ||
|
|
5821bd1a21 | ||
|
|
c5cdac9609 | ||
|
|
b7f55ad392 | ||
|
|
71fee09744 | ||
|
|
f9af1a445e | ||
|
|
0bc8c0c1da | ||
|
|
74156d5bed | ||
|
|
b04adde7ab | ||
|
|
3f64ac04b8 | ||
|
|
b009970af7 | ||
|
|
bd97586cc4 | ||
|
|
c3e0fbd9e4 | ||
|
|
b05ba0286e | ||
|
|
228b03edf8 | ||
|
|
d000d31355 | ||
|
|
971c9671f6 | ||
|
|
3d95226f2b | ||
|
|
b12072e6d9 | ||
|
|
03427d4eff | ||
|
|
a627a703ca | ||
|
|
440cfc8052 | ||
|
|
c959148ed1 | ||
|
|
530e480748 | ||
|
|
95c3f283ea | ||
|
|
aef6609f4f | ||
|
|
6b728e4756 | ||
|
|
f1ecd9eac8 | ||
|
|
243c96304b | ||
|
|
e3597e648c | ||
|
|
357037f7ab | ||
|
|
9715218d40 | ||
|
|
c0b8c2c73b | ||
|
|
51d0687377 | ||
|
|
849d7895dc | ||
|
|
850e213261 | ||
|
|
786c1f035f | ||
|
|
f70d2f58a1 | ||
|
|
67b0ab717e | ||
|
|
f7420dbfe1 | ||
|
|
400600ffff | ||
|
|
940e66bb89 | ||
|
|
69391dadda | ||
|
|
ff3393ebf1 | ||
|
|
0ac0bd26e7 | ||
|
|
bf6258f582 | ||
|
|
8587fcbb93 | ||
|
|
a4fbf772c1 | ||
|
|
e28c5a0beb | ||
|
|
c38b9490a8 | ||
|
|
c3085d7b61 | ||
|
|
179502fe93 | ||
|
|
b6b377edd1 | ||
|
|
a76097210f | ||
|
|
ac0bf1a445 | ||
|
|
7b80e73810 | ||
|
|
6d11711a01 | ||
|
|
bae03e173e | ||
|
|
07b388f8d4 | ||
|
|
4809213676 | ||
|
|
c77fa296bc | ||
|
|
c412d6251e | ||
|
|
68c2cfbb40 | ||
|
|
4076523198 | ||
|
|
3857bb9990 | ||
|
|
300f936228 | ||
|
|
1358428031 | ||
|
|
5a480137d2 | ||
|
|
943bafbbc8 | ||
|
|
b772f26213 | ||
|
|
c33bbd947b | ||
|
|
d37b4bb199 | ||
|
|
ea92dee1ae | ||
|
|
5420f9ae76 | ||
|
|
39404725f0 | ||
|
|
5d1e5f4ea0 | ||
|
|
5184476682 | ||
|
|
141cbc60b9 | ||
|
|
2c1fb48318 | ||
|
|
1dfadf4815 | ||
|
|
f5b9e3c064 | ||
|
|
ff70dbd316 | ||
|
|
dd4625ce13 | ||
|
|
1648c7aa5b | ||
|
|
edaf017908 | ||
|
|
2c7bf61e68 | ||
|
|
7efb548921 | ||
|
|
0c5329aedc | ||
|
|
2d236e281f | ||
|
|
a6fd5819f9 | ||
|
|
cef5dcc0a1 | ||
|
|
e5b119a324 | ||
|
|
719990b1c5 | ||
|
|
f47bb8c1db | ||
|
|
873025a495 | ||
|
|
409a3ed808 | ||
|
|
76283c25a5 | ||
|
|
b9866e43d3 | ||
|
|
9947b82cad | ||
|
|
5b82ffc291 | ||
|
|
8068fd5228 | ||
|
|
b9c610ac87 | ||
|
|
59189160e3 | ||
|
|
ee5d3337a7 | ||
|
|
4c6a8e3ca5 | ||
|
|
4f38cc9cae | ||
|
|
dfc09a37c9 | ||
|
|
8588c21689 | ||
|
|
2decc65b45 | ||
|
|
0fb877740b | ||
|
|
37181c9181 | ||
|
|
08b139f37c | ||
|
|
3973aeecd2 | ||
|
|
5b2d32b499 | ||
|
|
e0d5ee0045 | ||
|
|
d35efddd65 | ||
|
|
7b13776f2d | ||
|
|
f937ec9a7c | ||
|
|
a3f5284dc6 | ||
|
|
4a8cc87b4d | ||
|
|
183725733a | ||
|
|
3c1bacbdbc | ||
|
|
d5ed23438a | ||
|
|
5bbaf0c9f1 | ||
|
|
f2f8290242 | ||
|
|
c1186693b5 | ||
|
|
80b220a3a6 | ||
|
|
5cc3c4f503 | ||
|
|
96204ea3dc | ||
|
|
9d1a34e30b | ||
|
|
69d9716f8b | ||
|
|
707b9fea17 | ||
|
|
d605329f83 | ||
|
|
f6e7dffada | ||
|
|
43549db718 | ||
|
|
7c0c8ca8d7 | ||
|
|
884be8e2b3 | ||
|
|
305de100a7 | ||
|
|
79035d7ed9 | ||
|
|
c1a999c492 | ||
|
|
e51d9fc6a9 | ||
|
|
2f69831fb8 | ||
|
|
b6a2ffd3d7 | ||
|
|
6a5f4651a1 | ||
|
|
70106464d3 | ||
|
|
b722d3d7f3 | ||
|
|
de57c21a3b | ||
|
|
84917721c7 | ||
|
|
118495d372 | ||
|
|
a6bee71f1a | ||
|
|
e0e6813a1d | ||
|
|
3d2a9d3545 | ||
|
|
6975c087e0 | ||
|
|
54033c74e4 | ||
|
|
8420f1420f | ||
|
|
3359f8785e | ||
|
|
96b974bc45 | ||
|
|
1a4025420c | ||
|
|
223b0db5bd | ||
|
|
2f4144e1cd | ||
|
|
f66edccffd | ||
|
|
b043ade456 | ||
|
|
217b494cc5 | ||
|
|
5ef62312af | ||
|
|
5479ed7cfb | ||
|
|
cac63bfd21 | ||
|
|
1573ea1485 | ||
|
|
e3af0032b2 | ||
|
|
9b21152600 | ||
|
|
c440faa94d | ||
|
|
375fd5ed4c | ||
|
|
ec25cda68b | ||
|
|
c9f726048c | ||
|
|
c2251dc5a2 | ||
|
|
a505cbc6c9 | ||
|
|
c422344190 | ||
|
|
1a4cfc3d90 | ||
|
|
2f1bd39be8 | ||
|
|
4624dfcb30 | ||
|
|
3546d931a1 | ||
|
|
4dadb965a7 | ||
|
|
f2184db1cd | ||
|
|
41f3f12709 | ||
|
|
5d55b45654 | ||
|
|
e3da174fca | ||
|
|
bb862a8ceb | ||
|
|
9ba900486e | ||
|
|
587284bae6 | ||
|
|
4c7bfa514f | ||
|
|
f9218423b9 | ||
|
|
ba34d48cf0 | ||
|
|
2598dd5109 | ||
|
|
fac6e9ecdb | ||
|
|
dbbead6e72 | ||
|
|
8cb9f9b070 | ||
|
|
61cb5df842 | ||
|
|
7e66f34154 | ||
|
|
f3203b5de5 | ||
|
|
65e56ff829 | ||
|
|
a4970c66ef | ||
|
|
945287358b | ||
|
|
979cd5a768 | ||
|
|
a68da8a475 | ||
|
|
2a9685cb3a | ||
|
|
9449006ac3 | ||
|
|
88abfc0d0f | ||
|
|
4ec25b5d64 | ||
|
|
e96dc9a14c | ||
|
|
a434c35eb4 | ||
|
|
7af8c88e37 | ||
|
|
a027bdf118 | ||
|
|
c043bf0f63 | ||
|
|
5744634094 | ||
|
|
a22c93b659 | ||
|
|
507606bb78 | ||
|
|
f421c8a191 | ||
|
|
d06f8baf69 | ||
|
|
56b8081af6 | ||
|
|
98d30ac9cf | ||
|
|
98150f503a | ||
|
|
6c78c21fb8 | ||
|
|
7fd2097a44 | ||
|
|
89893faa19 | ||
|
|
bfa7da943c | ||
|
|
734676fcfb | ||
|
|
acb265d082 | ||
|
|
77a647fc26 | ||
|
|
59ffb5b7c1 | ||
|
|
4dc25d3908 | ||
|
|
0f9f82f227 | ||
|
|
55e3aa8179 | ||
|
|
408d52fe39 | ||
|
|
b9013944dc | ||
|
|
e814f8d5bd | ||
|
|
64a481d873 | ||
|
|
93d8f9f00e | ||
|
|
d1742a2330 | ||
|
|
5555d30bbd | ||
|
|
4b17aca747 | ||
|
|
63dda94a02 | ||
|
|
7f9ee00980 | ||
|
|
53a749780a | ||
|
|
c504004702 | ||
|
|
8bfe3497b0 | ||
|
|
9bcfd3a47d | ||
|
|
451f932d80 | ||
|
|
10b7608926 | ||
|
|
2cd8a9fecf | ||
|
|
22279127f9 | ||
|
|
ad01e1249b | ||
|
|
6ef428af2e | ||
|
|
806496dfc9 | ||
|
|
9eadb517da | ||
|
|
1ae4e4dcd3 | ||
|
|
d099a7e464 | ||
|
|
25a806a347 | ||
|
|
73cff374fd | ||
|
|
cec019efff | ||
|
|
4748decd8d | ||
|
|
975143ab47 | ||
|
|
bb5acc939f | ||
|
|
bde3d14339 | ||
|
|
1403172ef3 | ||
|
|
f74ee1a352 | ||
|
|
cf8d278b78 | ||
|
|
82af161210 | ||
|
|
9fed97b1f1 | ||
|
|
83a9cf74fb | ||
|
|
a13e9832e7 | ||
|
|
f67426871b | ||
|
|
2bfddd4310 | ||
|
|
7b343eaf50 | ||
|
|
ac830468bf | ||
|
|
7547b1170b | ||
|
|
e53ca368a5 | ||
|
|
c426a0bc5c | ||
|
|
acc99da73d | ||
|
|
c4f895daf4 | ||
|
|
ede828c910 | ||
|
|
7dd172efec | ||
|
|
80ac82c8fb | ||
|
|
fd182f6d1e | ||
|
|
a05d0d5d94 | ||
|
|
0107ef2aad | ||
|
|
a153f21315 | ||
|
|
294aaf7a90 | ||
|
|
2024763d2a | ||
|
|
9526fdbe73 | ||
|
|
a5c4566fa1 | ||
|
|
2c05430002 | ||
|
|
643650dba7 | ||
|
|
8cb6854da4 | ||
|
|
83844ec239 | ||
|
|
1bfd25be35 | ||
|
|
cee216f2dc | ||
|
|
27c246e8d9 | ||
|
|
1ff39476eb | ||
|
|
acfd9a73bc | ||
|
|
b479a264b6 | ||
|
|
ac32f36e4e | ||
|
|
b8ccc885c8 | ||
|
|
4e2fb3fb89 | ||
|
|
9cd2129eeb | ||
|
|
097e200a97 | ||
|
|
bd28caed3c | ||
|
|
bc4266bef8 | ||
|
|
bc629c8a3e | ||
|
|
2e9e34aa40 | ||
|
|
c953d6409d | ||
|
|
447e4d3583 | ||
|
|
33bf6c0978 | ||
|
|
d69e0ab53a | ||
|
|
e7c267db4f | ||
|
|
6a5e7c118b | ||
|
|
bddb6b4273 | ||
|
|
7ec32704f9 | ||
|
|
8d217567c6 | ||
|
|
a17d814381 | ||
|
|
5554633ab8 | ||
|
|
8cd845b79e | ||
|
|
06da91a73b | ||
|
|
792010ba32 | ||
|
|
b69246c646 | ||
|
|
f4ea9b7393 | ||
|
|
1ff101c568 | ||
|
|
a93ffdd1be | ||
|
|
319149254d | ||
|
|
f7ce4f6239 | ||
|
|
28442cce9f | ||
|
|
f122e6d456 | ||
|
|
8391ac4cc8 | ||
|
|
898d885ae2 | ||
|
|
a61d0c9567 | ||
|
|
eea4edd92c | ||
|
|
cb331ae436 | ||
|
|
767e27c8f0 | ||
|
|
e78370e050 | ||
|
|
ea616b3ed4 | ||
|
|
a9dc0e816c | ||
|
|
4390d72b14 | ||
|
|
2bf740fc71 | ||
|
|
dbb1e732b8 | ||
|
|
7ec503c4ec | ||
|
|
a07ab27dae | ||
|
|
727c301fbc | ||
|
|
71f881d5cb | ||
|
|
d4e3fb4330 | ||
|
|
57f69a2915 | ||
|
|
448aefaace | ||
|
|
47b5e73a15 | ||
|
|
d6a6f428b3 | ||
|
|
fd3934b849 | ||
|
|
e5857cb722 | ||
|
|
2ec89c6304 | ||
|
|
76953a9748 | ||
|
|
128a5fa4a5 | ||
|
|
a5960c20cc | ||
|
|
aa71ce4cd5 | ||
|
|
16c981d425 | ||
|
|
81e56705ad | ||
|
|
ecb118f1ed | ||
|
|
1e663b1869 | ||
|
|
7013f9fc31 | ||
|
|
31cabc751d | ||
|
|
0478a8e288 | ||
|
|
1039d57251 | ||
|
|
934a38f976 | ||
|
|
8f261af5c1 | ||
|
|
3e8bdb9384 | ||
|
|
7542ec4f20 | ||
|
|
89ba540e6d | ||
|
|
47ab2ad6f3 | ||
|
|
06d5d4b03e | ||
|
|
f3739a73af | ||
|
|
1b227e0145 | ||
|
|
308b3f2337 | ||
|
|
1e56107967 | ||
|
|
b0e1a3d34c | ||
|
|
1cc4914b24 | ||
|
|
aec18c74ec | ||
|
|
4729d10bb2 | ||
|
|
a8af3ce0dd | ||
|
|
d657be33ba | ||
|
|
a86f9b8035 | ||
|
|
32f212cb86 | ||
|
|
fdc479676f | ||
|
|
4cdf62000c | ||
|
|
761771ad24 | ||
|
|
f5f4a9da6b | ||
|
|
eefef369ea | ||
|
|
4ebf53ffdf | ||
|
|
8b6a5d19d0 | ||
|
|
129920e8f4 | ||
|
|
dc3b96a003 | ||
|
|
5a0d77bea3 | ||
|
|
430b5b0490 | ||
|
|
cde9408bd8 | ||
|
|
85ce9aa7de | ||
|
|
f2ff2409ad | ||
|
|
bff4c54ece | ||
|
|
df0c26a394 | ||
|
|
1e56c7b862 | ||
|
|
4a0d4a02a6 | ||
|
|
4231ec51c3 | ||
|
|
f401c1059c | ||
|
|
e28a0e97b5 | ||
|
|
499ed62dd7 | ||
|
|
c3e02bec3b | ||
|
|
4363c49443 | ||
|
|
09571fcc55 | ||
|
|
c7563a5783 | ||
|
|
b06b4f937d | ||
|
|
86c6bb618b | ||
|
|
bff72634ca | ||
|
|
6f060081be | ||
|
|
7f865f722c | ||
|
|
614a011845 | ||
|
|
0c66832b3b | ||
|
|
f78366910e | ||
|
|
c426bbcf95 | ||
|
|
6d71094ce5 | ||
|
|
9322f04529 | ||
|
|
884201b919 | ||
|
|
a0afd0369f | ||
|
|
caf48ee973 | ||
|
|
0749dbfadf | ||
|
|
54889c72e3 | ||
|
|
cba126ceb8 | ||
|
|
c95663312a | ||
|
|
00ee3de7b2 | ||
|
|
172a1dbdb9 | ||
|
|
c5a662f405 | ||
|
|
9277950441 | ||
|
|
44c97a8f6d | ||
|
|
6a071efa27 | ||
|
|
bfed1c04cc | ||
|
|
bdb1965b50 | ||
|
|
c9d43b4d71 | ||
|
|
49ede85827 | ||
|
|
05693e2d5d | ||
|
|
c5ec035fb4 | ||
|
|
5e2f98fdad | ||
|
|
7b92750622 | ||
|
|
37754559b8 | ||
|
|
09008cb0ec | ||
|
|
b58aa1f5ee | ||
|
|
38e9205d4e | ||
|
|
16ff44ad30 | ||
|
|
1745e68795 | ||
|
|
809c96b53f | ||
|
|
03e43356ce | ||
|
|
d718a8b59d | ||
|
|
4100035b19 | ||
|
|
9bef46c0da | ||
|
|
c134adbcbf | ||
|
|
7bc8c7518b | ||
|
|
739984f920 | ||
|
|
7261739526 | ||
|
|
62f9df98b4 | ||
|
|
c76f71e8d6 | ||
|
|
f7cc5b2efd | ||
|
|
df7d7732c6 | ||
|
|
889b1c1eae | ||
|
|
297d749fc8 | ||
|
|
6233de0546 | ||
|
|
b1afa40fc1 | ||
|
|
a620e936cc | ||
|
|
d897170455 | ||
|
|
5a886da93b | ||
|
|
9206f54979 | ||
|
|
75c0a33ec5 | ||
|
|
8ae9b45da0 | ||
|
|
1eebbc746f | ||
|
|
adb2f66ced | ||
|
|
7246d67263 | ||
|
|
971fbe5d8e | ||
|
|
45df3e5e54 | ||
|
|
468b7d3aea | ||
|
|
0275690b5c | ||
|
|
da8095db54 | ||
|
|
5d74b1efef | ||
|
|
d3b5574d7a | ||
|
|
90472526e0 | ||
|
|
206392ad1a | ||
|
|
171a9ee291 | ||
|
|
13e725ab09 | ||
|
|
f5c2acf1d4 | ||
|
|
17e3054399 | ||
|
|
09aadffe9b | ||
|
|
e47bdfe8e6 | ||
|
|
cd9c0a6b3e | ||
|
|
c372c3756b | ||
|
|
78fa3f06f9 | ||
|
|
1350cd0e42 | ||
|
|
89d4f438c0 | ||
|
|
8bd7b5b607 | ||
|
|
d78be1ab49 | ||
|
|
86e279f886 | ||
|
|
d4914fc9ef | ||
|
|
46c19b8249 | ||
|
|
077b24d62d | ||
|
|
111c4dac63 | ||
|
|
69ff9d757f | ||
|
|
0c2ab17e91 | ||
|
|
12e82b9e33 | ||
|
|
644fc48776 | ||
|
|
666b1fae79 | ||
|
|
e3c436f411 | ||
|
|
48f3e13bec | ||
|
|
a923080d9b | ||
|
|
706614b0d7 | ||
|
|
a641f562f3 | ||
|
|
fe96bdf7e6 | ||
|
|
5b78b46a30 | ||
|
|
eacd604518 | ||
|
|
637c6a1850 | ||
|
|
0cbc4012e8 | ||
|
|
8074b82653 | ||
|
|
5d583c9b2d | ||
|
|
093e900d44 | ||
|
|
8965b66ce4 | ||
|
|
146add67c2 | ||
|
|
34e9a0a960 | ||
|
|
b1e95b1fa8 | ||
|
|
ce072b89d2 | ||
|
|
4ffd9bce5a | ||
|
|
f16d05c633 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -24,6 +24,7 @@ config.sub
|
||||
config_detected.h
|
||||
config_detected.mk
|
||||
configure
|
||||
configure.lineno
|
||||
depcomp
|
||||
depmode
|
||||
install-sh
|
||||
@@ -35,6 +36,7 @@ mpd
|
||||
stamp-h1
|
||||
tags
|
||||
*~
|
||||
.#*
|
||||
.stgit*
|
||||
doc/protocol.html
|
||||
doc/protocol
|
||||
@@ -43,10 +45,16 @@ doc/developer
|
||||
doc/sticker
|
||||
doc/api
|
||||
test/software_volume
|
||||
test/run_convert
|
||||
test/run_decoder
|
||||
test/read_tags
|
||||
test/run_filter
|
||||
test/run_encoder
|
||||
test/run_output
|
||||
test/read_conf
|
||||
test/run_input
|
||||
test/read_mixer
|
||||
test/dump_playlist
|
||||
test/run_normalize
|
||||
test/tmp
|
||||
test/run_inotify
|
||||
|
||||
3
AUTHORS
3
AUTHORS
@@ -19,9 +19,6 @@ Eric Wollesen <encoded@xmtp.net>
|
||||
Thomas Jansen <mithi@mithi.net>
|
||||
multithreading tweaks, miscellaneous
|
||||
|
||||
Rasmus Steinke <rasi1979@googlemail.com>
|
||||
documentation
|
||||
|
||||
Romain Bignon <romain@peerfuse.org>
|
||||
playlist manipulation
|
||||
|
||||
|
||||
14
INSTALL
14
INSTALL
@@ -13,7 +13,7 @@ Dependencies
|
||||
gcc - http://gcc.gnu.org/
|
||||
Any other C99 compliant compiler should also work.
|
||||
|
||||
glib - http://www.gtk.org/
|
||||
GLib 2.12 - http://www.gtk.org/
|
||||
General-purpose utility library.
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ libshout - http://www.icecast.org/
|
||||
For streaming to an Icecast or Shoutcast server.
|
||||
You also need an encoder: either libvorbisenc (ogg), or liblame (mp3).
|
||||
|
||||
OpenAL - http://kcat.strangesoft.net/openal.html
|
||||
Open Audio Library
|
||||
|
||||
|
||||
Optional Input Dependencies
|
||||
---------------------------
|
||||
@@ -69,6 +72,9 @@ MAD - http://www.underbit.com/products/mad/
|
||||
For MP3 support. You will need libmad, and optionally libid3tag if you want
|
||||
ID3 tag support.
|
||||
|
||||
libmpg123 - http://www.mpg123.de/
|
||||
Alternative for MP3 support.
|
||||
|
||||
Ogg Vorbis - http://www.xiph.org/ogg/vorbis/
|
||||
For Ogg Vorbis support. You will need libogg and libvorbis.
|
||||
|
||||
@@ -104,6 +110,12 @@ For MIDI support (DO NOT USE - use libwildmidi instead)
|
||||
libwildmidi - http://wildmidi.sourceforge.net/
|
||||
For MIDI support.
|
||||
|
||||
libsndfile - http://www.mega-nerd.com/libsndfile/
|
||||
WAVE, AIFF, and many others.
|
||||
|
||||
libwavpack - http://www.wavpack.com/
|
||||
For WavPack playback.
|
||||
|
||||
|
||||
Optional Miscellaneous Dependencies
|
||||
-----------------------------------
|
||||
|
||||
463
Makefile.am
463
Makefile.am
@@ -1,12 +1,15 @@
|
||||
ACLOCAL_AMFLAGS = -I m4
|
||||
AUTOMAKE_OPTIONS = foreign 1.9 dist-bzip2
|
||||
AUTOMAKE_OPTIONS = foreign 1.10 dist-bzip2 subdir-objects
|
||||
|
||||
AM_CPPFLAGS = -I$(srcdir)/src $(GLIB_CFLAGS)
|
||||
|
||||
AM_CPPFLAGS += -DSYSTEM_CONFIG_FILE_LOCATION='"$(sysconfdir)/mpd.conf"'
|
||||
|
||||
bin_PROGRAMS = src/mpd
|
||||
|
||||
src_mpd_CFLAGS = $(AM_CFLAGS) $(MPD_CFLAGS)
|
||||
src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(LIBWRAP_CFLAGS) \
|
||||
$(SQLITE_CFLAGS) \
|
||||
$(ARCHIVE_CFLAGS) \
|
||||
$(INPUT_CFLAGS) \
|
||||
@@ -16,6 +19,7 @@ src_mpd_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(FILTER_CFLAGS) \
|
||||
$(OUTPUT_CFLAGS)
|
||||
src_mpd_LDADD = $(MPD_LIBS) \
|
||||
$(LIBWRAP_LDFLAGS) \
|
||||
$(SQLITE_LIBS) \
|
||||
$(ARCHIVE_LIBS) \
|
||||
$(INPUT_LIBS) \
|
||||
@@ -27,10 +31,12 @@ src_mpd_LDADD = $(MPD_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
mpd_headers = \
|
||||
src/check.h \
|
||||
src/notify.h \
|
||||
src/ack.h \
|
||||
src/audio.h \
|
||||
src/audio_format.h \
|
||||
src/audio_check.h \
|
||||
src/audio_parser.h \
|
||||
src/output_internal.h \
|
||||
src/output_api.h \
|
||||
@@ -42,7 +48,15 @@ mpd_headers = \
|
||||
src/output_state.h \
|
||||
src/output_print.h \
|
||||
src/output_command.h \
|
||||
src/buffer2array.h \
|
||||
src/filter_internal.h \
|
||||
src/filter_config.h \
|
||||
src/filter_plugin.h \
|
||||
src/filter_registry.h \
|
||||
src/filter/autoconvert_filter_plugin.h \
|
||||
src/filter/chain_filter_plugin.h \
|
||||
src/filter/convert_filter_plugin.h \
|
||||
src/filter/replay_gain_filter_plugin.h \
|
||||
src/filter/volume_filter_plugin.h \
|
||||
src/command.h \
|
||||
src/idle.h \
|
||||
src/cmdline.h \
|
||||
@@ -64,23 +78,39 @@ mpd_headers = \
|
||||
src/encoder_plugin.h \
|
||||
src/encoder_list.h \
|
||||
src/encoder_api.h \
|
||||
src/exclude.h \
|
||||
src/fd_util.h \
|
||||
src/fifo_buffer.h \
|
||||
src/glib_compat.h \
|
||||
src/update.h \
|
||||
src/update_internal.h \
|
||||
src/inotify_source.h \
|
||||
src/inotify_queue.h \
|
||||
src/inotify_update.h \
|
||||
src/dirvec.h \
|
||||
src/gcc.h \
|
||||
src/decoder_list.h \
|
||||
src/decoder_print.h \
|
||||
src/decoder/flac_compat.h \
|
||||
src/decoder/flac_metadata.h \
|
||||
src/decoder/flac_pcm.h \
|
||||
src/decoder/_flac_common.h \
|
||||
src/decoder/_ogg_common.h \
|
||||
src/input_init.h \
|
||||
src/input_plugin.h \
|
||||
src/input_registry.h \
|
||||
src/input_stream.h \
|
||||
src/input/file_input_plugin.h \
|
||||
src/input/ffmpeg_input_plugin.h \
|
||||
src/input/curl_input_plugin.h \
|
||||
src/input/rewind_input_plugin.h \
|
||||
src/input/lastfm_input_plugin.h \
|
||||
src/input/mms_input_plugin.h \
|
||||
src/text_file.h \
|
||||
src/text_input_stream.h \
|
||||
src/icy_server.h \
|
||||
src/icy_metadata.h \
|
||||
src/client.h \
|
||||
src/client_internal.h \
|
||||
src/listen.h \
|
||||
src/log.h \
|
||||
src/ls.h \
|
||||
@@ -91,27 +121,34 @@ mpd_headers = \
|
||||
src/mixer_list.h \
|
||||
src/event_pipe.h \
|
||||
src/mixer_plugin.h \
|
||||
src/mixer_type.h \
|
||||
src/mixer/software_mixer_plugin.h \
|
||||
src/mixer/pulse_mixer_plugin.h \
|
||||
src/daemon.h \
|
||||
src/normalize.h \
|
||||
src/compress.h \
|
||||
src/AudioCompress/config.h \
|
||||
src/AudioCompress/compress.h \
|
||||
src/buffer.h \
|
||||
src/pipe.h \
|
||||
src/chunk.h \
|
||||
src/path.h \
|
||||
src/mapper.h \
|
||||
src/open.h \
|
||||
src/output/httpd_client.h \
|
||||
src/output/httpd_internal.h \
|
||||
src/output/pulse_output_plugin.h \
|
||||
src/page.h \
|
||||
src/pcm_buffer.h \
|
||||
src/pcm_utils.h \
|
||||
src/pcm_convert.h \
|
||||
src/pcm_volume.h \
|
||||
src/pcm_mix.h \
|
||||
src/pcm_byteswap.h \
|
||||
src/pcm_channels.h \
|
||||
src/pcm_format.h \
|
||||
src/pcm_resample.h \
|
||||
src/pcm_resample_internal.h \
|
||||
src/pcm_dither.h \
|
||||
src/pcm_pack.h \
|
||||
src/pcm_prng.h \
|
||||
src/permission.h \
|
||||
src/player_thread.h \
|
||||
@@ -121,13 +158,31 @@ mpd_headers = \
|
||||
src/playlist_print.h \
|
||||
src/playlist_save.h \
|
||||
src/playlist_state.h \
|
||||
src/playlist_plugin.h \
|
||||
src/playlist_list.h \
|
||||
src/playlist_mapper.h \
|
||||
src/playlist_any.h \
|
||||
src/playlist_song.h \
|
||||
src/playlist_queue.h \
|
||||
src/playlist_vector.h \
|
||||
src/playlist_database.h \
|
||||
src/playlist/extm3u_playlist_plugin.h \
|
||||
src/playlist/m3u_playlist_plugin.h \
|
||||
src/playlist/pls_playlist_plugin.h \
|
||||
src/playlist/xspf_playlist_plugin.h \
|
||||
src/playlist/asx_playlist_plugin.h \
|
||||
src/playlist/lastfm_playlist_plugin.h \
|
||||
src/playlist/cue_playlist_plugin.h \
|
||||
src/playlist/flac_playlist_plugin.h \
|
||||
src/poison.h \
|
||||
src/riff.h \
|
||||
src/aiff.h \
|
||||
src/queue.h \
|
||||
src/queue_print.h \
|
||||
src/queue_save.h \
|
||||
src/replay_gain.h \
|
||||
src/refcount.h \
|
||||
src/replay_gain_config.h \
|
||||
src/replay_gain_info.h \
|
||||
src/sig_handlers.h \
|
||||
src/song.h \
|
||||
src/song_print.h \
|
||||
@@ -142,10 +197,13 @@ mpd_headers = \
|
||||
src/tag.h \
|
||||
src/tag_internal.h \
|
||||
src/tag_pool.h \
|
||||
src/tag_table.h \
|
||||
src/tag_ape.h \
|
||||
src/tag_id3.h \
|
||||
src/tag_rva2.h \
|
||||
src/tag_print.h \
|
||||
src/tag_save.h \
|
||||
src/tokenizer.h \
|
||||
src/strset.h \
|
||||
src/uri.h \
|
||||
src/utils.h \
|
||||
@@ -157,6 +215,10 @@ mpd_headers = \
|
||||
src/archive_api.h \
|
||||
src/archive_internal.h \
|
||||
src/archive_list.h \
|
||||
src/archive_plugin.h \
|
||||
src/archive/bz2_archive_plugin.h \
|
||||
src/archive/iso9660_archive_plugin.h \
|
||||
src/archive/zzip_archive_plugin.h \
|
||||
src/input/archive_input_plugin.h \
|
||||
src/cue/cue_tag.h
|
||||
|
||||
@@ -164,15 +226,18 @@ src_mpd_SOURCES = \
|
||||
$(mpd_headers) \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC) \
|
||||
$(PLAYLIST_SRC) \
|
||||
$(TAG_SRC) \
|
||||
$(DECODER_SRC) \
|
||||
$(ENCODER_SRC) \
|
||||
$(OUTPUT_API_SRC) $(OUTPUT_SRC) \
|
||||
$(MIXER_API_SRC) $(MIXER_SRC) \
|
||||
$(FILTER_SRC) \
|
||||
src/notify.c \
|
||||
src/audio.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/audio_parser.c \
|
||||
src/buffer2array.c \
|
||||
src/command.c \
|
||||
src/idle.c \
|
||||
src/cmdline.c \
|
||||
@@ -183,22 +248,39 @@ src_mpd_SOURCES = \
|
||||
src/decoder_control.c \
|
||||
src/decoder_api.c \
|
||||
src/decoder_internal.c \
|
||||
src/decoder_print.c \
|
||||
src/directory.c \
|
||||
src/directory_save.c \
|
||||
src/directory_print.c \
|
||||
src/database.c \
|
||||
src/dirvec.c \
|
||||
src/exclude.c \
|
||||
src/fd_util.c \
|
||||
src/fifo_buffer.c \
|
||||
src/filter_config.c \
|
||||
src/filter_plugin.c \
|
||||
src/filter_registry.c \
|
||||
src/update.c \
|
||||
src/update_queue.c \
|
||||
src/update_walk.c \
|
||||
src/update_remove.c \
|
||||
src/client.c \
|
||||
src/client_event.c \
|
||||
src/client_expire.c \
|
||||
src/client_global.c \
|
||||
src/client_idle.c \
|
||||
src/client_list.c \
|
||||
src/client_new.c \
|
||||
src/client_process.c \
|
||||
src/client_read.c \
|
||||
src/client_write.c \
|
||||
src/listen.c \
|
||||
src/log.c \
|
||||
src/ls.c \
|
||||
src/main.c \
|
||||
src/event_pipe.c \
|
||||
src/daemon.c \
|
||||
src/normalize.c \
|
||||
src/compress.c \
|
||||
src/AudioCompress/compress.c \
|
||||
src/buffer.c \
|
||||
src/pipe.c \
|
||||
src/chunk.c \
|
||||
@@ -208,7 +290,9 @@ src_mpd_SOURCES = \
|
||||
src/pcm_convert.c \
|
||||
src/pcm_volume.c \
|
||||
src/pcm_mix.c \
|
||||
src/pcm_byteswap.c \
|
||||
src/pcm_channels.c \
|
||||
src/pcm_pack.c \
|
||||
src/pcm_format.c \
|
||||
src/pcm_resample.c \
|
||||
src/pcm_resample_fallback.c \
|
||||
@@ -222,13 +306,21 @@ src_mpd_SOURCES = \
|
||||
src/playlist_edit.c \
|
||||
src/playlist_print.c \
|
||||
src/playlist_save.c \
|
||||
src/playlist_mapper.c \
|
||||
src/playlist_any.c \
|
||||
src/playlist_song.c \
|
||||
src/playlist_state.c \
|
||||
src/playlist_queue.c \
|
||||
src/playlist_vector.c \
|
||||
src/playlist_database.c \
|
||||
src/queue.c \
|
||||
src/queue_print.c \
|
||||
src/queue_save.c \
|
||||
src/replay_gain.c \
|
||||
src/replay_gain_config.c \
|
||||
src/replay_gain_info.c \
|
||||
src/sig_handlers.c \
|
||||
src/song.c \
|
||||
src/song_update.c \
|
||||
src/song_print.c \
|
||||
src/song_save.c \
|
||||
src/songvec.c \
|
||||
@@ -239,6 +331,9 @@ src_mpd_SOURCES = \
|
||||
src/tag_pool.c \
|
||||
src/tag_print.c \
|
||||
src/tag_save.c \
|
||||
src/tokenizer.c \
|
||||
src/text_file.c \
|
||||
src/text_input_stream.c \
|
||||
src/strset.c \
|
||||
src/uri.c \
|
||||
src/utils.c \
|
||||
@@ -247,6 +342,13 @@ src_mpd_SOURCES = \
|
||||
src/stored_playlist.c \
|
||||
src/timer.c
|
||||
|
||||
if ENABLE_INOTIFY
|
||||
src_mpd_SOURCES += \
|
||||
src/inotify_source.c \
|
||||
src/inotify_queue.c \
|
||||
src/inotify_update.c
|
||||
endif
|
||||
|
||||
if ENABLE_SQLITE
|
||||
src_mpd_SOURCES += \
|
||||
src/sticker.c \
|
||||
@@ -276,21 +378,22 @@ ARCHIVE_LIBS = \
|
||||
ARCHIVE_SRC =
|
||||
|
||||
if HAVE_BZ2
|
||||
ARCHIVE_SRC += src/archive/bz2_plugin.c
|
||||
ARCHIVE_SRC += src/archive/bz2_archive_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_ZIP
|
||||
ARCHIVE_SRC += src/archive/zip_plugin.c
|
||||
if HAVE_ZZIP
|
||||
ARCHIVE_SRC += src/archive/zzip_archive_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_ISO
|
||||
ARCHIVE_SRC += src/archive/iso_plugin.c
|
||||
if HAVE_ISO9660
|
||||
ARCHIVE_SRC += src/archive/iso9660_archive_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_ARCHIVE
|
||||
ARCHIVE_SRC += \
|
||||
src/archive_api.c \
|
||||
src/archive_list.c \
|
||||
src/archive_plugin.c \
|
||||
src/input/archive_input_plugin.c
|
||||
endif
|
||||
|
||||
@@ -307,6 +410,7 @@ TAG_SRC = \
|
||||
|
||||
if HAVE_ID3TAG
|
||||
TAG_SRC += src/tag_id3.c \
|
||||
src/tag_rva2.c \
|
||||
src/riff.c src/aiff.c
|
||||
endif
|
||||
|
||||
@@ -315,51 +419,64 @@ endif
|
||||
DECODER_CFLAGS = \
|
||||
$(VORBIS_CFLAGS) $(TREMOR_CFLAGS) \
|
||||
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
|
||||
$(SNDFILE_CFLAGS) \
|
||||
$(AUDIOFILE_CFLAGS) \
|
||||
$(LIBMIKMOD_CFLAGS) \
|
||||
$(MODPLUG_CFLAGS) \
|
||||
$(GME_CFLAGS) \
|
||||
$(SIDPLAY_CFLAGS) \
|
||||
$(FLUIDSYNTH_CFLAGS) \
|
||||
$(WILDMIDI_CFLAGS) \
|
||||
$(WAVPACK_CFLAGS) \
|
||||
$(MAD_CFLAGS) \
|
||||
$(MPG123_CFLAGS) \
|
||||
$(FFMPEG_CFLAGS) \
|
||||
$(CUE_CFLAGS)
|
||||
|
||||
DECODER_LIBS = \
|
||||
$(VORBIS_LIBS) $(TREMOR_LIBS) \
|
||||
$(FLAC_LIBS) \
|
||||
$(SNDFILE_LIBS) \
|
||||
$(AUDIOFILE_LIBS) $(LIBMIKMOD_LIBS) \
|
||||
$(MODPLUG_LIBS) \
|
||||
$(GME_LIBS) \
|
||||
$(SIDPLAY_LIBS) \
|
||||
$(FLUIDSYNTH_LIBS) \
|
||||
$(WILDMIDI_LIBS) \
|
||||
$(WAVPACK_LIBS) \
|
||||
$(MAD_LIBS) \
|
||||
$(MPG123_LIBS) \
|
||||
$(MP4FF_LIBS) \
|
||||
$(FFMPEG_LIBS) \
|
||||
$(CUE_LIBS)
|
||||
|
||||
DECODER_SRC = \
|
||||
src/decoder_buffer.c \
|
||||
src/decoder_plugin.c \
|
||||
src/decoder_list.c
|
||||
|
||||
if HAVE_MAD
|
||||
DECODER_SRC += src/decoder/mad_plugin.c
|
||||
DECODER_SRC += src/decoder/mad_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_MPG123
|
||||
DECODER_SRC += src/decoder/mpg123_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_MPCDEC
|
||||
DECODER_SRC += src/decoder/mpcdec_plugin.c
|
||||
DECODER_SRC += src/decoder/mpcdec_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_WAVPACK
|
||||
DECODER_SRC += src/decoder/wavpack_plugin.c
|
||||
DECODER_SRC += src/decoder/wavpack_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_FAAD
|
||||
DECODER_SRC += src/decoder/faad_plugin.c
|
||||
DECODER_SRC += src/decoder/faad_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_MP4
|
||||
DECODER_SRC += src/decoder/mp4ff_plugin.c
|
||||
DECODER_SRC += src/decoder/mp4ff_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_OGG_COMMON
|
||||
@@ -367,63 +484,83 @@ DECODER_SRC += src/decoder/_ogg_common.c
|
||||
endif
|
||||
|
||||
if HAVE_FLAC_COMMON
|
||||
DECODER_SRC += src/decoder/_flac_common.c
|
||||
DECODER_SRC += \
|
||||
src/decoder/flac_metadata.c \
|
||||
src/decoder/flac_pcm.c \
|
||||
src/decoder/_flac_common.c
|
||||
endif
|
||||
|
||||
if ENABLE_VORBIS_DECODER
|
||||
DECODER_SRC += src/decoder/vorbis_plugin.c
|
||||
DECODER_SRC += src/decoder/vorbis_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_FLAC
|
||||
DECODER_SRC += src/decoder/flac_plugin.c
|
||||
DECODER_SRC += src/decoder/flac_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_OGGFLAC
|
||||
DECODER_SRC += src/decoder/oggflac_plugin.c
|
||||
DECODER_SRC += src/decoder/oggflac_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_AUDIOFILE
|
||||
DECODER_SRC += src/decoder/audiofile_plugin.c
|
||||
DECODER_SRC += src/decoder/audiofile_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_MIKMOD_DECODER
|
||||
DECODER_SRC += src/decoder/mikmod_plugin.c
|
||||
DECODER_SRC += src/decoder/mikmod_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_MODPLUG
|
||||
DECODER_SRC += src/decoder/modplug_plugin.c
|
||||
DECODER_SRC += src/decoder/modplug_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_SIDPLAY
|
||||
DECODER_SRC += src/decoder/sidplay_plugin.cxx
|
||||
DECODER_SRC += src/decoder/sidplay_decoder_plugin.cxx
|
||||
endif
|
||||
|
||||
if ENABLE_FLUIDSYNTH
|
||||
DECODER_SRC += src/decoder/fluidsynth_plugin.c
|
||||
DECODER_SRC += src/decoder/fluidsynth_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_WILDMIDI
|
||||
DECODER_SRC += src/decoder/wildmidi_plugin.c
|
||||
DECODER_SRC += src/decoder/wildmidi_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_FFMPEG
|
||||
DECODER_SRC += src/decoder/ffmpeg_plugin.c
|
||||
DECODER_SRC += src/decoder/ffmpeg_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_SNDFILE
|
||||
DECODER_SRC += src/decoder/sndfile_decoder_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_GME
|
||||
DECODER_SRC += src/decoder/gme_decoder_plugin.c
|
||||
endif
|
||||
|
||||
# encoder plugins
|
||||
|
||||
ENCODER_CFLAGS = \
|
||||
$(LAME_CFLAGS) \
|
||||
$(TWOLAME_CFLAGS) \
|
||||
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
|
||||
$(VORBISENC_CFLAGS)
|
||||
|
||||
ENCODER_LIBS = \
|
||||
$(LAME_LIBS) \
|
||||
$(TWOLAME_LIBS) \
|
||||
$(FLAC_LIBS) \
|
||||
$(VORBISENC_LIBS)
|
||||
|
||||
ENCODER_SRC =
|
||||
|
||||
if ENABLE_ENCODER
|
||||
ENCODER_SRC += src/encoder_list.c
|
||||
ENCODER_SRC += src/encoder/null_encoder.c
|
||||
|
||||
if ENABLE_WAVE_ENCODER
|
||||
ENCODER_SRC += src/encoder/wave_encoder.c
|
||||
endif
|
||||
|
||||
if ENABLE_VORBIS_ENCODER
|
||||
ENCODER_SRC += src/encoder/vorbis_encoder.c
|
||||
@@ -432,6 +569,14 @@ endif
|
||||
if ENABLE_LAME_ENCODER
|
||||
ENCODER_SRC += src/encoder/lame_encoder.c
|
||||
endif
|
||||
|
||||
if ENABLE_TWOLAME_ENCODER
|
||||
ENCODER_SRC += src/encoder/twolame_encoder.c
|
||||
endif
|
||||
|
||||
if ENABLE_FLAC_ENCODER
|
||||
ENCODER_SRC += src/encoder/flac_encoder.c
|
||||
endif
|
||||
endif
|
||||
|
||||
|
||||
@@ -457,24 +602,28 @@ endif
|
||||
|
||||
INPUT_CFLAGS = \
|
||||
$(CURL_CFLAGS) \
|
||||
$(FFMPEG_CFLAGS) \
|
||||
$(MMS_CFLAGS)
|
||||
|
||||
INPUT_LIBS = \
|
||||
$(CURL_LIBS) \
|
||||
$(FFMPEG_LIBS) \
|
||||
$(MMS_LIBS)
|
||||
|
||||
INPUT_SRC = \
|
||||
src/input_init.c \
|
||||
src/input_registry.c \
|
||||
src/input_stream.c \
|
||||
src/input/rewind_input_plugin.c \
|
||||
src/input/file_input_plugin.c
|
||||
|
||||
if HAVE_CURL
|
||||
if ENABLE_CURL
|
||||
INPUT_SRC += src/input/curl_input_plugin.c \
|
||||
src/input/rewind_input_plugin.c \
|
||||
src/icy_metadata.c
|
||||
endif
|
||||
|
||||
if ENABLE_LASTFM
|
||||
INPUT_SRC += src/input/lastfm_input_plugin.c
|
||||
if HAVE_FFMPEG
|
||||
INPUT_SRC += src/input/ffmpeg_input_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_MMS
|
||||
@@ -486,13 +635,16 @@ OUTPUT_CFLAGS = \
|
||||
$(AO_CFLAGS) \
|
||||
$(ALSA_CFLAGS) \
|
||||
$(JACK_CFLAGS) \
|
||||
$(OPENAL_CFLAGS) \
|
||||
$(PULSE_CFLAGS) \
|
||||
$(SHOUT_CFLAGS)
|
||||
|
||||
OUTPUT_LIBS = \
|
||||
$(LIBWRAP_LDFLAGS) \
|
||||
$(AO_LIBS) \
|
||||
$(ALSA_LIBS) \
|
||||
$(JACK_LIBS) \
|
||||
$(OPENAL_LIBS) \
|
||||
$(PULSE_LIBS) \
|
||||
$(SHOUT_LIBS)
|
||||
|
||||
@@ -511,14 +663,16 @@ OUTPUT_SRC = \
|
||||
|
||||
MIXER_API_SRC = \
|
||||
src/mixer_control.c \
|
||||
src/mixer_type.c \
|
||||
src/mixer_all.c \
|
||||
src/mixer_api.c
|
||||
|
||||
MIXER_SRC =
|
||||
MIXER_SRC = \
|
||||
src/mixer/software_mixer_plugin.c
|
||||
|
||||
if HAVE_ALSA
|
||||
OUTPUT_SRC += src/output/alsa_plugin.c
|
||||
MIXER_SRC += src/mixer/alsa_mixer.c
|
||||
MIXER_SRC += src/mixer/alsa_mixer_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_AO
|
||||
@@ -526,7 +680,7 @@ OUTPUT_SRC += src/output/ao_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_FIFO
|
||||
OUTPUT_SRC += src/output/fifo_plugin.c
|
||||
OUTPUT_SRC += src/output/fifo_output_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_PIPE_OUTPUT
|
||||
@@ -534,7 +688,7 @@ OUTPUT_SRC += src/output/pipe_output_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_JACK
|
||||
OUTPUT_SRC += src/output/jack_plugin.c
|
||||
OUTPUT_SRC += src/output/jack_output_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_MVP
|
||||
@@ -543,7 +697,11 @@ endif
|
||||
|
||||
if HAVE_OSS
|
||||
OUTPUT_SRC += src/output/oss_plugin.c
|
||||
MIXER_SRC += src/mixer/oss_mixer.c
|
||||
MIXER_SRC += src/mixer/oss_mixer_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_OPENAL
|
||||
OUTPUT_SRC += src/output/openal_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_OSX
|
||||
@@ -551,14 +709,18 @@ OUTPUT_SRC += src/output/osx_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_PULSE
|
||||
OUTPUT_SRC += src/output/pulse_plugin.c
|
||||
MIXER_SRC += src/mixer/pulse_mixer.c
|
||||
OUTPUT_SRC += src/output/pulse_output_plugin.c
|
||||
MIXER_SRC += src/mixer/pulse_mixer_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_SHOUT
|
||||
OUTPUT_SRC += src/output/shout_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_RECORDER_OUTPUT
|
||||
OUTPUT_SRC += src/output/recorder_output_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_HTTPD_OUTPUT
|
||||
OUTPUT_SRC += \
|
||||
src/icy_server.c \
|
||||
@@ -570,6 +732,50 @@ if ENABLE_SOLARIS_OUTPUT
|
||||
OUTPUT_SRC += src/output/solaris_output_plugin.c
|
||||
endif
|
||||
|
||||
if ENABLE_WIN32_OUTPUT
|
||||
OUTPUT_SRC += src/output/win32_output_plugin.c
|
||||
endif
|
||||
|
||||
|
||||
#
|
||||
# Playlist plugins
|
||||
#
|
||||
|
||||
PLAYLIST_SRC = \
|
||||
src/playlist/extm3u_playlist_plugin.c \
|
||||
src/playlist/m3u_playlist_plugin.c \
|
||||
src/playlist/pls_playlist_plugin.c \
|
||||
src/playlist/xspf_playlist_plugin.c \
|
||||
src/playlist/asx_playlist_plugin.c \
|
||||
src/playlist_list.c
|
||||
|
||||
if ENABLE_LASTFM
|
||||
PLAYLIST_SRC += src/playlist/lastfm_playlist_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_CUE
|
||||
PLAYLIST_SRC += src/playlist/cue_playlist_plugin.c
|
||||
endif
|
||||
|
||||
if HAVE_FLAC
|
||||
PLAYLIST_SRC += src/playlist/flac_playlist_plugin.c
|
||||
endif
|
||||
|
||||
|
||||
#
|
||||
# Filter plugins
|
||||
#
|
||||
|
||||
FILTER_SRC = \
|
||||
src/filter/null_filter_plugin.c \
|
||||
src/filter/chain_filter_plugin.c \
|
||||
src/filter/autoconvert_filter_plugin.c \
|
||||
src/filter/convert_filter_plugin.c \
|
||||
src/filter/route_filter_plugin.c \
|
||||
src/filter/normalize_filter_plugin.c \
|
||||
src/filter/replay_gain_filter_plugin.c \
|
||||
src/filter/volume_filter_plugin.c
|
||||
|
||||
|
||||
#
|
||||
# Sparse code analysis
|
||||
@@ -597,21 +803,31 @@ sparse-check:
|
||||
|
||||
if ENABLE_TEST
|
||||
|
||||
TESTS =
|
||||
|
||||
noinst_PROGRAMS = \
|
||||
test/read_conf \
|
||||
test/run_input \
|
||||
test/dump_playlist \
|
||||
test/run_decoder \
|
||||
test/read_tags \
|
||||
test/run_filter \
|
||||
test/run_output \
|
||||
test/read_mixer \
|
||||
test/run_convert \
|
||||
test/run_normalize \
|
||||
test/software_volume
|
||||
|
||||
if HAVE_ALSA
|
||||
# this debug program is still ALSA specific
|
||||
noinst_PROGRAMS += test/read_mixer
|
||||
endif
|
||||
|
||||
test_read_conf_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(GLIB_CFLAGS)
|
||||
test_read_conf_LDADD = $(MPD_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_read_conf_SOURCES = test/read_conf.c \
|
||||
src/conf.c src/buffer2array.c src/utils.c
|
||||
src/conf.c src/tokenizer.c src/utils.c
|
||||
|
||||
test_run_input_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(ARCHIVE_CFLAGS) \
|
||||
@@ -621,11 +837,43 @@ test_run_input_LDADD = $(MPD_LIBS) \
|
||||
$(INPUT_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_run_input_SOURCES = test/run_input.c \
|
||||
src/conf.c src/buffer2array.c src/utils.c \
|
||||
src/conf.c src/tokenizer.c src/utils.c \
|
||||
src/tag.c src/tag_pool.c src/tag_save.c \
|
||||
src/fd_util.c \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC)
|
||||
|
||||
test_dump_playlist_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(CUE_CFLAGS) \
|
||||
$(patsubst -I%/FLAC,-I%,$(FLAC_CFLAGS)) \
|
||||
$(ARCHIVE_CFLAGS) \
|
||||
$(INPUT_CFLAGS)
|
||||
test_dump_playlist_LDADD = $(MPD_LIBS) \
|
||||
$(CUE_LIBS) \
|
||||
$(FLAC_LIBS) \
|
||||
$(ARCHIVE_LIBS) \
|
||||
$(INPUT_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_dump_playlist_SOURCES = test/dump_playlist.c \
|
||||
src/conf.c src/tokenizer.c src/utils.c \
|
||||
src/uri.c \
|
||||
src/song.c src/tag.c src/tag_pool.c src/tag_save.c \
|
||||
src/text_input_stream.c src/fifo_buffer.c \
|
||||
src/fd_util.c \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC) \
|
||||
$(PLAYLIST_SRC)
|
||||
|
||||
if HAVE_CUE
|
||||
test_dump_playlist_SOURCES += src/cue/cue_tag.c
|
||||
endif
|
||||
|
||||
if HAVE_FLAC
|
||||
test_dump_playlist_SOURCES += \
|
||||
src/replay_gain_info.c \
|
||||
src/decoder/flac_metadata.c
|
||||
endif
|
||||
|
||||
test_run_decoder_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(TAG_CFLAGS) \
|
||||
$(ARCHIVE_CFLAGS) \
|
||||
@@ -636,10 +884,13 @@ test_run_decoder_LDADD = $(MPD_LIBS) \
|
||||
$(INPUT_LIBS) $(DECODER_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_run_decoder_SOURCES = test/run_decoder.c \
|
||||
src/conf.c src/buffer2array.c src/utils.c src/log.c \
|
||||
src/conf.c src/tokenizer.c src/utils.c src/log.c \
|
||||
src/tag.c src/tag_pool.c \
|
||||
src/replay_gain.c \
|
||||
src/replay_gain_info.c \
|
||||
src/uri.c \
|
||||
src/fd_util.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/timer.c \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC) \
|
||||
@@ -656,22 +907,50 @@ test_read_tags_LDADD = $(MPD_LIBS) \
|
||||
$(INPUT_LIBS) $(DECODER_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_read_tags_SOURCES = test/read_tags.c \
|
||||
src/conf.c src/buffer2array.c src/utils.c src/log.c \
|
||||
src/conf.c src/tokenizer.c src/utils.c src/log.c \
|
||||
src/tag.c src/tag_pool.c \
|
||||
src/replay_gain.c \
|
||||
src/replay_gain_info.c \
|
||||
src/uri.c \
|
||||
src/fd_util.c \
|
||||
src/audio_check.c \
|
||||
src/timer.c \
|
||||
$(ARCHIVE_SRC) \
|
||||
$(INPUT_SRC) \
|
||||
$(TAG_SRC) \
|
||||
$(DECODER_SRC)
|
||||
|
||||
test_run_filter_CPPFLAGS = $(AM_CPPFLAGS)
|
||||
test_run_filter_LDADD = $(MPD_LIBS) \
|
||||
$(SAMPLERATE_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_run_filter_SOURCES = test/run_filter.c \
|
||||
src/filter_plugin.c \
|
||||
src/filter_registry.c \
|
||||
src/conf.c src/tokenizer.c src/utils.c \
|
||||
src/pcm_volume.c src/pcm_convert.c src/pcm_byteswap.c \
|
||||
src/pcm_format.c src/pcm_channels.c src/pcm_dither.c \
|
||||
src/pcm_pack.c \
|
||||
src/pcm_resample.c src/pcm_resample_fallback.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/audio_parser.c \
|
||||
src/replay_gain_config.c \
|
||||
src/replay_gain_info.c \
|
||||
src/AudioCompress/compress.c \
|
||||
$(FILTER_SRC)
|
||||
|
||||
if HAVE_LIBSAMPLERATE
|
||||
test_run_filter_SOURCES += src/pcm_resample_libsamplerate.c
|
||||
endif
|
||||
|
||||
if ENABLE_ENCODER
|
||||
noinst_PROGRAMS += test/run_encoder
|
||||
test_run_encoder_SOURCES = test/run_encoder.c \
|
||||
src/conf.c src/buffer2array.c \
|
||||
src/conf.c src/tokenizer.c \
|
||||
src/utils.c \
|
||||
src/tag.c src/tag_pool.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/audio_parser.c \
|
||||
$(ENCODER_SRC)
|
||||
test_run_encoder_LDADD = $(MPD_LIBS) \
|
||||
@@ -680,11 +959,41 @@ test_run_encoder_LDADD = $(MPD_LIBS) \
|
||||
endif
|
||||
|
||||
test_software_volume_SOURCES = test/software_volume.c \
|
||||
src/audio_check.c \
|
||||
src/audio_parser.c \
|
||||
src/pcm_volume.c
|
||||
test_software_volume_LDADD = \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
test_run_normalize_SOURCES = test/run_normalize.c \
|
||||
src/audio_check.c \
|
||||
src/audio_parser.c \
|
||||
src/AudioCompress/compress.c
|
||||
test_run_normalize_LDADD = \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
test_run_convert_SOURCES = test/run_convert.c \
|
||||
src/fifo_buffer.c \
|
||||
src/audio_format.c \
|
||||
src/audio_check.c \
|
||||
src/audio_parser.c \
|
||||
src/pcm_channels.c \
|
||||
src/pcm_format.c \
|
||||
src/pcm_pack.c \
|
||||
src/pcm_dither.c \
|
||||
src/pcm_byteswap.c \
|
||||
src/pcm_resample.c \
|
||||
src/pcm_resample_fallback.c \
|
||||
src/pcm_convert.c
|
||||
test_run_convert_CPPFLAGS = $(AM_CPPFLAGS) $(SAMPLERATE_CFLAGS)
|
||||
test_run_convert_LDADD = \
|
||||
$(SAMPLERATE_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
|
||||
if HAVE_LIBSAMPLERATE
|
||||
test_run_convert_SOURCES += src/pcm_resample_libsamplerate.c
|
||||
endif
|
||||
|
||||
test_run_output_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
$(ENCODER_CFLAGS) \
|
||||
$(OUTPUT_CFLAGS)
|
||||
@@ -693,7 +1002,9 @@ test_run_output_LDADD = $(MPD_LIBS) \
|
||||
$(OUTPUT_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_run_output_SOURCES = test/run_output.c \
|
||||
src/conf.c src/buffer2array.c src/utils.c src/log.c \
|
||||
src/conf.c src/tokenizer.c src/utils.c src/log.c \
|
||||
src/audio_check.c \
|
||||
src/audio_format.c \
|
||||
src/audio_parser.c \
|
||||
src/timer.c \
|
||||
src/tag.c src/tag_pool.c \
|
||||
@@ -704,7 +1015,20 @@ test_run_output_SOURCES = test/run_output.c \
|
||||
$(ENCODER_SRC) \
|
||||
src/mixer_api.c \
|
||||
src/mixer_control.c \
|
||||
src/mixer_type.c \
|
||||
$(MIXER_SRC) \
|
||||
src/filter_plugin.c src/filter/chain_filter_plugin.c \
|
||||
src/filter_config.c \
|
||||
src/filter/autoconvert_filter_plugin.c \
|
||||
src/filter/convert_filter_plugin.c \
|
||||
src/filter/replay_gain_filter_plugin.c \
|
||||
src/filter/normalize_filter_plugin.c \
|
||||
src/filter/volume_filter_plugin.c \
|
||||
src/pcm_volume.c \
|
||||
src/AudioCompress/compress.c \
|
||||
src/replay_gain_info.c \
|
||||
src/replay_gain_config.c \
|
||||
src/fd_util.c \
|
||||
$(OUTPUT_SRC)
|
||||
|
||||
test_read_mixer_CPPFLAGS = $(AM_CPPFLAGS) \
|
||||
@@ -713,10 +1037,34 @@ test_read_mixer_LDADD = $(MPD_LIBS) \
|
||||
$(OUTPUT_LIBS) \
|
||||
$(GLIB_LIBS)
|
||||
test_read_mixer_SOURCES = test/read_mixer.c \
|
||||
src/conf.c src/buffer2array.c src/utils.c src/log.c \
|
||||
src/conf.c src/tokenizer.c src/utils.c src/log.c \
|
||||
src/mixer_control.c src/mixer_api.c \
|
||||
src/filter_plugin.c \
|
||||
src/filter/volume_filter_plugin.c \
|
||||
src/fd_util.c \
|
||||
$(MIXER_SRC)
|
||||
|
||||
if ENABLE_BZIP2_TEST
|
||||
TESTS += test/test_archive_bzip2.sh
|
||||
endif
|
||||
|
||||
if ENABLE_ZZIP_TEST
|
||||
TESTS += test/test_archive_zzip.sh
|
||||
endif
|
||||
|
||||
if ENABLE_ISO9660_TEST
|
||||
TESTS += test/test_archive_iso9660.sh
|
||||
endif
|
||||
|
||||
if ENABLE_INOTIFY
|
||||
noinst_PROGRAMS += test/run_inotify
|
||||
test_run_inotify_SOURCES = test/run_inotify.c \
|
||||
src/fd_util.c \
|
||||
src/fifo_buffer.c \
|
||||
src/inotify_source.c
|
||||
test_run_inotify_LDADD = $(GLIB_LIBS)
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
|
||||
@@ -727,7 +1075,7 @@ endif
|
||||
man_MANS = doc/mpd.1 doc/mpd.conf.5
|
||||
doc_DATA = AUTHORS COPYING NEWS README UPGRADING doc/mpdconf.example
|
||||
|
||||
DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml doc/sticker.xml
|
||||
DOCBOOK_FILES = doc/protocol.xml doc/user.xml doc/developer.xml
|
||||
|
||||
if ENABLE_DOCUMENTATION
|
||||
protocoldir = $(docdir)/protocol
|
||||
@@ -754,7 +1102,8 @@ endif
|
||||
|
||||
doc/api/html/index.html: doc/doxygen.conf
|
||||
@mkdir -p $(@D)
|
||||
$(DOXYGEN) $<
|
||||
[ "$(srcdir)" = "." ] || sed '/INPUT *=/ s/\([^ ]\+\/\)/$(subst /,\/,$(srcdir))\/\1/g' $(srcdir)/doc/doxygen.conf >doc/doxygen.conf
|
||||
$(DOXYGEN) doc/doxygen.conf
|
||||
|
||||
all-local: $(DOCBOOK_HTML) doc/api/html/index.html
|
||||
|
||||
@@ -764,10 +1113,12 @@ clean-local:
|
||||
|
||||
install-data-local: doc/api/html/index.html
|
||||
$(mkinstalldirs) $(DESTDIR)$(docdir)/api/html
|
||||
$(INSTALL_DATA) -c -m 644 doc/api/html/*.html doc/api/html/*.css \
|
||||
doc/api/html/*.png doc/api/html/*.gif \
|
||||
$(INSTALL_DATA) -c -m 644 doc/api/html/*.* \
|
||||
$(DESTDIR)$(docdir)/api/html
|
||||
|
||||
uninstall-local:
|
||||
rm -f $(DESTDIR)$(docdir)/api/html/*.*
|
||||
|
||||
upload: $(DOCBOOK_HTML) doc/api/html/index.html
|
||||
rsync -vpruz --delete doc/ cirrus@www.musicpd.org:/var/www/musicpd.org/www/doc/ \
|
||||
--chmod=Dug+rwx,Do+rx,Fug+rw,Fo+r \
|
||||
|
||||
129
NEWS
129
NEWS
@@ -1,3 +1,132 @@
|
||||
ver 0.16 (20??/??/??)
|
||||
* protocol:
|
||||
- send song modification time to client
|
||||
- added "update" idle event
|
||||
- removed the deprecated "volume" command
|
||||
- added the "findadd" command
|
||||
- range support for "delete"
|
||||
- "previous" really plays the previous song
|
||||
- "addid" with negative position is deprecated
|
||||
- "load" supports remote playlists (extm3u, pls, asx, xspf, lastfm://)
|
||||
- allow changing replay gain mode on-the-fly
|
||||
- omitting the range end is possible
|
||||
- "update" checks if the path is malformed
|
||||
* archive:
|
||||
- iso: renamed plugin to "iso9660"
|
||||
- zip: renamed plugin to "zzip"
|
||||
* input:
|
||||
- lastfm: obsolete plugin removed
|
||||
- ffmpeg: new input plugin using libavformat's "avio" library
|
||||
* tags:
|
||||
- added tags "ArtistSort", "AlbumArtistSort"
|
||||
- id3: revised "performer" tag support
|
||||
- ape: MusicBrainz tags
|
||||
* decoders:
|
||||
- don't try a plugin twice (MIME type & suffix)
|
||||
- don't fall back to "mad" unless no plugin matches
|
||||
- ffmpeg: support multiple tags
|
||||
- ffmpeg: convert metadata to generic format
|
||||
- ffmpeg: implement the libavutil log callback
|
||||
- sndfile: new decoder plugin based on libsndfile
|
||||
- flac: moved CUE sheet support to a playlist plugin
|
||||
- flac: support streams without STREAMINFO block
|
||||
- mikmod: sample rate is configurable
|
||||
- mpg123: new decoder plugin based on libmpg123
|
||||
- sidplay: support sub-tunes
|
||||
- sidplay: implemented songlength database
|
||||
- sidplay: support seeking
|
||||
- wavpack: activate 32 bit support
|
||||
- wavpack: allow more than 2 channels
|
||||
- mp4ff: rename plugin "mp4" to "mp4ff"
|
||||
* encoders:
|
||||
- twolame: new encoder plugin based on libtwolame
|
||||
- flac: new encoder plugin based on libFLAC
|
||||
- wave: new encoder plugin for PCM WAV format
|
||||
* output:
|
||||
- recorder: new output plugin for recording radio streams
|
||||
- alsa: don't recover on CANCEL
|
||||
- alsa: fill period buffer with silence before draining
|
||||
- openal: new output plugin
|
||||
- pulse: announce "media.role=music"
|
||||
- pulse: renamed context to "Music Player Daemon"
|
||||
- pulse: connect to server on MPD startup, implement pause
|
||||
- jack: require libjack 0.100
|
||||
- jack: don't disconnect during pause
|
||||
- jack: connect to server on MPD startup
|
||||
- jack: added options "client_name", "server_name"
|
||||
- jack: clear ring buffers before activating
|
||||
- jack: renamed option "ports" to "destination_ports"
|
||||
- jack: support more than two audio channels
|
||||
- httpd: bind port when output is enabled
|
||||
- httpd: added name/genre/website configuration
|
||||
- oss: 24 bit support via OSS4
|
||||
- win32: new output plugin for Windows Wave
|
||||
- wildcards allowed in audio_format configuration
|
||||
- consistently lock audio output objects
|
||||
* player:
|
||||
- drain audio outputs at the end of the playlist
|
||||
* mixers:
|
||||
- removed support for legacy mixer configuration
|
||||
- reimplemented software volume as mixer+filter plugin
|
||||
- per-device software/hardware mixer setting
|
||||
* commands:
|
||||
- added new "status" line with more precise "elapsed time"
|
||||
* update:
|
||||
- automatically update the database with Linux inotify
|
||||
- support .mpdignore files in the music directory
|
||||
- sort songs by album name first, then disc/track number
|
||||
- rescan after metadata_to_use change
|
||||
* normalize: upgraded to AudioCompress 2.0
|
||||
- automatically convert to 16 bit samples
|
||||
* replay gain:
|
||||
- reimplemented as a filter plugin
|
||||
- fall back to track gain if album gain is unavailable
|
||||
- optionally use hardware mixer to apply replay gain
|
||||
- added mode "auto"
|
||||
* log unused/unknown block parameters
|
||||
* removed the deprecated "error_file" option
|
||||
* save state when stopped
|
||||
* renamed option "--stdout" to "--stderr"
|
||||
* removed options --create-db and --no-create-db
|
||||
* state_file: save only if something has changed
|
||||
* database: eliminated maximum line length
|
||||
* log: redirect stdout/stderr to /dev/null if syslog is used
|
||||
* set the close-on-exec flag on all file descriptors
|
||||
* pcm_volume, pcm_mix: implemented 32 bit support
|
||||
* support packed 24 bit samples
|
||||
* CUE sheet support
|
||||
* support for MixRamp tags
|
||||
* obey $(sysconfdir) for default mpd.conf location
|
||||
* build with large file support by default
|
||||
* added test suite ("make check")
|
||||
* require GLib 2.12
|
||||
* added libwrap support
|
||||
|
||||
|
||||
ver 0.15.12 (2010/07/20)
|
||||
* input:
|
||||
- curl: remove assertion after curl_multi_fdset()
|
||||
* tags:
|
||||
- rva2: set "gain", not "peak"
|
||||
* decoders:
|
||||
- wildmidi: support version 0.2.3
|
||||
|
||||
|
||||
ver 0.15.11 (2010/06/14)
|
||||
* tags:
|
||||
- ape: support album artist
|
||||
* decoders:
|
||||
- mp4ff: support tags "album artist", "albumartist", "band"
|
||||
- mikmod: fix memory leak
|
||||
- vorbis: handle uri==NULL
|
||||
- ffmpeg: fix memory leak
|
||||
- ffmpeg: free AVFormatContext on error
|
||||
- ffmpeg: read more metadata
|
||||
- ffmpeg: fix libavformat 0.6 by using av_open_input_stream()
|
||||
* playlist: emit IDLE_OPTIONS when resetting single mode
|
||||
* listen: make get_remote_uid() work on BSD
|
||||
|
||||
|
||||
ver 0.15.10 (2010/05/30)
|
||||
* input:
|
||||
- mms: fix memory leak in error handler
|
||||
|
||||
@@ -9,20 +9,20 @@ srcdir="`dirname $0`"
|
||||
test -z "$srcdir" && srcdir=.
|
||||
cd "$srcdir"
|
||||
DIE=
|
||||
AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9]\).*/\1/"
|
||||
AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]*\).*/\1/"
|
||||
AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/"
|
||||
VERSIONMKINT="sed -e s/[^0-9]//"
|
||||
if test -n "$AM_FORCE_VERSION"
|
||||
then
|
||||
AM_VERSIONS="$AM_FORCE_VERSION"
|
||||
else
|
||||
AM_VERSIONS='1.9 1.10'
|
||||
AM_VERSIONS='1.10'
|
||||
fi
|
||||
if test -n "$AC_FORCE_VERSION"
|
||||
then
|
||||
AC_VERSIONS="$AC_FORCE_VERSION"
|
||||
else
|
||||
AC_VERSIONS='2.58 2.59 2.60 2.61'
|
||||
AC_VERSIONS='2.60 2.61'
|
||||
fi
|
||||
|
||||
versioned_bins ()
|
||||
|
||||
2051
configure.ac
2051
configure.ac
File diff suppressed because it is too large
Load Diff
@@ -534,7 +534,7 @@ WARN_LOGFILE =
|
||||
# directories like "/usr/src/myproject". Separate the files or directories
|
||||
# with spaces.
|
||||
|
||||
INPUT = src
|
||||
INPUT = src/
|
||||
|
||||
# This tag can be used to specify the character encoding of the source files
|
||||
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
|
||||
|
||||
10
doc/mpd.1
10
doc/mpd.1
@@ -25,17 +25,11 @@ Output a brief help message.
|
||||
Kill the currently running mpd session. The pid_file parameter must be
|
||||
specified in the config file for this to work.
|
||||
.TP
|
||||
.BI --create-db
|
||||
Force (re)creation of database.
|
||||
.TP
|
||||
.BI --no-create-db
|
||||
Do not create database, even if it doesn't exist.
|
||||
.TP
|
||||
.BI --no-daemon
|
||||
Don't detach from console.
|
||||
.TP
|
||||
.BI --stdout
|
||||
Print messages to stdout and stderr.
|
||||
.BI --stderr
|
||||
Print messages stderr.
|
||||
.TP
|
||||
.BI --verbose
|
||||
Verbose logging.
|
||||
|
||||
@@ -129,6 +129,8 @@ audio that is sent to each audio output. Note that audio outputs may specify
|
||||
their own audio format which will be used for actual output to the audio
|
||||
device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The
|
||||
default is to use the audio format of the input file.
|
||||
Any of the three attributes may be an asterisk to specify that this
|
||||
attribute should not be enforced
|
||||
.TP
|
||||
.B samplerate_converter <integer or prefix>
|
||||
This specifies the libsamplerate converter to use. The supplied value should
|
||||
@@ -168,31 +170,14 @@ only choice) if MPD was compiled without libsamplerate.
|
||||
For an up-to-date list of available converters, please see the libsamplerate
|
||||
documentation (available online at <\fBhttp://www.mega-nerd.com/SRC/\fP>).
|
||||
.TP
|
||||
.B mixer_type <alsa, oss, software, hardware or disabled>
|
||||
This specifies which mixer to use. The default is hardware and depends on
|
||||
what audio output support mpd was built with. Options alsa and oss are
|
||||
legacy and should not be used in new configs, but when set mixer_device
|
||||
and mixer_control will apply.
|
||||
.TP
|
||||
.B mixer_device <mixer dev>
|
||||
This specifies which mixer to use. The default for oss is
|
||||
"/dev/mixer"; the default for alsa is "default". This global option is
|
||||
deprecated and should not be used. Look at the mixer_device option of
|
||||
corresponding output device instead.
|
||||
.TP
|
||||
.B mixer_control <mixer ctrl>
|
||||
This specifies which mixer control to use (sometimes referred to as
|
||||
the "device"). Examples of mixer controls are PCM, Line1, Master,
|
||||
etc. An example for OSS is "Pcm", and an example for alsa is
|
||||
"PCM". This global option is deprecated and should not be used. Look
|
||||
at the mixer_control option of corresponding output device instead.
|
||||
.TP
|
||||
.B replaygain <album or track>
|
||||
.B replaygain <off or album or track or auto>
|
||||
If specified, mpd will adjust the volume of songs played using ReplayGain tags
|
||||
(see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust
|
||||
volume using the album's ReplayGain tags, while setting it to "track" will
|
||||
adjust it using the track ReplayGain tags. Currently only FLAC, Ogg Vorbis,
|
||||
Musepack, and MP3 (through ID3v2 ReplayGain tags, not APEv2) are supported.
|
||||
adjust it using the track ReplayGain tags. "auto" uses the track ReplayGain
|
||||
tags if random play is activated otherwise the album ReplayGain tags. Currently
|
||||
only FLAC, Ogg Vorbis, Musepack, and MP3 (through ID3v2 ReplayGain tags, not
|
||||
APEv2) are supported.
|
||||
.TP
|
||||
.B replaygain_preamp <-15 to 15>
|
||||
This is the gain (in dB) applied to songs with ReplayGain tags.
|
||||
@@ -265,6 +250,15 @@ tags may be specified as a comma separated list. An example value is
|
||||
"artist,album,title,track". The special value "none" may be used alone to
|
||||
disable all metadata. The default is to use all known tag types except for
|
||||
comments.
|
||||
.TP
|
||||
.B auto_update <yes or no>
|
||||
This specifies the wheter to support automatic update of music database when
|
||||
files are changed in music_directory. The default is to disable autoupdate
|
||||
of database.
|
||||
.TP
|
||||
.B auto_update_depth <N>
|
||||
Limit the depth of the directories being watched, 0 means only watch
|
||||
the music directory itself. There is no limit by default.
|
||||
.SH REQUIRED AUDIO OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B type <type>
|
||||
@@ -280,11 +274,25 @@ This specifies the sample rate, bits per sample, and number of channels of
|
||||
audio that is sent to the audio output device. See documentation for the
|
||||
\fBaudio_output_format\fP parameter for more details. The default is to use
|
||||
whatever audio format is passed to the audio output.
|
||||
Any of the three attributes may be an asterisk to specify that this
|
||||
attribute should not be enforced
|
||||
.TP
|
||||
.B replay_gain_handler <software, mixer or none>
|
||||
Specifies how replay gain is applied. The default is "software",
|
||||
which uses an internal software volume control. "mixer" uses the
|
||||
configured (hardware) mixer control. "none" disables replay gain on
|
||||
this audio output.
|
||||
.SH OPTIONAL ALSA OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B device <dev>
|
||||
This specifies the device to use for audio output. The default is "default".
|
||||
.TP
|
||||
.B mixer_type <hardware, software or none>
|
||||
Specifies which mixer should be used for this audio output: the
|
||||
hardware mixer (available for ALSA, OSS and PulseAudio), the software
|
||||
mixer or no mixer ("none"). By default, the hardware mixer is used
|
||||
for devices which support it, and none for the others.
|
||||
.TP
|
||||
.B mixer_device <mixer dev>
|
||||
This specifies which mixer to use. The default is "default". To use
|
||||
the second sound card in a system, use "hw:1".
|
||||
@@ -352,13 +360,12 @@ after another until it successfully establishes a connection.
|
||||
.TP
|
||||
.B sink <sink>
|
||||
The sink to output to. The default is to let PulseAudio choose a sink.
|
||||
.SH REQUIRED JACK OUTPUT PARAMETERS
|
||||
.SH OPTIONAL JACK OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B name <name>
|
||||
.B client_name <name>
|
||||
The client name to use when connecting to JACK. The output ports <name>:left
|
||||
and <name>:right will also be created for the left and right channels,
|
||||
respectively.
|
||||
.SH OPTIONAL JACK OUTPUT PARAMETERS
|
||||
.TP
|
||||
.B ports <left_port,right_port>
|
||||
This specifies the left and right ports to connect to for the left and right
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
#
|
||||
#state_file "~/.mpd/state"
|
||||
#
|
||||
# The location of the sticker database. This is a database which
|
||||
# manages dynamic information attached to songs.
|
||||
#
|
||||
#sticker_file "~/.mpd/sticker.sql"
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
@@ -61,6 +66,13 @@
|
||||
#
|
||||
#user "nobody"
|
||||
#
|
||||
# This setting specifies the group that MPD will run as. If not specified
|
||||
# primary group of user specified with "user" setting will be used (if set).
|
||||
# This is useful if MPD needs to be a member of group such as "audio" to
|
||||
# have permission to use sound card.
|
||||
#
|
||||
#group "nogroup"
|
||||
#
|
||||
# This setting sets the address for the daemon to listen on. Careful attention
|
||||
# should be paid if this is assigned to anything other then the default, any.
|
||||
# This setting can deny access to control of the daemon.
|
||||
@@ -102,6 +114,16 @@
|
||||
#
|
||||
#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc"
|
||||
#
|
||||
# This setting enables automatic update of MPD's database when files in
|
||||
# music_directory are changed.
|
||||
#
|
||||
#auto_update "yes"
|
||||
#
|
||||
# Limit the depth of the directories being watched, 0 means only watch
|
||||
# the music directory itself. There is no limit by default.
|
||||
#
|
||||
#auto_update_depth "3"
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
@@ -179,6 +201,7 @@ input {
|
||||
# name "My ALSA Device"
|
||||
## device "hw:0,0" # optional
|
||||
## format "44100:16:2" # optional
|
||||
## mixer_type "hardware" # optional
|
||||
## mixer_device "default" # optional
|
||||
## mixer_control "PCM" # optional
|
||||
## mixer_index "0" # optional
|
||||
@@ -191,6 +214,7 @@ input {
|
||||
# name "My OSS Device"
|
||||
## device "/dev/dsp" # optional
|
||||
## format "44100:16:2" # optional
|
||||
## mixer_type "hardware" # optional
|
||||
## mixer_device "/dev/mixer" # optional
|
||||
## mixer_control "PCM" # optional
|
||||
#}
|
||||
@@ -214,6 +238,19 @@ input {
|
||||
## genre "jazz" # optional
|
||||
## public "no" # optional
|
||||
## timeout "2" # optional
|
||||
## mixer_type "software" # optional
|
||||
#}
|
||||
#
|
||||
# An example of a recorder output:
|
||||
#
|
||||
#audio_output {
|
||||
# type "recorder"
|
||||
# name "My recorder"
|
||||
# encoder "vorbis" # optional, vorbis or lame
|
||||
# path "/var/lib/mpd/recorder/mpd.ogg"
|
||||
## quality "5.0" # do not define if bitrate is defined
|
||||
# bitrate "128" # do not define if quality is defined
|
||||
# format "44100:16:1"
|
||||
#}
|
||||
#
|
||||
# An example of a httpd output (built-in HTTP streaming server):
|
||||
@@ -226,6 +263,7 @@ input {
|
||||
## quality "5.0" # do not define if bitrate is defined
|
||||
# bitrate "128" # do not define if quality is defined
|
||||
# format "44100:16:1"
|
||||
# max_clients "0" # optional 0=no limit
|
||||
#}
|
||||
#
|
||||
# An example of a pulseaudio output (streaming to a remote pulseaudio server)
|
||||
@@ -255,6 +293,7 @@ input {
|
||||
#audio_output {
|
||||
# type "null"
|
||||
# name "My Null Output"
|
||||
# mixer_type "none" # optional
|
||||
#}
|
||||
#
|
||||
# This setting will change all decoded audio to be converted to the specified
|
||||
@@ -273,38 +312,11 @@ input {
|
||||
###############################################################################
|
||||
|
||||
|
||||
# Volume control mixer ########################################################
|
||||
#
|
||||
# These are the global volume control settings. By default, this setting will
|
||||
# be detected to the available audio output device, with preference going to
|
||||
# hardware mixing. Hardware and software mixers for individual audio_output
|
||||
# sections cannot yet be mixed.
|
||||
#
|
||||
# An example for controlling an ALSA, OSS or Pulseaudio mixer; If this
|
||||
# setting is used other sound applications will be affected by the volume
|
||||
# being controlled by MPD.
|
||||
#
|
||||
#mixer_type "hardware"
|
||||
#
|
||||
# An example for controlling all mixers through software. This will control
|
||||
# all controls, even if the mixer is not supported by the device and will not
|
||||
# affect any other sound producing applications.
|
||||
#
|
||||
#mixer_type "software"
|
||||
#
|
||||
# This example will not allow MPD to touch the mixer at all and will disable
|
||||
# all volume controls.
|
||||
#
|
||||
#mixer_type "disabled"
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
# Normalization automatic volume adjustments ##################################
|
||||
#
|
||||
# This setting specifies the type of ReplayGain to use. This setting can have
|
||||
# the argument "album" or "track". See <http://www.replaygain.org> for more
|
||||
# details. This setting is disabled by default.
|
||||
# the argument "off", "album" or "track". See <http://www.replaygain.org>
|
||||
# for more details. This setting is off by default.
|
||||
#
|
||||
#replaygain "album"
|
||||
#
|
||||
@@ -357,8 +369,7 @@ input {
|
||||
# Character Encoding ##########################################################
|
||||
#
|
||||
# If file or directory names do not display correctly for your locale then you
|
||||
# may need to modify this setting. After modification of this setting mpd
|
||||
# --create-db must be run to change the database.
|
||||
# may need to modify this setting.
|
||||
#
|
||||
#filesystem_charset "UTF-8"
|
||||
#
|
||||
@@ -367,3 +378,29 @@ input {
|
||||
#id3v1_encoding "ISO-8859-1"
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
# SIDPlay decoder #############################################################
|
||||
#
|
||||
# songlength_database:
|
||||
# Location of your songlengths file, as distributed with the HVSC.
|
||||
# The sidplay plugin checks this for matching MD5 fingerprints.
|
||||
# See http://www.c64.org/HVSC/DOCUMENTS/Songlengths.faq
|
||||
#
|
||||
# default_songlength:
|
||||
# This is the default playing time in seconds for songs not in the
|
||||
# songlength database, or in case you're not using a database.
|
||||
# A value of 0 means play indefinitely.
|
||||
#
|
||||
# filter:
|
||||
# Turns the SID filter emulation on or off.
|
||||
#
|
||||
#decoder {
|
||||
# plugin "sidplay"
|
||||
# songlength_database "/media/C64Music/DOCUMENTS/Songlengths.txt"
|
||||
# default_songlength "120"
|
||||
# filter "true"
|
||||
#}
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
|
||||
207
doc/protocol.xml
207
doc/protocol.xml
@@ -67,6 +67,20 @@
|
||||
successful command executed in the command list.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Ranges</title>
|
||||
|
||||
<para>
|
||||
Some commands (e.g. <link
|
||||
linkend="command_delete"><command>delete</command></link>)
|
||||
allow specifying a range in the form
|
||||
<parameter>START:END</parameter> (the <varname>END</varname>
|
||||
item is not included in the range, similar to ranges in the
|
||||
Python programming language). If <varname>END</varname> is
|
||||
omitted, then the maximum possible value is assumed.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
@@ -123,7 +137,7 @@
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
<footnote id="since_0_14"><simpara>Since MPD 0.14</simpara></footnote>
|
||||
<footnote id="since_0_14"><simpara>Introduced with MPD 0.14</simpara></footnote>
|
||||
Waits until there is a noteworthy change in one or more
|
||||
of MPD's subsystems. As soon as there is one, it lists
|
||||
all changed systems in a line in the format
|
||||
@@ -137,6 +151,15 @@
|
||||
has been modified after <command>update</command>.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<returnvalue>update</returnvalue>: a database update
|
||||
has started or finished. If the database was
|
||||
modified during the update, the
|
||||
<returnvalue>database</returnvalue> event is also
|
||||
emitted.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<returnvalue>stored_playlist</returnvalue>: a stored
|
||||
@@ -172,7 +195,7 @@
|
||||
<para>
|
||||
<returnvalue>options</returnvalue>: options like
|
||||
<option>repeat</option>, <option>random</option>,
|
||||
<option>crossfade</option>
|
||||
<option>crossfade</option>, replay gain
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
@@ -191,9 +214,6 @@
|
||||
MPD will only send notifications when something changed in
|
||||
one of the specified subsytems.
|
||||
</para>
|
||||
<simpara>
|
||||
Since <application>MPD</application> 0.14
|
||||
</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_status">
|
||||
@@ -223,15 +243,15 @@
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>single</varname>:
|
||||
<footnote id="since_0_15"><simpara>Introduced with MPD 0.15</simpara></footnote>
|
||||
<returnvalue>0 or 1</returnvalue>
|
||||
<footnote id="since_0_15"><simpara>Since MPD 0.15</simpara></footnote>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>consume</varname>:
|
||||
<returnvalue>0 or 1</returnvalue>
|
||||
<footnoteref linkend="since_0_15"/>
|
||||
<returnvalue>0 or 1</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
@@ -295,6 +315,16 @@
|
||||
playing/paused song)</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>elapsed</varname>:
|
||||
<footnote id="since_0_16"><simpara>Introduced with MPD 0.16</simpara></footnote>
|
||||
<returnvalue>
|
||||
Total time elapsed within the current song, but
|
||||
with higher resolution.
|
||||
</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>bitrate</varname>:
|
||||
@@ -308,6 +338,18 @@
|
||||
<returnvalue>crossfade in seconds</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>mixrampdb</varname>:
|
||||
<returnvalue>mixramp threshold in dB</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>mixrampdelay</varname>:
|
||||
<returnvalue>mixrampdelay in seconds</returnvalue>
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
<varname>audio</varname>:
|
||||
@@ -412,6 +454,32 @@
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_mixrampdb">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
<command>mixrampdb</command>
|
||||
<arg choice="req"><replaceable>deciBels</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Sets the threshold at which songs will be overlapped. Like crossfading but doesn't fade the track volume, just overlaps. The songs need to have MixRamp tags added by an external tool. 0dB is the normalized maximum volume so use negative values, I prefer -17dB. In the absence of mixramp tags crossfading will be used. See http://sourceforge.net/projects/mixramp
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_mixrampdelay">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
<command>mixrampdelay</command>
|
||||
<arg choice="req"><replaceable>SECONDS</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_random">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
@@ -471,23 +539,43 @@
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_volume">
|
||||
<varlistentry id="command_replay_gain_mode">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
<command>volume</command>
|
||||
<arg choice="req"><replaceable>CHANGE</replaceable></arg>
|
||||
<command>replay_gain_mode</command>
|
||||
<arg choice="req"><replaceable>MODE</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Changes volume by amount <varname>CHANGE</varname>.
|
||||
Sets the replay gain mode. One of
|
||||
<parameter>off</parameter>,
|
||||
<parameter>track</parameter>,
|
||||
<parameter>album</parameter>.
|
||||
</para>
|
||||
<para>
|
||||
Changing the mode during playback may take several
|
||||
seconds, because the new settings does not affect the
|
||||
buffered data.
|
||||
</para>
|
||||
<para>
|
||||
This command triggers the
|
||||
<returnvalue>options</returnvalue> idle event.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_replay_gain_status">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
<command>replay_gain_status</command>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Prints replay gain options. Currently, only the
|
||||
variable <varname>replay_gain_mode</varname> is
|
||||
returned.
|
||||
</para>
|
||||
<note>
|
||||
<para>
|
||||
<command>volume</command> is deprecated, use
|
||||
<command>setvol</command> instead.
|
||||
</para>
|
||||
</note>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
@@ -648,10 +736,7 @@
|
||||
</para>
|
||||
<para>
|
||||
<varname>URI</varname> is always a single file or
|
||||
URL. <varname>POSITION</varname> is optional, a
|
||||
negative number means it is relative to the currently
|
||||
playing song in the playlist (if there is one).
|
||||
For example:
|
||||
URL. For example:
|
||||
</para>
|
||||
<screen>
|
||||
addid "foo.mp3"
|
||||
@@ -676,7 +761,10 @@ OK
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
<command>delete</command>
|
||||
<arg choice="req"><replaceable>SONGPOS</replaceable></arg>
|
||||
<group>
|
||||
<arg choice="req"><replaceable>POS</replaceable></arg>
|
||||
<arg choice="req"><replaceable>START:END</replaceable></arg>
|
||||
</group>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
@@ -910,6 +998,20 @@ OK
|
||||
<section>
|
||||
<title>Stored playlists</title>
|
||||
|
||||
<para>
|
||||
Playlists are stored inside the configured playlist directory.
|
||||
They are addressed with their file name (without the directory
|
||||
and without the <filename>.m3u</filename> suffix).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Some of the commands described in this section can be used to
|
||||
run playlist plugins instead of the hard-coded simple
|
||||
<filename>m3u</filename> parser. They can access playlists in
|
||||
the music directory (relative path including the suffix) or
|
||||
remote playlists (absolute URI with a supported scheme).
|
||||
</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry id="command_listplaylist">
|
||||
<term>
|
||||
@@ -920,8 +1022,8 @@ OK
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Lists the files in the playlist
|
||||
<filename>NAME.m3u</filename>.
|
||||
Lists the songs in the playlist. Playlist plugins are
|
||||
supported.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@@ -934,7 +1036,8 @@ OK
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Lists songs in the playlist <filename>NAME.m3u</filename>.
|
||||
Lists the songs with metadata in the playlist. Playlist
|
||||
plugins are supported.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@@ -966,8 +1069,8 @@ OK
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Loads the playlist <filename>NAME.m3u</filename> from
|
||||
the playlist directory.
|
||||
Loads the playlist into the current queue. Playlist
|
||||
plugins are supported.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
@@ -1118,6 +1221,23 @@ OK
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_findadd">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
<command>findadd</command>
|
||||
<arg choice="req"><replaceable>TYPE</replaceable></arg>
|
||||
<arg choice="req"><replaceable>WHAT</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Finds songs in the db that are exactly
|
||||
<varname>WHAT</varname> and adds them to current playlist.
|
||||
<varname>TYPE</varname> can be any tag supported by MPD.
|
||||
<varname>WHAT</varname> is what to find.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_list">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
@@ -1231,6 +1351,20 @@ OK
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_rescan">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
<command>rescan</command>
|
||||
<arg choice="opt"><replaceable>URI</replaceable></arg>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Same as <command>update</command>, but also rescans
|
||||
unmodified files.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</section>
|
||||
|
||||
@@ -1508,6 +1642,25 @@ OK
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry id="command_decoders">
|
||||
<term>
|
||||
<cmdsynopsis>
|
||||
<command>decoders</command>
|
||||
</cmdsynopsis>
|
||||
</term>
|
||||
<listitem>
|
||||
<para>
|
||||
Print a list of decoder plugins, followed by their
|
||||
supported suffixes and MIME types. Example response:
|
||||
</para>
|
||||
<programlisting>plugin: mad
|
||||
suffix: mp3
|
||||
suffix: mp2
|
||||
mime_type: audio/mpeg
|
||||
plugin: mpcdec
|
||||
suffix: mpc</programlisting>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
<?xml version='1.0' encoding="utf-8"?>
|
||||
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
|
||||
"docbook/dtd/xml/4.2/docbookx.dtd">
|
||||
<book>
|
||||
<title>The Music Player Daemon Sticker Database</title>
|
||||
|
||||
<chapter>
|
||||
<title>Introduction to MPD's Sticker Database</title>
|
||||
<para>
|
||||
This document shell give a short guideline for recommended tags
|
||||
for use in MPD's Sticker Database.
|
||||
MPD's Sticker Database is a subsystem that enables users to add
|
||||
custom tags. MPD does not alter the media files.
|
||||
</para>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<title>Guideline for recommended tags</title>
|
||||
<para>
|
||||
Since there is no standard for tags in media files, this
|
||||
document is trying to give you some help deciding what tags to
|
||||
use. The selection of these tags tries to cover the most
|
||||
widely used tags. This way the tags might still work in other
|
||||
players, if you sync the database with your original media
|
||||
files.
|
||||
Keep in mind that we stick with lower case tags with underscores
|
||||
instead of spaces. If there will be a Sync tool in future
|
||||
its easy to change this on the fly, if needed.
|
||||
</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>rating</varname></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Will store a rating value from 1 (worst) to 5 (best) for a
|
||||
given song.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>album_rating</varname></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Will store a rating value from 1 (worst) to 5 (best) for a
|
||||
given album.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>style</varname></term>
|
||||
<listitem>
|
||||
<para>
|
||||
This tag is used to keep the Genre tag clean, by now
|
||||
having 1000's of genres. Instead you define a Main Genre
|
||||
for each file and can make a more specific
|
||||
description. This should be one Keyword like "Post Punk"
|
||||
or "Progressive Death Metal" An Alternative name for this
|
||||
tag is "Subgenre", time will tell which one gets more
|
||||
support.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>lyrics</varname></term>
|
||||
<listitem>
|
||||
<para>
|
||||
This one is self explaining. This gives the option to
|
||||
store lyrics of a song where they belong to: mapped to the
|
||||
song
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>similar_artists</varname> (Comma seperated list of artists)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This tag enables a last.fm alike aproach which will still
|
||||
work when being offline Keep in mind, that this tag is
|
||||
absolutely non-standard! I am not aware of any other
|
||||
player that uses a comparable tag.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</chapter>
|
||||
</book>
|
||||
550
doc/user.xml
550
doc/user.xml
@@ -300,10 +300,30 @@ cd mpd-version</programlisting>
|
||||
<varname>format</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
Always open the audio output with the specified audio
|
||||
format (samplerate:bits:channels), regardless of the
|
||||
format of the input file. This is optional for most
|
||||
plugins.
|
||||
<para>
|
||||
Always open the audio output with the specified audio
|
||||
format (samplerate:bits:channels), regardless of the
|
||||
format of the input file. This is optional for most
|
||||
plugins.
|
||||
</para>
|
||||
<para>
|
||||
Any of the three attributes may be an asterisk to
|
||||
specify that this attribute should not be enforced,
|
||||
example: <parameter>48000:16:*</parameter>.
|
||||
<parameter>*:*:*</parameter> is equal to not having
|
||||
a <varname>format</varname> specification.
|
||||
</para>
|
||||
<para>
|
||||
The following values are valid for
|
||||
<varname>bits</varname>: <varname>8</varname>
|
||||
(signed 8 bit integer samples),
|
||||
<varname>16</varname>, <varname>24</varname> (signed
|
||||
24 bit integer samples padded to 32 bit),
|
||||
<varname>24_3</varname> (signed 24 bit integer
|
||||
samples, no padding, 3 bytes per sample),
|
||||
<varname>32</varname> (signed 32 bit integer
|
||||
samples).
|
||||
</para>
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
@@ -319,19 +339,227 @@ cd mpd-version</programlisting>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>mixer_enabled</varname>
|
||||
<parameter>yes|no</parameter>
|
||||
<varname>always_on</varname>
|
||||
<parameter>yes|no</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Specifies whether the hardware mixer of this audio
|
||||
output should be used. By default, all hardware
|
||||
mixers are enabled if available.
|
||||
If set to "yes", then MPD attempts to keep this audio
|
||||
output always open. This may be useful for streaming
|
||||
servers, when you don't want to disconnect all
|
||||
listeners even when playback is accidently stopped.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>mixer_type</varname>
|
||||
<parameter>hardware|software|none</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Specifies which mixer should be used for this audio
|
||||
output: the hardware mixer (available for ALSA, OSS
|
||||
and PulseAudio), the software mixer or no mixer
|
||||
("none"). By default, the hardware mixer is used for
|
||||
devices which support it, and none for the others.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>replay_gain_handler</varname>
|
||||
<parameter>software|mixer|none</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Specifies how replay gain is applied. The default is
|
||||
"software", which uses an internal software volume
|
||||
control. "mixer" uses the configured (hardware) mixer
|
||||
control. "none" disables replay gain on this audio
|
||||
output.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Configuring filters</title>
|
||||
|
||||
<para>
|
||||
Filters are plugins which modify an audio stream.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To configure a filter, add a <varname>filter</varname> block
|
||||
to <filename>mpd.conf</filename>:
|
||||
</para>
|
||||
|
||||
<programlisting>filter {
|
||||
plugin "volume"
|
||||
name "software volume"
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
The following table lists the <varname>filter</varname>
|
||||
options valid for all plugins:
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>
|
||||
Name
|
||||
</entry>
|
||||
<entry>
|
||||
Description
|
||||
</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>plugin</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
The name of the plugin.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>name</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
The name of the filter.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Configuring playlist plugins</title>
|
||||
|
||||
<para>
|
||||
Playlist plugins are used to load remote playlists. This is
|
||||
not related to MPD's playlist directory.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To configure a filter, add a
|
||||
<varname>playlist_plugin</varname> block to
|
||||
<filename>mpd.conf</filename>:
|
||||
</para>
|
||||
|
||||
<programlisting>playlist_plugin {
|
||||
name "m3u"
|
||||
enabled "true"
|
||||
}
|
||||
</programlisting>
|
||||
|
||||
<para>
|
||||
The following table lists the
|
||||
<varname>playlist_plugin</varname> options valid for all
|
||||
plugins:
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>
|
||||
Name
|
||||
</entry>
|
||||
<entry>
|
||||
Description
|
||||
</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>name</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
The name of the plugin.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>enabled</varname>
|
||||
<parameter>yes|no</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Allows you to disable a input plugin without
|
||||
recompiling. By default, all plugins are enabled.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
<title>Using MPD</title>
|
||||
|
||||
<section>
|
||||
<title>The client</title>
|
||||
|
||||
<para>
|
||||
After you have installed, configured and started MPD, you
|
||||
choose a client to control the playback.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The most basic client is <filename>mpc</filename>, which
|
||||
provides a command line interface. It is useful in shell
|
||||
scripts. Many people bind specific <filename>mpc</filename>
|
||||
commands to hotkeys.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The <ulink url="http://mpd.wikia.com/wiki/Clients">MPD
|
||||
Wiki</ulink> contains an extensive list of clients to choose
|
||||
from.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>The music directory and the database</title>
|
||||
|
||||
<para>
|
||||
The "music directory" is where you store your music files.
|
||||
MPD stores all relevant meta information about all songs in
|
||||
its "database". Whenever you add, modify or remove songs in
|
||||
the music directory, you have to update the database, for
|
||||
example with <filename>mpc</filename>:
|
||||
</para>
|
||||
|
||||
<programlisting>mpc update</programlisting>
|
||||
|
||||
<para>
|
||||
Depending on the size of your music collection and the speed
|
||||
of the storage, this can take a while.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To exclude a file from the update, create a file called
|
||||
<filename>.mpdignore</filename> in its parent directory. Each
|
||||
line of that file may contain a list of shell wildcards.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>The queue</title>
|
||||
|
||||
<para>
|
||||
The queue (sometimes called "current playlist") is a list of
|
||||
songs to be played by MPD. To play a song, add it to the
|
||||
queue and start playback. Most clients offer an interface to
|
||||
edit the queue.
|
||||
</para>
|
||||
</section>
|
||||
</chapter>
|
||||
|
||||
<chapter>
|
||||
@@ -386,15 +614,6 @@ cd mpd-version</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>lastfm</varname></title>
|
||||
|
||||
<para>
|
||||
Plays last.fm radio. This plugin is experimental, and will
|
||||
be superseded by a better solution in MPD 0.16.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>mms</varname></title>
|
||||
|
||||
@@ -404,6 +623,40 @@ cd mpd-version</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Decoder plugins</title>
|
||||
|
||||
<section>
|
||||
<title><varname>mikmod</varname></title>
|
||||
|
||||
<para>
|
||||
Module player based on MikMod.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>sample_rate</varname>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets the sample rate generated by
|
||||
<filename>libmikmod</filename>. Default is 44100.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Output plugins</title>
|
||||
|
||||
@@ -542,6 +795,81 @@ cd mpd-version</programlisting>
|
||||
The <varname>jack</varname> plugin connects to a JACK
|
||||
server.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>client_name</varname>
|
||||
<parameter>NAME</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The name of the JACK client. Defaults to "Music
|
||||
Player Daemon".
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>server_name</varname>
|
||||
<parameter>NAME</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Optional name of the JACK server.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>autostart</varname>
|
||||
<parameter>yes|no</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
If set to <parameter>yes</parameter>, then
|
||||
<filename>libjack</filename> will automatically
|
||||
launch the JACK daemon. Disabled by default.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>source_ports</varname>
|
||||
<parameter>A,B</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The names of the JACK source ports to be created.
|
||||
By default, the ports "left" and "right" are
|
||||
created. To use more ports, you have to tweak this
|
||||
option.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>destination_ports</varname>
|
||||
<parameter>A,B</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The names of the JACK destination ports to connect to.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>ringbuffer_size</varname>
|
||||
<parameter>NBYTES</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets the size of the ring buffer for each channel.
|
||||
Do not configure this value unless you know what
|
||||
you're doing.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
@@ -620,6 +948,16 @@ cd mpd-version</programlisting>
|
||||
second.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>max_clients</varname>
|
||||
<parameter>MC</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets a limit, number of concurrent clients. When set
|
||||
to 0 no limit will apply.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
@@ -693,6 +1031,39 @@ cd mpd-version</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>openal</varname></title>
|
||||
|
||||
<para>
|
||||
The "OpenAL" plugin uses <filename>libopenal</filename>.
|
||||
It is supported on many platforms.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>device</varname>
|
||||
<parameter>NAME</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets the device which should be used. This can be
|
||||
any valid OpenAL device name. If not specified, then
|
||||
<filename>libopenal</filename> will choose a default device.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>osx</varname></title>
|
||||
|
||||
@@ -775,6 +1146,73 @@ cd mpd-version</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>recorder</varname></title>
|
||||
|
||||
<para>
|
||||
The <varname>recorder</varname> plugin writes the audio
|
||||
played by MPD to a file. This may be useful for recording
|
||||
radio streams.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
You must configure either <varname>quality</varname> or
|
||||
<varname>bitrate</varname>.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>path</varname>
|
||||
<parameter>P</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Write to this file.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>encoder</varname>
|
||||
<parameter>NAME</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Chooses an encoder plugin,
|
||||
e.g. <parameter>vorbis</parameter>.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>quality</varname>
|
||||
<parameter>Q</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Configures the encoder quality (for VBR) in the
|
||||
range -1 .. 10.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>bitrate</varname>
|
||||
<parameter>BR</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
Sets a constant encoder bit rate, in kilobit per
|
||||
second.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>shout</varname></title>
|
||||
|
||||
@@ -939,5 +1377,81 @@ cd mpd-version</programlisting>
|
||||
</informaltable>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title>Playlist plugins</title>
|
||||
|
||||
<section>
|
||||
<title><varname>lastfm</varname></title>
|
||||
|
||||
<para>
|
||||
Plays last.fm radio.
|
||||
</para>
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Setting</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
<tbody>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>user</varname>
|
||||
<parameter>USERNAME</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The last.fm user name.
|
||||
</entry>
|
||||
</row>
|
||||
<row>
|
||||
<entry>
|
||||
<varname>password</varname>
|
||||
<parameter>PWD</parameter>
|
||||
</entry>
|
||||
<entry>
|
||||
The last.fm password.
|
||||
</entry>
|
||||
</row>
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>m3u</varname></title>
|
||||
|
||||
<para>
|
||||
Reads <filename>.m3u</filename> playlist files.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>extm3u</varname></title>
|
||||
|
||||
<para>
|
||||
Reads extended <filename>.m3u</filename> playlist files.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>pls</varname></title>
|
||||
|
||||
<para>
|
||||
Reads <filename>.pls</filename> playlist files.
|
||||
</para>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<title><varname>xspf</varname></title>
|
||||
|
||||
<para>
|
||||
Reads <ulink url="http://www.xspf.org/">XSPF</ulink>
|
||||
playlist files.
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
</book>
|
||||
|
||||
111
m4/lame.m4
111
m4/lame.m4
@@ -1,111 +0,0 @@
|
||||
dnl borrowed from oddsock.org
|
||||
dnl AM_PATH_LAME([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]])
|
||||
dnl Test for liblame, and define LAME_CFLAGS and LAME_LIBS
|
||||
dnl
|
||||
AC_DEFUN([AM_PATH_LAME],
|
||||
[dnl
|
||||
dnl Get the cflags and libraries
|
||||
dnl
|
||||
AC_ARG_WITH(lame,
|
||||
AS_HELP_STRING([--with-lame=PFX],
|
||||
[prefix where liblame is installed (optional)]),,
|
||||
lame_prefix="")
|
||||
AC_ARG_WITH(lame-libraries,
|
||||
AS_HELP_STRING([--with-lame-libraries=DIR],
|
||||
[directory where liblame library is installed (optional)]),,
|
||||
lame_libraries="")
|
||||
AC_ARG_WITH(lame-includes,
|
||||
AS_HELP_STRING([--with-lame-includes=DIR],
|
||||
[directory where liblame header files are installed (optional)]),,
|
||||
lame_includes="")
|
||||
|
||||
if test "x$lame_prefix" != "xno" ; then
|
||||
|
||||
if test "x$lame_libraries" != "x" ; then
|
||||
LAME_LIBS="-L$lame_libraries"
|
||||
elif test "x$lame_prefix" != "x" ; then
|
||||
LAME_LIBS="-L$lame_prefix/lib"
|
||||
elif test "x$prefix" != "xNONE" ; then
|
||||
LAME_LIBS="-L$prefix/lib"
|
||||
fi
|
||||
|
||||
LAME_LIBS="$LAME_LIBS -lmp3lame -lm"
|
||||
|
||||
if test "x$lame_includes" != "x" ; then
|
||||
LAME_CFLAGS="-I$lame_includes"
|
||||
elif test "x$lame_prefix" != "x" ; then
|
||||
LAME_CFLAGS="-I$lame_prefix/include"
|
||||
elif test "x$prefix" != "xNONE"; then
|
||||
LAME_CFLAGS="-I$prefix/include"
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING(for liblame)
|
||||
no_lame=""
|
||||
|
||||
|
||||
ac_save_CFLAGS="$CFLAGS"
|
||||
ac_save_LIBS="$LIBS"
|
||||
CFLAGS="$CFLAGS $LAME_CFLAGS"
|
||||
LIBS="$LIBS $LAME_LIBS"
|
||||
dnl
|
||||
dnl Now check if the installed liblame is sufficiently new.
|
||||
dnl
|
||||
rm -f conf.lametest
|
||||
AC_TRY_RUN([
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <lame/lame.h>
|
||||
|
||||
int main ()
|
||||
{
|
||||
system("touch conf.lametest");
|
||||
return 0;
|
||||
}
|
||||
|
||||
],, no_lame=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
|
||||
CFLAGS="$ac_save_CFLAGS"
|
||||
LIBS="$ac_save_LIBS"
|
||||
fi
|
||||
|
||||
if test "x$no_lame" = "x" ; then
|
||||
AC_MSG_RESULT(yes)
|
||||
ifelse([$1], , :, [$1])
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
if test -f conf.lametest ; then
|
||||
:
|
||||
else
|
||||
echo "*** Could not run liblame test program, checking why..."
|
||||
CFLAGS="$CFLAGS $LAME_CFLAGS"
|
||||
LIBS="$LIBS $LAME_LIBS"
|
||||
AC_TRY_LINK([
|
||||
#include <stdio.h>
|
||||
#include <lame/lame.h>
|
||||
], [ return 0; ],
|
||||
[ echo "*** The test program compiled, but did not run. This usually means"
|
||||
echo "*** that the run-time linker is not finding liblame or finding the wrong"
|
||||
echo "*** version of liblame. If it is not finding liblame, you'll need to set your"
|
||||
echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
|
||||
echo "*** to the installed location Also, make sure you have run ldconfig if that"
|
||||
echo "*** is required on your system"
|
||||
echo "***"
|
||||
echo "*** If you have an old version installed, it is best to remove it, although"
|
||||
echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"],
|
||||
[ echo "*** The test program failed to compile or link. See the file config.log for the"
|
||||
echo "*** exact error that occured. This usually means liblame was incorrectly installed"
|
||||
echo "*** or that you have moved liblame since it was installed." ])
|
||||
CFLAGS="$ac_save_CFLAGS"
|
||||
LIBS="$ac_save_LIBS"
|
||||
fi
|
||||
LAME_CFLAGS=""
|
||||
LAME_LIBS=""
|
||||
ifelse([$2], , :, [$2])
|
||||
fi
|
||||
AC_DEFINE(HAVE_LAME, 1, [Define if you have liblame.])
|
||||
use_lame="1"
|
||||
AC_SUBST(LAME_CFLAGS)
|
||||
AC_SUBST(LAME_LIBS)
|
||||
rm -f conf.lametest
|
||||
])
|
||||
|
||||
116
m4/libOggFLAC.m4
116
m4/libOggFLAC.m4
@@ -1,116 +0,0 @@
|
||||
# Configure paths for libOggFLAC
|
||||
# "Inspired" by ogg.m4
|
||||
|
||||
dnl AM_PATH_LIBOGGFLAC([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]])
|
||||
dnl Test for libOggFLAC, and define LIBOGGFLAC_CFLAGS and LIBOGGFLAC_LIBS
|
||||
dnl
|
||||
AC_DEFUN([AM_PATH_LIBOGGFLAC],
|
||||
[dnl
|
||||
dnl Get the cflags and libraries
|
||||
dnl
|
||||
AC_ARG_WITH(libOggFLAC,
|
||||
AS_HELP_STRING([--with-libOggFLAC=PFX],
|
||||
[prefix where libOggFLAC is installed (optional)]),,
|
||||
libOggFLAC_prefix="")
|
||||
AC_ARG_WITH(libOggFLAC-libraries,
|
||||
AS_HELP_STRING([--with-libOggFLAC-libraries=DIR],
|
||||
[directory where libOggFLAC library is installed (optional)]),,
|
||||
libOggFLAC_libraries="")
|
||||
AC_ARG_WITH(libOggFLAC-includes,
|
||||
AS_HELP_STRING([--with-libOggFLAC-includes=DIR],
|
||||
[directory where libOggFLAC header files are installed (optional)]),,
|
||||
libOggFLAC_includes="")
|
||||
AC_ARG_ENABLE(libOggFLACtest,
|
||||
AS_HELP_STRING([--disable-libOggFLACtest],
|
||||
[do not try to compile and run a test libOggFLAC program]),,
|
||||
enable_libOggFLACtest=yes)
|
||||
|
||||
if test "x$libOggFLAC_libraries" != "x" ; then
|
||||
LIBOGGFLAC_LIBS="-L$libOggFLAC_libraries"
|
||||
elif test "x$libOggFLAC_prefix" != "x" ; then
|
||||
LIBOGGFLAC_LIBS="-L$libOggFLAC_prefix/lib"
|
||||
elif test "x$prefix" != "xNONE" ; then
|
||||
LIBOGGFLAC_LIBS="-L$libdir"
|
||||
fi
|
||||
|
||||
LIBOGGFLAC_LIBS="$LIBOGGFLAC_LIBS -lOggFLAC -lFLAC -lm"
|
||||
|
||||
if test "x$libOggFLAC_includes" != "x" ; then
|
||||
LIBOGGFLAC_CFLAGS="-I$libOggFLAC_includes"
|
||||
elif test "x$libOggFLAC_prefix" != "x" ; then
|
||||
LIBOGGFLAC_CFLAGS="-I$libOggFLAC_prefix/include"
|
||||
elif test "x$prefix" != "xNONE"; then
|
||||
LIBOGGFLAC_CFLAGS="-I$prefix/include"
|
||||
fi
|
||||
|
||||
AC_MSG_CHECKING(for libOggFLAC)
|
||||
no_libOggFLAC=""
|
||||
|
||||
|
||||
if test "x$enable_libOggFLACtest" = "xyes" ; then
|
||||
ac_save_CFLAGS="$CFLAGS"
|
||||
ac_save_CXXFLAGS="$CXXFLAGS"
|
||||
ac_save_LIBS="$LIBS"
|
||||
CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS"
|
||||
CXXFLAGS="$CXXFLAGS $LIBOGGFLAC_CFLAGS"
|
||||
LIBS="$LIBS $LIBOGGFLAC_LIBS"
|
||||
dnl
|
||||
dnl Now check if the installed libOggFLAC is sufficiently new.
|
||||
dnl
|
||||
rm -f conf.libOggFLACtest
|
||||
AC_TRY_RUN([
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <OggFLAC/stream_decoder.h>
|
||||
|
||||
int main ()
|
||||
{
|
||||
system("touch conf.libOggFLACtest");
|
||||
return 0;
|
||||
}
|
||||
|
||||
],, no_libOggFLAC=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"])
|
||||
CFLAGS="$ac_save_CFLAGS"
|
||||
LIBS="$ac_save_LIBS"
|
||||
fi
|
||||
|
||||
if test "x$no_libOggFLAC" = "x" ; then
|
||||
AC_MSG_RESULT(yes)
|
||||
ifelse([$1], , :, [$1])
|
||||
else
|
||||
AC_MSG_RESULT(no)
|
||||
if test -f conf.libOggFLACtest ; then
|
||||
:
|
||||
else
|
||||
echo "*** Could not run libOggFLAC test program, checking why..."
|
||||
CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS"
|
||||
LIBS="$LIBS $LIBOGGFLAC_LIBS"
|
||||
AC_TRY_LINK([
|
||||
#include <stdio.h>
|
||||
#include <OggFLAC/stream_decoder.h>
|
||||
], [ return 0; ],
|
||||
[ echo "*** The test program compiled, but did not run. This usually means"
|
||||
echo "*** that the run-time linker is not finding libOggFLAC or finding the wrong"
|
||||
echo "*** version of libOggFLAC. If it is not finding libOggFLAC, you'll need to set your"
|
||||
echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point"
|
||||
echo "*** to the installed location Also, make sure you have run ldconfig if that"
|
||||
echo "*** is required on your system"
|
||||
echo "***"
|
||||
echo "*** If you have an old version installed, it is best to remove it, although"
|
||||
echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"],
|
||||
[ echo "*** The test program failed to compile or link. See the file config.log for the"
|
||||
echo "*** exact error that occured. This usually means libOggFLAC was incorrectly installed"
|
||||
echo "*** or that you have moved libOggFLAC since it was installed. In the latter case, you"
|
||||
echo "*** may want to edit the libOggFLAC-config script: $LIBOGGFLAC_CONFIG" ])
|
||||
CFLAGS="$ac_save_CFLAGS"
|
||||
LIBS="$ac_save_LIBS"
|
||||
fi
|
||||
LIBOGGFLAC_CFLAGS=""
|
||||
LIBOGGFLAC_LIBS=""
|
||||
ifelse([$2], , :, [$2])
|
||||
fi
|
||||
AC_SUBST(LIBOGGFLAC_CFLAGS)
|
||||
AC_SUBST(LIBOGGFLAC_LIBS)
|
||||
rm -f conf.libOggFLACtest
|
||||
])
|
||||
14
m4/libwrap.m4
Normal file
14
m4/libwrap.m4
Normal file
@@ -0,0 +1,14 @@
|
||||
dnl
|
||||
dnl Usage:
|
||||
dnl AC_CHECK_LIBWRAP([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
|
||||
dnl
|
||||
|
||||
AC_DEFUN([AC_CHECK_LIBWRAP],[
|
||||
AC_CHECK_HEADERS([tcpd.h],
|
||||
AC_CHECK_LIB([wrap], [request_init],
|
||||
[LIBWRAP_CFLAGS=""
|
||||
LIBWRAP_LDFLAGS="-lwrap"
|
||||
$1],
|
||||
$2),
|
||||
$2)
|
||||
])
|
||||
19
m4/pretty_print.m4
Normal file
19
m4/pretty_print.m4
Normal file
@@ -0,0 +1,19 @@
|
||||
AC_DEFUN([results], [
|
||||
dnl This is a hack to allow "with" names, otherwise "enable".
|
||||
num=`expr match $1 'with'`
|
||||
if test "$num" != "0"; then
|
||||
var="`echo '$'$1`"
|
||||
else
|
||||
var="`echo '$'enable_$1`"
|
||||
fi
|
||||
|
||||
echo -n '('
|
||||
if eval "test x$var = xyes"; then
|
||||
echo -n '+'
|
||||
elif test -n "$3" && eval "test x$var = x$3"; then
|
||||
echo -n '+'
|
||||
else
|
||||
echo -n '-'
|
||||
fi
|
||||
echo -n "$2) "
|
||||
])
|
||||
47
scripts/check_config_h.rb
Executable file
47
scripts/check_config_h.rb
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env ruby
|
||||
#
|
||||
# This script verifies that every source includes config.h first.
|
||||
# This is very important for consistent Large File Support.
|
||||
#
|
||||
|
||||
def check_file(file)
|
||||
first = true
|
||||
file.each_line do |line|
|
||||
if line =~ /^\#include\s+(\S+)/ then
|
||||
if $1 == '"config.h"'
|
||||
unless first
|
||||
puts "#{file.path}: config.h included too late"
|
||||
end
|
||||
else
|
||||
if first
|
||||
puts "#{file.path}: config.h missing"
|
||||
end
|
||||
end
|
||||
first = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def check_path(path)
|
||||
File.open(path) do |file|
|
||||
check_file(file)
|
||||
end
|
||||
end
|
||||
|
||||
if ARGV.empty?
|
||||
Dir["src/*.c"].each do |path|
|
||||
check_path(path)
|
||||
end
|
||||
|
||||
Dir["src/*/*.c"].each do |path|
|
||||
check_path(path)
|
||||
end
|
||||
|
||||
Dir["test/*.c"].each do |path|
|
||||
check_path(path)
|
||||
end
|
||||
else
|
||||
ARGV.each do |path|
|
||||
check_path(path)
|
||||
end
|
||||
end
|
||||
@@ -18,6 +18,7 @@ test -x configure || NOCONFIGURE=1 ./autogen.sh
|
||||
./configure --prefix=$PREFIX/full \
|
||||
--disable-dependency-tracking --enable-debug --enable-werror \
|
||||
--enable-un \
|
||||
--enable-modplug \
|
||||
--enable-ao --enable-mikmod --enable-mvp
|
||||
$MAKE install
|
||||
$MAKE distclean
|
||||
@@ -47,6 +48,7 @@ $MAKE install
|
||||
$MAKE distclean
|
||||
|
||||
# shout: ogg without mp3
|
||||
# sndfile instead of modplug
|
||||
./configure --prefix=$PREFIX/shout_ogg \
|
||||
--disable-dependency-tracking --disable-debug --enable-werror \
|
||||
--disable-tcp \
|
||||
@@ -56,6 +58,7 @@ $MAKE distclean
|
||||
--enable-shout-ogg --disable-shout-mp3 --disable-lame-encoder \
|
||||
--disable-ffmpeg --disable-wavpack --disable-mpc --disable-aac \
|
||||
--disable-flac --enable-vorbis --disable-oggflac --disable-audiofile \
|
||||
--disable-modplug --enable-sndfile \
|
||||
--with-zeroconf=no
|
||||
$MAKE install
|
||||
$MAKE distclean
|
||||
|
||||
185
src/AudioCompress/compress.c
Normal file
185
src/AudioCompress/compress.c
Normal file
@@ -0,0 +1,185 @@
|
||||
/* compress.c
|
||||
* Compressor logic
|
||||
*
|
||||
* (c)2007 busybee (http://beesbuzz.biz/
|
||||
* Licensed under the terms of the LGPL. See the file COPYING for details.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "compress.h"
|
||||
|
||||
struct Compressor {
|
||||
//! The compressor's preferences
|
||||
struct CompressorConfig prefs;
|
||||
|
||||
//! History of the peak values
|
||||
int *peaks;
|
||||
|
||||
//! History of the gain values
|
||||
int *gain;
|
||||
|
||||
//! History of clip amounts
|
||||
int *clipped;
|
||||
|
||||
unsigned int pos;
|
||||
unsigned int bufsz;
|
||||
};
|
||||
|
||||
struct Compressor *Compressor_new(unsigned int history)
|
||||
{
|
||||
struct Compressor *obj = malloc(sizeof(struct Compressor));
|
||||
|
||||
obj->prefs.target = TARGET;
|
||||
obj->prefs.maxgain = GAINMAX;
|
||||
obj->prefs.smooth = GAINSMOOTH;
|
||||
|
||||
obj->peaks = obj->gain = obj->clipped = NULL;
|
||||
obj->bufsz = 0;
|
||||
obj->pos = 0;
|
||||
|
||||
Compressor_setHistory(obj, history);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void Compressor_delete(struct Compressor *obj)
|
||||
{
|
||||
if (obj->peaks)
|
||||
free(obj->peaks);
|
||||
if (obj->gain)
|
||||
free(obj->gain);
|
||||
if (obj->clipped)
|
||||
free(obj->clipped);
|
||||
free(obj);
|
||||
}
|
||||
|
||||
static int *resizeArray(int *data, int newsz, int oldsz)
|
||||
{
|
||||
data = realloc(data, newsz*sizeof(int));
|
||||
if (newsz > oldsz)
|
||||
memset(data + oldsz, 0, sizeof(int)*(newsz - oldsz));
|
||||
return data;
|
||||
}
|
||||
|
||||
void Compressor_setHistory(struct Compressor *obj, unsigned int history)
|
||||
{
|
||||
if (!history)
|
||||
history = BUCKETS;
|
||||
|
||||
obj->peaks = resizeArray(obj->peaks, history, obj->bufsz);
|
||||
obj->gain = resizeArray(obj->gain, history, obj->bufsz);
|
||||
obj->clipped = resizeArray(obj->clipped, history, obj->bufsz);
|
||||
obj->bufsz = history;
|
||||
}
|
||||
|
||||
struct CompressorConfig *Compressor_getConfig(struct Compressor *obj)
|
||||
{
|
||||
return &obj->prefs;
|
||||
}
|
||||
|
||||
void Compressor_Process_int16(struct Compressor *obj, int16_t *audio,
|
||||
unsigned int count)
|
||||
{
|
||||
struct CompressorConfig *prefs = Compressor_getConfig(obj);
|
||||
int16_t *ap;
|
||||
unsigned int i;
|
||||
int *peaks = obj->peaks;
|
||||
int curGain = obj->gain[obj->pos];
|
||||
int newGain;
|
||||
int peakVal = 1;
|
||||
int peakPos = 0;
|
||||
int slot = (obj->pos + 1) % obj->bufsz;
|
||||
int *clipped = obj->clipped + slot;
|
||||
unsigned int ramp = count;
|
||||
int delta;
|
||||
|
||||
ap = audio;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
int val = *ap++;
|
||||
if (val < 0)
|
||||
val = -val;
|
||||
if (val > peakVal)
|
||||
{
|
||||
peakVal = val;
|
||||
peakPos = i;
|
||||
}
|
||||
}
|
||||
peaks[slot] = peakVal;
|
||||
|
||||
|
||||
for (i = 0; i < obj->bufsz; i++)
|
||||
{
|
||||
if (peaks[i] > peakVal)
|
||||
{
|
||||
peakVal = peaks[i];
|
||||
peakPos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//! Determine target gain
|
||||
newGain = (1 << 10)*prefs->target/peakVal;
|
||||
|
||||
//! Adjust the gain with inertia from the previous gain value
|
||||
newGain = (curGain*((1 << prefs->smooth) - 1) + newGain)
|
||||
>> prefs->smooth;
|
||||
|
||||
//! Make sure it's no more than the maximum gain value
|
||||
if (newGain > (prefs->maxgain << 10))
|
||||
newGain = prefs->maxgain << 10;
|
||||
|
||||
//! Make sure it's no less than 1:1
|
||||
if (newGain < (1 << 10))
|
||||
newGain = 1 << 10;
|
||||
|
||||
//! Make sure the adjusted gain won't cause clipping
|
||||
if ((peakVal*newGain >> 10) > 32767)
|
||||
{
|
||||
newGain = (32767 << 10)/peakVal;
|
||||
//! Truncate the ramp time
|
||||
ramp = peakPos;
|
||||
}
|
||||
|
||||
//! Record the new gain
|
||||
obj->gain[slot] = newGain;
|
||||
|
||||
if (!ramp)
|
||||
ramp = 1;
|
||||
if (!curGain)
|
||||
curGain = 1 << 10;
|
||||
delta = (newGain - curGain) / (int)ramp;
|
||||
|
||||
ap = audio;
|
||||
*clipped = 0;
|
||||
for (i = 0; i < count; i++)
|
||||
{
|
||||
int sample;
|
||||
|
||||
//! Amplify the sample
|
||||
sample = *ap*curGain >> 10;
|
||||
if (sample < -32768)
|
||||
{
|
||||
*clipped += -32768 - sample;
|
||||
sample = -32768;
|
||||
} else if (sample > 32767)
|
||||
{
|
||||
*clipped += sample - 32767;
|
||||
sample = 32767;
|
||||
}
|
||||
*ap++ = sample;
|
||||
|
||||
//! Adjust the gain
|
||||
if (i < ramp)
|
||||
curGain += delta;
|
||||
else
|
||||
curGain = newGain;
|
||||
}
|
||||
|
||||
obj->pos = slot;
|
||||
}
|
||||
|
||||
40
src/AudioCompress/compress.h
Normal file
40
src/AudioCompress/compress.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*! compress.h
|
||||
* interface to audio compression
|
||||
*
|
||||
* (c)2007 busybee (http://beesbuzz.biz/)
|
||||
* Licensed under the terms of the LGPL. See the file COPYING for details.
|
||||
*/
|
||||
|
||||
#ifndef COMPRESS_H
|
||||
#define COMPRESS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
//! Configuration values for the compressor object
|
||||
struct CompressorConfig {
|
||||
int target;
|
||||
int maxgain;
|
||||
int smooth;
|
||||
};
|
||||
|
||||
struct Compressor;
|
||||
|
||||
//! Create a new compressor (use history value of 0 for default)
|
||||
struct Compressor *Compressor_new(unsigned int history);
|
||||
|
||||
//! Delete a compressor
|
||||
void Compressor_delete(struct Compressor *);
|
||||
|
||||
//! Set the history length
|
||||
void Compressor_setHistory(struct Compressor *, unsigned int history);
|
||||
|
||||
//! Get the configuration for a compressor
|
||||
struct CompressorConfig *Compressor_getConfig(struct Compressor *);
|
||||
|
||||
//! Process 16-bit signed data
|
||||
void Compressor_Process_int16(struct Compressor *, int16_t *data, unsigned int count);
|
||||
|
||||
//! TODO: Compressor_Process_int32, Compressor_Process_float, others as needed
|
||||
|
||||
//! TODO: functions for getting at the peak/gain/clip history buffers (for monitoring)
|
||||
#endif
|
||||
19
src/AudioCompress/config.h
Normal file
19
src/AudioCompress/config.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/* config.h
|
||||
** Default values for the configuration, and also a few random debug things
|
||||
*/
|
||||
|
||||
#ifndef AC_CONFIG_H
|
||||
#define AC_CONFIG_H
|
||||
|
||||
/*** Version information ***/
|
||||
#define ACVERSION "2.0"
|
||||
|
||||
/*** Default configuration stuff ***/
|
||||
#define TARGET 16384 /*!< Target level (on a scale of 0-32767) */
|
||||
|
||||
#define GAINMAX 32 /*!< The maximum amount to amplify by */
|
||||
#define GAINSMOOTH 8 /*!< How much inertia ramping has*/
|
||||
#define BUCKETS 400 /*!< How long of a history to use by default */
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h" /* must be first for large file support */
|
||||
#include "aiff.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
||||
301
src/archive/bz2_archive_plugin.c
Normal file
301
src/archive/bz2_archive_plugin.c
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* single bz2 archive handling (requires libbz2)
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "archive/bz2_archive_plugin.h"
|
||||
#include "archive_api.h"
|
||||
#include "input_plugin.h"
|
||||
#include "refcount.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include <bzlib.h>
|
||||
|
||||
#ifdef HAVE_OLDER_BZIP2
|
||||
#define BZ2_bzDecompressInit bzDecompressInit
|
||||
#define BZ2_bzDecompress bzDecompress
|
||||
#endif
|
||||
|
||||
struct bz2_archive_file {
|
||||
struct archive_file base;
|
||||
|
||||
struct refcount ref;
|
||||
|
||||
char *name;
|
||||
bool reset;
|
||||
struct input_stream *istream;
|
||||
};
|
||||
|
||||
struct bz2_input_stream {
|
||||
struct input_stream base;
|
||||
|
||||
struct bz2_archive_file *archive;
|
||||
|
||||
bool eof;
|
||||
|
||||
bz_stream bzstream;
|
||||
|
||||
char buffer[5000];
|
||||
};
|
||||
|
||||
static const struct input_plugin bz2_inputplugin;
|
||||
|
||||
static inline GQuark
|
||||
bz2_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("bz2");
|
||||
}
|
||||
|
||||
/* single archive handling allocation helpers */
|
||||
|
||||
static bool
|
||||
bz2_alloc(struct bz2_input_stream *data, GError **error_r)
|
||||
{
|
||||
int ret;
|
||||
|
||||
data->bzstream.bzalloc = NULL;
|
||||
data->bzstream.bzfree = NULL;
|
||||
data->bzstream.opaque = NULL;
|
||||
|
||||
data->bzstream.next_in = (void *) data->buffer;
|
||||
data->bzstream.avail_in = 0;
|
||||
|
||||
ret = BZ2_bzDecompressInit(&data->bzstream, 0, 0);
|
||||
if (ret != BZ_OK) {
|
||||
g_free(data);
|
||||
|
||||
g_set_error(error_r, bz2_quark(), ret,
|
||||
"BZ2_bzDecompressInit() has failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
bz2_destroy(struct bz2_input_stream *data)
|
||||
{
|
||||
BZ2_bzDecompressEnd(&data->bzstream);
|
||||
}
|
||||
|
||||
/* archive open && listing routine */
|
||||
|
||||
static struct archive_file *
|
||||
bz2_open(const char *pathname, GError **error_r)
|
||||
{
|
||||
struct bz2_archive_file *context;
|
||||
int len;
|
||||
|
||||
context = g_malloc(sizeof(*context));
|
||||
archive_file_init(&context->base, &bz2_archive_plugin);
|
||||
refcount_init(&context->ref);
|
||||
|
||||
//open archive
|
||||
context->istream = input_stream_open(pathname, error_r);
|
||||
if (context->istream == NULL) {
|
||||
g_free(context);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
context->name = g_path_get_basename(pathname);
|
||||
|
||||
//remove suffix
|
||||
len = strlen(context->name);
|
||||
if (len > 4) {
|
||||
context->name[len - 4] = 0; //remove .bz2 suffix
|
||||
}
|
||||
|
||||
return &context->base;
|
||||
}
|
||||
|
||||
static void
|
||||
bz2_scan_reset(struct archive_file *file)
|
||||
{
|
||||
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
|
||||
context->reset = true;
|
||||
}
|
||||
|
||||
static char *
|
||||
bz2_scan_next(struct archive_file *file)
|
||||
{
|
||||
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
|
||||
char *name = NULL;
|
||||
|
||||
if (context->reset) {
|
||||
name = context->name;
|
||||
context->reset = false;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static void
|
||||
bz2_close(struct archive_file *file)
|
||||
{
|
||||
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
|
||||
|
||||
if (!refcount_dec(&context->ref))
|
||||
return;
|
||||
|
||||
g_free(context->name);
|
||||
|
||||
input_stream_close(context->istream);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
|
||||
static struct input_stream *
|
||||
bz2_open_stream(struct archive_file *file, const char *path, GError **error_r)
|
||||
{
|
||||
struct bz2_archive_file *context = (struct bz2_archive_file *) file;
|
||||
struct bz2_input_stream *bis = g_new(struct bz2_input_stream, 1);
|
||||
|
||||
input_stream_init(&bis->base, &bz2_inputplugin, path);
|
||||
|
||||
bis->archive = context;
|
||||
|
||||
bis->base.ready = true;
|
||||
bis->base.seekable = false;
|
||||
|
||||
if (!bz2_alloc(bis, error_r)) {
|
||||
input_stream_deinit(&bis->base);
|
||||
g_free(bis);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bis->eof = false;
|
||||
|
||||
refcount_inc(&context->ref);
|
||||
|
||||
return &bis->base;
|
||||
}
|
||||
|
||||
static void
|
||||
bz2_is_close(struct input_stream *is)
|
||||
{
|
||||
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
|
||||
|
||||
bz2_destroy(bis);
|
||||
|
||||
bz2_close(&bis->archive->base);
|
||||
|
||||
input_stream_deinit(&bis->base);
|
||||
g_free(bis);
|
||||
}
|
||||
|
||||
static bool
|
||||
bz2_fillbuffer(struct bz2_input_stream *bis, GError **error_r)
|
||||
{
|
||||
size_t count;
|
||||
bz_stream *bzstream;
|
||||
|
||||
bzstream = &bis->bzstream;
|
||||
|
||||
if (bzstream->avail_in > 0)
|
||||
return true;
|
||||
|
||||
count = input_stream_read(bis->archive->istream,
|
||||
bis->buffer, sizeof(bis->buffer),
|
||||
error_r);
|
||||
if (count == 0)
|
||||
return false;
|
||||
|
||||
bzstream->next_in = bis->buffer;
|
||||
bzstream->avail_in = count;
|
||||
return true;
|
||||
}
|
||||
|
||||
static size_t
|
||||
bz2_is_read(struct input_stream *is, void *ptr, size_t length,
|
||||
GError **error_r)
|
||||
{
|
||||
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
|
||||
bz_stream *bzstream;
|
||||
int bz_result;
|
||||
size_t nbytes = 0;
|
||||
|
||||
if (bis->eof)
|
||||
return 0;
|
||||
|
||||
bzstream = &bis->bzstream;
|
||||
bzstream->next_out = ptr;
|
||||
bzstream->avail_out = length;
|
||||
|
||||
do {
|
||||
if (!bz2_fillbuffer(bis, error_r))
|
||||
return 0;
|
||||
|
||||
bz_result = BZ2_bzDecompress(bzstream);
|
||||
|
||||
if (bz_result == BZ_STREAM_END) {
|
||||
bis->eof = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bz_result != BZ_OK) {
|
||||
g_set_error(error_r, bz2_quark(), bz_result,
|
||||
"BZ2_bzDecompress() has failed");
|
||||
return 0;
|
||||
}
|
||||
} while (bzstream->avail_out == length);
|
||||
|
||||
nbytes = length - bzstream->avail_out;
|
||||
is->offset += nbytes;
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
static bool
|
||||
bz2_is_eof(struct input_stream *is)
|
||||
{
|
||||
struct bz2_input_stream *bis = (struct bz2_input_stream *)is;
|
||||
|
||||
return bis->eof;
|
||||
}
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const bz2_extensions[] = {
|
||||
"bz2",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct input_plugin bz2_inputplugin = {
|
||||
.close = bz2_is_close,
|
||||
.read = bz2_is_read,
|
||||
.eof = bz2_is_eof,
|
||||
};
|
||||
|
||||
const struct archive_plugin bz2_archive_plugin = {
|
||||
.name = "bz2",
|
||||
.open = bz2_open,
|
||||
.scan_reset = bz2_scan_reset,
|
||||
.scan_next = bz2_scan_next,
|
||||
.open_stream = bz2_open_stream,
|
||||
.close = bz2_close,
|
||||
.suffixes = bz2_extensions
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,9 +17,9 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef LASTFM_INPUT_PLUGIN_H
|
||||
#define LASTFM_INPUT_PLUGIN_H
|
||||
#ifndef MPD_ARCHIVE_BZ2_H
|
||||
#define MPD_ARCHIVE_BZ2_H
|
||||
|
||||
extern const struct input_plugin lastfm_input_plugin;
|
||||
extern const struct archive_plugin bz2_archive_plugin;
|
||||
|
||||
#endif
|
||||
@@ -1,284 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* single bz2 archive handling (requires libbz2)
|
||||
*/
|
||||
|
||||
#include "archive_api.h"
|
||||
#include "input_plugin.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
#include <bzlib.h>
|
||||
|
||||
#ifdef HAVE_OLDER_BZIP2
|
||||
#define BZ2_bzDecompressInit bzDecompressInit
|
||||
#define BZ2_bzDecompress bzDecompress
|
||||
#endif
|
||||
|
||||
#define BZ_BUFSIZE 5000
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
bool reset;
|
||||
struct input_stream istream;
|
||||
int last_bz_result;
|
||||
int last_parent_result;
|
||||
bz_stream bzstream;
|
||||
char *buffer;
|
||||
} bz2_context;
|
||||
|
||||
|
||||
static const struct input_plugin bz2_inputplugin;
|
||||
|
||||
/* single archive handling allocation helpers */
|
||||
|
||||
static bool
|
||||
bz2_alloc(bz2_context *data)
|
||||
{
|
||||
data->bzstream.bzalloc = NULL;
|
||||
data->bzstream.bzfree = NULL;
|
||||
data->bzstream.opaque = NULL;
|
||||
|
||||
data->buffer = g_malloc(BZ_BUFSIZE);
|
||||
data->bzstream.next_in = (void *) data->buffer;
|
||||
data->bzstream.avail_in = 0;
|
||||
|
||||
if (BZ2_bzDecompressInit(&data->bzstream, 0, 0) != BZ_OK) {
|
||||
g_free(data->buffer);
|
||||
g_free(data);
|
||||
return false;
|
||||
}
|
||||
|
||||
data->last_bz_result = BZ_OK;
|
||||
data->last_parent_result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
bz2_destroy(bz2_context *data)
|
||||
{
|
||||
BZ2_bzDecompressEnd(&data->bzstream);
|
||||
g_free(data->buffer);
|
||||
}
|
||||
|
||||
/* archive open && listing routine */
|
||||
|
||||
static struct archive_file *
|
||||
bz2_open(char * pathname)
|
||||
{
|
||||
bz2_context *context;
|
||||
char *name;
|
||||
int len;
|
||||
|
||||
context = g_malloc(sizeof(bz2_context));
|
||||
if (!context) {
|
||||
return NULL;
|
||||
}
|
||||
//open archive
|
||||
if (!input_stream_open(&context->istream, pathname)) {
|
||||
g_warning("failed to open an bzip2 archive %s\n",pathname);
|
||||
g_free(context);
|
||||
return NULL;
|
||||
}
|
||||
//capture filename
|
||||
name = strrchr(pathname, '/');
|
||||
if (name == NULL) {
|
||||
g_warning("failed to get bzip2 name from %s\n",pathname);
|
||||
g_free(context);
|
||||
return NULL;
|
||||
}
|
||||
context->name = g_strdup(name+1);
|
||||
//remove suffix
|
||||
len = strlen(context->name);
|
||||
if (len > 4) {
|
||||
context->name[len-4] = 0; //remove .bz2 suffix
|
||||
}
|
||||
return (struct archive_file *) context;
|
||||
}
|
||||
|
||||
static void
|
||||
bz2_scan_reset(struct archive_file *file)
|
||||
{
|
||||
bz2_context *context = (bz2_context *) file;
|
||||
context->reset = true;
|
||||
}
|
||||
|
||||
static char *
|
||||
bz2_scan_next(struct archive_file *file)
|
||||
{
|
||||
bz2_context *context = (bz2_context *) file;
|
||||
char *name = NULL;
|
||||
if (context->reset) {
|
||||
name = context->name;
|
||||
context->reset = false;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
static void
|
||||
bz2_close(struct archive_file *file)
|
||||
{
|
||||
bz2_context *context = (bz2_context *) file;
|
||||
|
||||
g_free(context->name);
|
||||
|
||||
input_stream_close(&context->istream);
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
|
||||
static bool
|
||||
bz2_open_stream(struct archive_file *file, struct input_stream *is,
|
||||
G_GNUC_UNUSED const char *path)
|
||||
{
|
||||
bz2_context *context = (bz2_context *) file;
|
||||
//setup file ops
|
||||
is->plugin = &bz2_inputplugin;
|
||||
//insert back reference
|
||||
is->data = context;
|
||||
is->seekable = false;
|
||||
|
||||
if (!bz2_alloc(context)) {
|
||||
g_warning("alloc bz2 failed\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
bz2_is_close(struct input_stream *is)
|
||||
{
|
||||
bz2_context *context = (bz2_context *) is->data;
|
||||
bz2_destroy(context);
|
||||
is->data = NULL;
|
||||
|
||||
bz2_close((struct archive_file *)context);
|
||||
}
|
||||
|
||||
static int
|
||||
bz2_fillbuffer(bz2_context *context,
|
||||
size_t numBytes)
|
||||
{
|
||||
size_t count;
|
||||
bz_stream *bzstream;
|
||||
|
||||
bzstream = &context->bzstream;
|
||||
|
||||
if (bzstream->avail_in > 0)
|
||||
return 0;
|
||||
|
||||
count = input_stream_read(&context->istream,
|
||||
context->buffer, BZ_BUFSIZE);
|
||||
|
||||
if (count == 0) {
|
||||
if (bzstream->avail_out == numBytes)
|
||||
return -1;
|
||||
if (!input_stream_eof(&context->istream))
|
||||
context->last_parent_result = 1;
|
||||
} else {
|
||||
bzstream->next_in = context->buffer;
|
||||
bzstream->avail_in = count;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static size_t
|
||||
bz2_is_read(struct input_stream *is, void *ptr, size_t size)
|
||||
{
|
||||
bz2_context *context = (bz2_context *) is->data;
|
||||
bz_stream *bzstream;
|
||||
int bz_result;
|
||||
size_t numBytes = size;
|
||||
size_t bytesRead = 0;
|
||||
|
||||
if (context->last_bz_result != BZ_OK)
|
||||
return 0;
|
||||
if (context->last_parent_result != 0)
|
||||
return 0;
|
||||
|
||||
bzstream = &context->bzstream;
|
||||
bzstream->next_out = ptr;
|
||||
bzstream->avail_out = numBytes;
|
||||
|
||||
while (bzstream->avail_out != 0) {
|
||||
if (bz2_fillbuffer(context, numBytes) != 0)
|
||||
break;
|
||||
|
||||
bz_result = BZ2_bzDecompress(bzstream);
|
||||
|
||||
if (context->last_bz_result != BZ_OK
|
||||
&& bzstream->avail_out == numBytes) {
|
||||
context->last_bz_result = bz_result;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bz_result == BZ_STREAM_END) {
|
||||
context->last_bz_result = bz_result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bytesRead = numBytes - bzstream->avail_out;
|
||||
is->offset += bytesRead;
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
static bool
|
||||
bz2_is_eof(struct input_stream *is)
|
||||
{
|
||||
bz2_context *context = (bz2_context *) is->data;
|
||||
|
||||
if (context->last_bz_result == BZ_STREAM_END) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const bz2_extensions[] = {
|
||||
"bz2",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct input_plugin bz2_inputplugin = {
|
||||
.close = bz2_is_close,
|
||||
.read = bz2_is_read,
|
||||
.eof = bz2_is_eof,
|
||||
};
|
||||
|
||||
const struct archive_plugin bz2_plugin = {
|
||||
.name = "bz2",
|
||||
.open = bz2_open,
|
||||
.scan_reset = bz2_scan_reset,
|
||||
.scan_next = bz2_scan_next,
|
||||
.open_stream = bz2_open_stream,
|
||||
.close = bz2_close,
|
||||
.suffixes = bz2_extensions
|
||||
};
|
||||
|
||||
287
src/archive/iso9660_archive_plugin.c
Normal file
287
src/archive/iso9660_archive_plugin.c
Normal file
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* iso archive handling (requires cdio, and iso9660)
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "archive/iso9660_archive_plugin.h"
|
||||
#include "archive_api.h"
|
||||
#include "input_plugin.h"
|
||||
#include "refcount.h"
|
||||
|
||||
#include <cdio/cdio.h>
|
||||
#include <cdio/iso9660.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CEILING(x, y) ((x+(y-1))/y)
|
||||
|
||||
struct iso9660_archive_file {
|
||||
struct archive_file base;
|
||||
|
||||
struct refcount ref;
|
||||
|
||||
iso9660_t *iso;
|
||||
GSList *list;
|
||||
GSList *iter;
|
||||
};
|
||||
|
||||
static const struct input_plugin iso9660_input_plugin;
|
||||
|
||||
static inline GQuark
|
||||
iso9660_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("iso9660");
|
||||
}
|
||||
|
||||
/* archive open && listing routine */
|
||||
|
||||
static void
|
||||
listdir_recur(const char *psz_path, struct iso9660_archive_file *context)
|
||||
{
|
||||
iso9660_t *iso = context->iso;
|
||||
CdioList_t *entlist;
|
||||
CdioListNode_t *entnode;
|
||||
iso9660_stat_t *statbuf;
|
||||
char pathname[4096];
|
||||
|
||||
entlist = iso9660_ifs_readdir (iso, psz_path);
|
||||
if (!entlist) {
|
||||
return;
|
||||
}
|
||||
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
|
||||
_CDIO_LIST_FOREACH (entnode, entlist) {
|
||||
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
|
||||
|
||||
strcpy(pathname, psz_path);
|
||||
strcat(pathname, statbuf->filename);
|
||||
|
||||
if (_STAT_DIR == statbuf->type ) {
|
||||
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
|
||||
strcat(pathname, "/");
|
||||
listdir_recur(pathname, context);
|
||||
}
|
||||
} else {
|
||||
//remove leading /
|
||||
context->list = g_slist_prepend( context->list,
|
||||
g_strdup(pathname + 1));
|
||||
}
|
||||
}
|
||||
_cdio_list_free (entlist, true);
|
||||
}
|
||||
|
||||
static struct archive_file *
|
||||
iso9660_archive_open(const char *pathname, GError **error_r)
|
||||
{
|
||||
struct iso9660_archive_file *context =
|
||||
g_new(struct iso9660_archive_file, 1);
|
||||
|
||||
archive_file_init(&context->base, &iso9660_archive_plugin);
|
||||
refcount_init(&context->ref);
|
||||
|
||||
context->list = NULL;
|
||||
|
||||
/* open archive */
|
||||
context->iso = iso9660_open (pathname);
|
||||
if (context->iso == NULL) {
|
||||
g_set_error(error_r, iso9660_quark(), 0,
|
||||
"Failed to open ISO9660 file %s", pathname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
listdir_recur("/", context);
|
||||
|
||||
return &context->base;
|
||||
}
|
||||
|
||||
static void
|
||||
iso9660_archive_scan_reset(struct archive_file *file)
|
||||
{
|
||||
struct iso9660_archive_file *context =
|
||||
(struct iso9660_archive_file *)file;
|
||||
|
||||
//reset iterator
|
||||
context->iter = context->list;
|
||||
}
|
||||
|
||||
static char *
|
||||
iso9660_archive_scan_next(struct archive_file *file)
|
||||
{
|
||||
struct iso9660_archive_file *context =
|
||||
(struct iso9660_archive_file *)file;
|
||||
|
||||
char *data = NULL;
|
||||
if (context->iter != NULL) {
|
||||
///fetch data and goto next
|
||||
data = context->iter->data;
|
||||
context->iter = g_slist_next(context->iter);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
iso9660_archive_close(struct archive_file *file)
|
||||
{
|
||||
struct iso9660_archive_file *context =
|
||||
(struct iso9660_archive_file *)file;
|
||||
GSList *tmp;
|
||||
|
||||
if (!refcount_dec(&context->ref))
|
||||
return;
|
||||
|
||||
if (context->list) {
|
||||
//free list
|
||||
for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||
g_free(tmp->data);
|
||||
g_slist_free(context->list);
|
||||
}
|
||||
//close archive
|
||||
iso9660_close(context->iso);
|
||||
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
|
||||
struct iso9660_input_stream {
|
||||
struct input_stream base;
|
||||
|
||||
struct iso9660_archive_file *archive;
|
||||
|
||||
iso9660_stat_t *statbuf;
|
||||
size_t max_blocks;
|
||||
};
|
||||
|
||||
static struct input_stream *
|
||||
iso9660_archive_open_stream(struct archive_file *file,
|
||||
const char *pathname, GError **error_r)
|
||||
{
|
||||
struct iso9660_archive_file *context =
|
||||
(struct iso9660_archive_file *)file;
|
||||
struct iso9660_input_stream *iis;
|
||||
|
||||
iis = g_new(struct iso9660_input_stream, 1);
|
||||
input_stream_init(&iis->base, &iso9660_input_plugin, pathname);
|
||||
|
||||
iis->archive = context;
|
||||
iis->statbuf = iso9660_ifs_stat_translate(context->iso, pathname);
|
||||
if (iis->statbuf == NULL) {
|
||||
g_free(iis);
|
||||
g_set_error(error_r, iso9660_quark(), 0,
|
||||
"not found in the ISO file: %s", pathname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
iis->base.ready = true;
|
||||
//we are not seekable
|
||||
iis->base.seekable = false;
|
||||
|
||||
iis->base.size = iis->statbuf->size;
|
||||
|
||||
iis->max_blocks = CEILING(iis->statbuf->size, ISO_BLOCKSIZE);
|
||||
|
||||
refcount_inc(&context->ref);
|
||||
|
||||
return &iis->base;
|
||||
}
|
||||
|
||||
static void
|
||||
iso9660_input_close(struct input_stream *is)
|
||||
{
|
||||
struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
|
||||
|
||||
g_free(iis->statbuf);
|
||||
|
||||
iso9660_archive_close(&iis->archive->base);
|
||||
|
||||
input_stream_deinit(&iis->base);
|
||||
g_free(iis);
|
||||
}
|
||||
|
||||
|
||||
static size_t
|
||||
iso9660_input_read(struct input_stream *is, void *ptr, size_t size, GError **error_r)
|
||||
{
|
||||
struct iso9660_input_stream *iis = (struct iso9660_input_stream *)is;
|
||||
int toread, readed = 0;
|
||||
int no_blocks, cur_block;
|
||||
size_t left_bytes = iis->statbuf->size - is->offset;
|
||||
|
||||
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
|
||||
|
||||
if (left_bytes < size) {
|
||||
toread = left_bytes;
|
||||
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
|
||||
} else {
|
||||
toread = size;
|
||||
no_blocks = toread / ISO_BLOCKSIZE;
|
||||
}
|
||||
if (no_blocks > 0) {
|
||||
|
||||
cur_block = is->offset / ISO_BLOCKSIZE;
|
||||
|
||||
readed = iso9660_iso_seek_read (iis->archive->iso, ptr,
|
||||
iis->statbuf->lsn + cur_block, no_blocks);
|
||||
|
||||
if (readed != no_blocks * ISO_BLOCKSIZE) {
|
||||
g_set_error(error_r, iso9660_quark(), 0,
|
||||
"error reading ISO file at lsn %lu",
|
||||
(long unsigned int) cur_block);
|
||||
return 0;
|
||||
}
|
||||
if (left_bytes < size) {
|
||||
readed = left_bytes;
|
||||
}
|
||||
|
||||
is->offset += readed;
|
||||
}
|
||||
return readed;
|
||||
}
|
||||
|
||||
static bool
|
||||
iso9660_input_eof(struct input_stream *is)
|
||||
{
|
||||
return is->offset == is->size;
|
||||
}
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const iso9660_archive_extensions[] = {
|
||||
"iso",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct input_plugin iso9660_input_plugin = {
|
||||
.close = iso9660_input_close,
|
||||
.read = iso9660_input_read,
|
||||
.eof = iso9660_input_eof,
|
||||
};
|
||||
|
||||
const struct archive_plugin iso9660_archive_plugin = {
|
||||
.name = "iso",
|
||||
.open = iso9660_archive_open,
|
||||
.scan_reset = iso9660_archive_scan_reset,
|
||||
.scan_next = iso9660_archive_scan_next,
|
||||
.open_stream = iso9660_archive_open_stream,
|
||||
.close = iso9660_archive_close,
|
||||
.suffixes = iso9660_archive_extensions
|
||||
};
|
||||
25
src/archive/iso9660_archive_plugin.h
Normal file
25
src/archive/iso9660_archive_plugin.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_ARCHIVE_ISO9660_H
|
||||
#define MPD_ARCHIVE_ISO9660_H
|
||||
|
||||
extern const struct archive_plugin iso9660_archive_plugin;
|
||||
|
||||
#endif
|
||||
@@ -1,239 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* iso archive handling (requires cdio, and iso9660)
|
||||
*/
|
||||
|
||||
#include "archive_api.h"
|
||||
#include "input_plugin.h"
|
||||
|
||||
#include <cdio/cdio.h>
|
||||
#include <cdio/iso9660.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CEILING(x, y) ((x+(y-1))/y)
|
||||
|
||||
typedef struct {
|
||||
iso9660_t *iso;
|
||||
iso9660_stat_t *statbuf;
|
||||
size_t cur_ofs;
|
||||
size_t max_blocks;
|
||||
GSList *list;
|
||||
GSList *iter;
|
||||
} iso_context;
|
||||
|
||||
static const struct input_plugin iso_inputplugin;
|
||||
|
||||
/* archive open && listing routine */
|
||||
|
||||
static void
|
||||
listdir_recur(const char *psz_path, iso_context *context)
|
||||
{
|
||||
iso9660_t *iso = context->iso;
|
||||
CdioList_t *entlist;
|
||||
CdioListNode_t *entnode;
|
||||
iso9660_stat_t *statbuf;
|
||||
char pathname[4096];
|
||||
|
||||
entlist = iso9660_ifs_readdir (iso, psz_path);
|
||||
if (!entlist) {
|
||||
return;
|
||||
}
|
||||
/* Iterate over the list of nodes that iso9660_ifs_readdir gives */
|
||||
_CDIO_LIST_FOREACH (entnode, entlist) {
|
||||
statbuf = (iso9660_stat_t *) _cdio_list_node_data (entnode);
|
||||
|
||||
strcpy(pathname, psz_path);
|
||||
strcat(pathname, statbuf->filename);
|
||||
|
||||
if (_STAT_DIR == statbuf->type ) {
|
||||
if (strcmp(statbuf->filename, ".") && strcmp(statbuf->filename, "..")) {
|
||||
strcat(pathname, "/");
|
||||
listdir_recur(pathname, context);
|
||||
}
|
||||
} else {
|
||||
//remove leading /
|
||||
context->list = g_slist_prepend( context->list,
|
||||
g_strdup(pathname + 1));
|
||||
}
|
||||
}
|
||||
_cdio_list_free (entlist, true);
|
||||
}
|
||||
|
||||
static struct archive_file *
|
||||
iso_open(char * pathname)
|
||||
{
|
||||
iso_context *context = g_malloc(sizeof(iso_context));
|
||||
|
||||
context->list = NULL;
|
||||
|
||||
/* open archive */
|
||||
context->iso = iso9660_open (pathname);
|
||||
if (context->iso == NULL) {
|
||||
g_warning("iso %s open failed\n", pathname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
listdir_recur("/", context);
|
||||
|
||||
return (struct archive_file *)context;
|
||||
}
|
||||
|
||||
static void
|
||||
iso_scan_reset(struct archive_file *file)
|
||||
{
|
||||
iso_context *context = (iso_context *) file;
|
||||
//reset iterator
|
||||
context->iter = context->list;
|
||||
}
|
||||
|
||||
static char *
|
||||
iso_scan_next(struct archive_file *file)
|
||||
{
|
||||
iso_context *context = (iso_context *) file;
|
||||
char *data = NULL;
|
||||
if (context->iter != NULL) {
|
||||
///fetch data and goto next
|
||||
data = context->iter->data;
|
||||
context->iter = g_slist_next(context->iter);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
iso_close(struct archive_file *file)
|
||||
{
|
||||
iso_context *context = (iso_context *) file;
|
||||
GSList *tmp;
|
||||
if (context->list) {
|
||||
//free list
|
||||
for (tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||
g_free(tmp->data);
|
||||
g_slist_free(context->list);
|
||||
}
|
||||
//close archive
|
||||
iso9660_close(context->iso);
|
||||
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
|
||||
static bool
|
||||
iso_open_stream(struct archive_file *file, struct input_stream *is,
|
||||
const char *pathname)
|
||||
{
|
||||
iso_context *context = (iso_context *) file;
|
||||
//setup file ops
|
||||
is->plugin = &iso_inputplugin;
|
||||
//insert back reference
|
||||
is->data = context;
|
||||
//we are not seekable
|
||||
is->seekable = false;
|
||||
|
||||
context->statbuf = iso9660_ifs_stat_translate (context->iso, pathname);
|
||||
|
||||
if (context->statbuf == NULL) {
|
||||
g_warning("file %s not found in iso\n", pathname);
|
||||
return false;
|
||||
}
|
||||
context->cur_ofs = 0;
|
||||
context->max_blocks = CEILING(context->statbuf->size, ISO_BLOCKSIZE);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
iso_is_close(struct input_stream *is)
|
||||
{
|
||||
iso_context *context = (iso_context *) is->data;
|
||||
g_free(context->statbuf);
|
||||
|
||||
iso_close((struct archive_file *)context);
|
||||
}
|
||||
|
||||
|
||||
static size_t
|
||||
iso_is_read(struct input_stream *is, void *ptr, size_t size)
|
||||
{
|
||||
iso_context *context = (iso_context *) is->data;
|
||||
int toread, readed = 0;
|
||||
int no_blocks, cur_block;
|
||||
size_t left_bytes = context->statbuf->size - context->cur_ofs;
|
||||
|
||||
size = (size * ISO_BLOCKSIZE) / ISO_BLOCKSIZE;
|
||||
|
||||
if (left_bytes < size) {
|
||||
toread = left_bytes;
|
||||
no_blocks = CEILING(left_bytes,ISO_BLOCKSIZE);
|
||||
} else {
|
||||
toread = size;
|
||||
no_blocks = toread / ISO_BLOCKSIZE;
|
||||
}
|
||||
if (no_blocks > 0) {
|
||||
|
||||
cur_block = context->cur_ofs / ISO_BLOCKSIZE;
|
||||
|
||||
readed = iso9660_iso_seek_read (context->iso, ptr,
|
||||
context->statbuf->lsn + cur_block, no_blocks);
|
||||
|
||||
if (readed != no_blocks * ISO_BLOCKSIZE) {
|
||||
g_warning("error reading ISO file at lsn %lu\n",
|
||||
(long unsigned int) cur_block );
|
||||
return -1;
|
||||
}
|
||||
if (left_bytes < size) {
|
||||
readed = left_bytes;
|
||||
}
|
||||
context->cur_ofs += readed;
|
||||
}
|
||||
return readed;
|
||||
}
|
||||
|
||||
static bool
|
||||
iso_is_eof(struct input_stream *is)
|
||||
{
|
||||
iso_context *context = (iso_context *) is->data;
|
||||
return (context->cur_ofs == context->statbuf->size);
|
||||
}
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const iso_extensions[] = {
|
||||
"iso",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct input_plugin iso_inputplugin = {
|
||||
.close = iso_is_close,
|
||||
.read = iso_is_read,
|
||||
.eof = iso_is_eof,
|
||||
};
|
||||
|
||||
const struct archive_plugin iso_plugin = {
|
||||
.name = "iso",
|
||||
.open = iso_open,
|
||||
.scan_reset = iso_scan_reset,
|
||||
.scan_next = iso_scan_next,
|
||||
.open_stream = iso_open_stream,
|
||||
.close = iso_close,
|
||||
.suffixes = iso_extensions
|
||||
};
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* zip archive handling (requires zziplib)
|
||||
*/
|
||||
|
||||
#include "archive_api.h"
|
||||
#include "archive_api.h"
|
||||
#include "input_plugin.h"
|
||||
|
||||
#include <zzip/zzip.h>
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
ZZIP_DIR *dir;
|
||||
ZZIP_FILE *file;
|
||||
size_t length;
|
||||
GSList *list;
|
||||
GSList *iter;
|
||||
} zip_context;
|
||||
|
||||
static const struct input_plugin zip_inputplugin;
|
||||
|
||||
/* archive open && listing routine */
|
||||
|
||||
static struct archive_file *
|
||||
zip_open(char * pathname)
|
||||
{
|
||||
zip_context *context = g_malloc(sizeof(zip_context));
|
||||
ZZIP_DIRENT dirent;
|
||||
|
||||
// open archive
|
||||
context->list = NULL;
|
||||
context->dir = zzip_dir_open(pathname, NULL);
|
||||
if (context->dir == NULL) {
|
||||
g_warning("zipfile %s open failed\n", pathname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (zzip_dir_read(context->dir, &dirent)) {
|
||||
//add only files
|
||||
if (dirent.st_size > 0) {
|
||||
context->list = g_slist_prepend(context->list,
|
||||
g_strdup(dirent.d_name));
|
||||
}
|
||||
}
|
||||
|
||||
return (struct archive_file *)context;
|
||||
}
|
||||
|
||||
static void
|
||||
zip_scan_reset(struct archive_file *file)
|
||||
{
|
||||
zip_context *context = (zip_context *) file;
|
||||
//reset iterator
|
||||
context->iter = context->list;
|
||||
}
|
||||
|
||||
static char *
|
||||
zip_scan_next(struct archive_file *file)
|
||||
{
|
||||
zip_context *context = (zip_context *) file;
|
||||
char *data = NULL;
|
||||
if (context->iter != NULL) {
|
||||
///fetch data and goto next
|
||||
data = context->iter->data;
|
||||
context->iter = g_slist_next(context->iter);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
zip_close(struct archive_file *file)
|
||||
{
|
||||
zip_context *context = (zip_context *) file;
|
||||
if (context->list) {
|
||||
//free list
|
||||
for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||
g_free(tmp->data);
|
||||
g_slist_free(context->list);
|
||||
}
|
||||
//close archive
|
||||
zzip_dir_close (context->dir);
|
||||
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
|
||||
static bool
|
||||
zip_open_stream(struct archive_file *file, struct input_stream *is,
|
||||
const char *pathname)
|
||||
{
|
||||
zip_context *context = (zip_context *) file;
|
||||
ZZIP_STAT z_stat;
|
||||
|
||||
//setup file ops
|
||||
is->plugin = &zip_inputplugin;
|
||||
//insert back reference
|
||||
is->data = context;
|
||||
//we are seekable (but its not recommendent to do so)
|
||||
is->seekable = true;
|
||||
|
||||
context->file = zzip_file_open(context->dir, pathname, 0);
|
||||
if (!context->file) {
|
||||
g_warning("file %s not found in the zipfile\n", pathname);
|
||||
return false;
|
||||
}
|
||||
zzip_file_stat(context->file, &z_stat);
|
||||
context->length = z_stat.st_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
zip_is_close(struct input_stream *is)
|
||||
{
|
||||
zip_context *context = (zip_context *) is->data;
|
||||
zzip_file_close (context->file);
|
||||
|
||||
zip_close((struct archive_file *)context);
|
||||
}
|
||||
|
||||
static size_t
|
||||
zip_is_read(struct input_stream *is, void *ptr, size_t size)
|
||||
{
|
||||
zip_context *context = (zip_context *) is->data;
|
||||
int ret;
|
||||
ret = zzip_file_read(context->file, ptr, size);
|
||||
if (ret < 0) {
|
||||
g_warning("error %d reading zipfile\n", ret);
|
||||
return 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
zip_is_eof(struct input_stream *is)
|
||||
{
|
||||
zip_context *context = (zip_context *) is->data;
|
||||
return ((size_t) zzip_tell(context->file) == context->length);
|
||||
}
|
||||
|
||||
static bool
|
||||
zip_is_seek(G_GNUC_UNUSED struct input_stream *is,
|
||||
G_GNUC_UNUSED off_t offset, G_GNUC_UNUSED int whence)
|
||||
{
|
||||
zip_context *context = (zip_context *) is->data;
|
||||
zzip_off_t ofs = zzip_seek(context->file, offset, whence);
|
||||
if (ofs != -1) {
|
||||
is->offset = ofs;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const zip_extensions[] = {
|
||||
"zip",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct input_plugin zip_inputplugin = {
|
||||
.close = zip_is_close,
|
||||
.read = zip_is_read,
|
||||
.eof = zip_is_eof,
|
||||
.seek = zip_is_seek,
|
||||
};
|
||||
|
||||
const struct archive_plugin zip_plugin = {
|
||||
.name = "zip",
|
||||
.open = zip_open,
|
||||
.scan_reset = zip_scan_reset,
|
||||
.scan_next = zip_scan_next,
|
||||
.open_stream = zip_open_stream,
|
||||
.close = zip_close,
|
||||
.suffixes = zip_extensions
|
||||
};
|
||||
242
src/archive/zzip_archive_plugin.c
Normal file
242
src/archive/zzip_archive_plugin.c
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/**
|
||||
* zip archive handling (requires zziplib)
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "archive/zzip_archive_plugin.h"
|
||||
#include "archive_api.h"
|
||||
#include "archive_api.h"
|
||||
#include "input_plugin.h"
|
||||
#include "refcount.h"
|
||||
|
||||
#include <zzip/zzip.h>
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
|
||||
struct zzip_archive {
|
||||
struct archive_file base;
|
||||
|
||||
struct refcount ref;
|
||||
|
||||
ZZIP_DIR *dir;
|
||||
GSList *list;
|
||||
GSList *iter;
|
||||
};
|
||||
|
||||
static const struct input_plugin zzip_input_plugin;
|
||||
|
||||
static inline GQuark
|
||||
zzip_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("zzip");
|
||||
}
|
||||
|
||||
/* archive open && listing routine */
|
||||
|
||||
static struct archive_file *
|
||||
zzip_archive_open(const char *pathname, GError **error_r)
|
||||
{
|
||||
struct zzip_archive *context = g_malloc(sizeof(*context));
|
||||
ZZIP_DIRENT dirent;
|
||||
|
||||
archive_file_init(&context->base, &zzip_archive_plugin);
|
||||
refcount_init(&context->ref);
|
||||
|
||||
// open archive
|
||||
context->list = NULL;
|
||||
context->dir = zzip_dir_open(pathname, NULL);
|
||||
if (context->dir == NULL) {
|
||||
g_set_error(error_r, zzip_quark(), 0,
|
||||
"Failed to open ZIP file %s", pathname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
while (zzip_dir_read(context->dir, &dirent)) {
|
||||
//add only files
|
||||
if (dirent.st_size > 0) {
|
||||
context->list = g_slist_prepend(context->list,
|
||||
g_strdup(dirent.d_name));
|
||||
}
|
||||
}
|
||||
|
||||
return &context->base;
|
||||
}
|
||||
|
||||
static void
|
||||
zzip_archive_scan_reset(struct archive_file *file)
|
||||
{
|
||||
struct zzip_archive *context = (struct zzip_archive *) file;
|
||||
//reset iterator
|
||||
context->iter = context->list;
|
||||
}
|
||||
|
||||
static char *
|
||||
zzip_archive_scan_next(struct archive_file *file)
|
||||
{
|
||||
struct zzip_archive *context = (struct zzip_archive *) file;
|
||||
char *data = NULL;
|
||||
if (context->iter != NULL) {
|
||||
///fetch data and goto next
|
||||
data = context->iter->data;
|
||||
context->iter = g_slist_next(context->iter);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
zzip_archive_close(struct archive_file *file)
|
||||
{
|
||||
struct zzip_archive *context = (struct zzip_archive *) file;
|
||||
|
||||
if (!refcount_dec(&context->ref))
|
||||
return;
|
||||
|
||||
if (context->list) {
|
||||
//free list
|
||||
for (GSList *tmp = context->list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||
g_free(tmp->data);
|
||||
g_slist_free(context->list);
|
||||
}
|
||||
//close archive
|
||||
zzip_dir_close (context->dir);
|
||||
|
||||
g_free(context);
|
||||
}
|
||||
|
||||
/* single archive handling */
|
||||
|
||||
struct zzip_input_stream {
|
||||
struct input_stream base;
|
||||
|
||||
struct zzip_archive *archive;
|
||||
|
||||
ZZIP_FILE *file;
|
||||
};
|
||||
|
||||
static struct input_stream *
|
||||
zzip_archive_open_stream(struct archive_file *file,
|
||||
const char *pathname, GError **error_r)
|
||||
{
|
||||
struct zzip_archive *context = (struct zzip_archive *) file;
|
||||
struct zzip_input_stream *zis;
|
||||
ZZIP_STAT z_stat;
|
||||
|
||||
zis = g_new(struct zzip_input_stream, 1);
|
||||
input_stream_init(&zis->base, &zzip_input_plugin, pathname);
|
||||
|
||||
zis->archive = context;
|
||||
zis->file = zzip_file_open(context->dir, pathname, 0);
|
||||
if (zis->file == NULL) {
|
||||
g_free(zis);
|
||||
g_set_error(error_r, zzip_quark(), 0,
|
||||
"not found in the ZIP file: %s", pathname);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
zis->base.ready = true;
|
||||
//we are seekable (but its not recommendent to do so)
|
||||
zis->base.seekable = true;
|
||||
|
||||
zzip_file_stat(zis->file, &z_stat);
|
||||
zis->base.size = z_stat.st_size;
|
||||
|
||||
refcount_inc(&context->ref);
|
||||
|
||||
return &zis->base;
|
||||
}
|
||||
|
||||
static void
|
||||
zzip_input_close(struct input_stream *is)
|
||||
{
|
||||
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
|
||||
|
||||
zzip_file_close(zis->file);
|
||||
zzip_archive_close(&zis->archive->base);
|
||||
input_stream_deinit(&zis->base);
|
||||
g_free(zis);
|
||||
}
|
||||
|
||||
static size_t
|
||||
zzip_input_read(struct input_stream *is, void *ptr, size_t size,
|
||||
GError **error_r)
|
||||
{
|
||||
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
|
||||
int ret;
|
||||
|
||||
ret = zzip_file_read(zis->file, ptr, size);
|
||||
if (ret < 0) {
|
||||
g_set_error(error_r, zzip_quark(), ret,
|
||||
"zzip_file_read() has failed");
|
||||
return 0;
|
||||
}
|
||||
|
||||
is->offset = zzip_tell(zis->file);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static bool
|
||||
zzip_input_eof(struct input_stream *is)
|
||||
{
|
||||
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
|
||||
|
||||
return (goffset)zzip_tell(zis->file) == is->size;
|
||||
}
|
||||
|
||||
static bool
|
||||
zzip_input_seek(struct input_stream *is,
|
||||
goffset offset, int whence, GError **error_r)
|
||||
{
|
||||
struct zzip_input_stream *zis = (struct zzip_input_stream *)is;
|
||||
zzip_off_t ofs = zzip_seek(zis->file, offset, whence);
|
||||
if (ofs != -1) {
|
||||
g_set_error(error_r, zzip_quark(), ofs,
|
||||
"zzip_seek() has failed");
|
||||
is->offset = ofs;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* exported structures */
|
||||
|
||||
static const char *const zzip_archive_extensions[] = {
|
||||
"zip",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct input_plugin zzip_input_plugin = {
|
||||
.close = zzip_input_close,
|
||||
.read = zzip_input_read,
|
||||
.eof = zzip_input_eof,
|
||||
.seek = zzip_input_seek,
|
||||
};
|
||||
|
||||
const struct archive_plugin zzip_archive_plugin = {
|
||||
.name = "zzip",
|
||||
.open = zzip_archive_open,
|
||||
.scan_reset = zzip_archive_scan_reset,
|
||||
.scan_next = zzip_archive_scan_next,
|
||||
.open_stream = zzip_archive_open_stream,
|
||||
.close = zzip_archive_close,
|
||||
.suffixes = zzip_archive_extensions
|
||||
};
|
||||
25
src/archive/zzip_archive_plugin.h
Normal file
25
src/archive/zzip_archive_plugin.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_ARCHIVE_ZZIP_H
|
||||
#define MPD_ARCHIVE_ZZIP_H
|
||||
|
||||
extern const struct archive_plugin zzip_archive_plugin;
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,6 +17,9 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h" /* must be first for large file support */
|
||||
#include "archive_api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string.h>
|
||||
@@ -26,8 +29,6 @@
|
||||
#include <errno.h>
|
||||
#include <glib.h>
|
||||
|
||||
#include "archive_api.h"
|
||||
|
||||
/**
|
||||
*
|
||||
* archive_lookup is used to determine if part of pathname refers to an regular
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -27,72 +27,11 @@
|
||||
*/
|
||||
|
||||
#include "archive_internal.h"
|
||||
#include "archive_plugin.h"
|
||||
#include "input_stream.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct archive_file;
|
||||
|
||||
struct archive_plugin {
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* optional, set this to NULL if the archive plugin doesn't
|
||||
* have/need one this must false if there is an error and
|
||||
* true otherwise
|
||||
*/
|
||||
bool (*init)(void);
|
||||
|
||||
/**
|
||||
* optional, set this to NULL if the archive plugin doesn't
|
||||
* have/need one
|
||||
*/
|
||||
void (*finish)(void);
|
||||
|
||||
/**
|
||||
* tryes to open archive file and associates handle with archive
|
||||
* returns pointer to handle used is all operations with this archive
|
||||
* or NULL when opening fails
|
||||
*/
|
||||
struct archive_file *(*open)(char * pathname);
|
||||
|
||||
/**
|
||||
* reset routine will move current read index in archive to default
|
||||
* position and then the filenames from archives can be read
|
||||
* via scan_next routine
|
||||
*/
|
||||
void (*scan_reset)(struct archive_file *);
|
||||
|
||||
/**
|
||||
* the read method will return corresponding files from archive
|
||||
* (as pathnames) and move read index to next file. When there is no
|
||||
* next file it return NULL.
|
||||
*/
|
||||
char *(*scan_next)(struct archive_file *);
|
||||
|
||||
/**
|
||||
* Opens an input_stream of a file within the archive.
|
||||
*
|
||||
* If this function succeeds, then the #input_stream "owns"
|
||||
* the archive file and will automatically close it.
|
||||
*
|
||||
* @param path the path within the archive
|
||||
*/
|
||||
bool (*open_stream)(struct archive_file *, struct input_stream *is,
|
||||
const char *path);
|
||||
|
||||
/**
|
||||
* closes archive file.
|
||||
*/
|
||||
void (*close)(struct archive_file *);
|
||||
|
||||
/**
|
||||
* suffixes handled by this plugin.
|
||||
* last element in these arrays must always be a NULL
|
||||
*/
|
||||
const char *const*suffixes;
|
||||
};
|
||||
|
||||
bool archive_lookup(char *pathname, char **archive, char **inpath, char **suffix);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -21,7 +21,14 @@
|
||||
#define MPD_ARCHIVE_INTERNAL_H
|
||||
|
||||
struct archive_file {
|
||||
int placeholder;
|
||||
const struct archive_plugin *plugin;
|
||||
};
|
||||
|
||||
static inline void
|
||||
archive_file_init(struct archive_file *archive_file,
|
||||
const struct archive_plugin *plugin)
|
||||
{
|
||||
archive_file->plugin = plugin;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,50 +17,44 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "archive_list.h"
|
||||
#include "archive_api.h"
|
||||
#include "utils.h"
|
||||
#include "config.h"
|
||||
#include "archive_list.h"
|
||||
#include "archive_plugin.h"
|
||||
#include "utils.h"
|
||||
#include "archive/bz2_archive_plugin.h"
|
||||
#include "archive/iso9660_archive_plugin.h"
|
||||
#include "archive/zzip_archive_plugin.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
|
||||
extern const struct archive_plugin bz2_plugin;
|
||||
extern const struct archive_plugin zip_plugin;
|
||||
extern const struct archive_plugin iso_plugin;
|
||||
|
||||
static const struct archive_plugin *const archive_plugins[] = {
|
||||
#ifdef HAVE_BZ2
|
||||
&bz2_plugin,
|
||||
&bz2_archive_plugin,
|
||||
#endif
|
||||
#ifdef HAVE_ZIP
|
||||
&zip_plugin,
|
||||
#ifdef HAVE_ZZIP
|
||||
&zzip_archive_plugin,
|
||||
#endif
|
||||
#ifdef HAVE_ISO
|
||||
&iso_plugin,
|
||||
#ifdef HAVE_ISO9660
|
||||
&iso9660_archive_plugin,
|
||||
#endif
|
||||
NULL
|
||||
};
|
||||
|
||||
enum {
|
||||
num_archive_plugins = G_N_ELEMENTS(archive_plugins)-1,
|
||||
};
|
||||
|
||||
/** which plugins have been initialized successfully? */
|
||||
static bool archive_plugins_enabled[num_archive_plugins+1];
|
||||
static bool archive_plugins_enabled[G_N_ELEMENTS(archive_plugins) - 1];
|
||||
|
||||
const struct archive_plugin *
|
||||
archive_plugin_from_suffix(const char *suffix)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
if (suffix == NULL)
|
||||
return NULL;
|
||||
|
||||
for (i=0; i < num_archive_plugins; ++i) {
|
||||
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
|
||||
const struct archive_plugin *plugin = archive_plugins[i];
|
||||
if (archive_plugins_enabled[i] &&
|
||||
stringFoundInStringArray(plugin->suffixes, suffix)) {
|
||||
plugin->suffixes != NULL &&
|
||||
string_array_contains(plugin->suffixes, suffix)) {
|
||||
++i;
|
||||
return plugin;
|
||||
}
|
||||
@@ -71,7 +65,7 @@ archive_plugin_from_suffix(const char *suffix)
|
||||
const struct archive_plugin *
|
||||
archive_plugin_from_name(const char *name)
|
||||
{
|
||||
for (unsigned i = 0; i < num_archive_plugins; ++i) {
|
||||
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
|
||||
const struct archive_plugin *plugin = archive_plugins[i];
|
||||
if (archive_plugins_enabled[i] &&
|
||||
strcmp(plugin->name, name) == 0)
|
||||
@@ -84,7 +78,7 @@ void archive_plugin_print_all_suffixes(FILE * fp)
|
||||
{
|
||||
const char *const*suffixes;
|
||||
|
||||
for (unsigned i = 0; i < num_archive_plugins; ++i) {
|
||||
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
|
||||
const struct archive_plugin *plugin = archive_plugins[i];
|
||||
if (!archive_plugins_enabled[i])
|
||||
continue;
|
||||
@@ -101,7 +95,7 @@ void archive_plugin_print_all_suffixes(FILE * fp)
|
||||
|
||||
void archive_plugin_init_all(void)
|
||||
{
|
||||
for (unsigned i = 0; i < num_archive_plugins; ++i) {
|
||||
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
|
||||
const struct archive_plugin *plugin = archive_plugins[i];
|
||||
if (plugin->init == NULL || archive_plugins[i]->init())
|
||||
archive_plugins_enabled[i] = true;
|
||||
@@ -110,7 +104,7 @@ void archive_plugin_init_all(void)
|
||||
|
||||
void archive_plugin_deinit_all(void)
|
||||
{
|
||||
for (unsigned i = 0; i < num_archive_plugins; ++i) {
|
||||
for (unsigned i = 0; archive_plugins[i] != NULL; ++i) {
|
||||
const struct archive_plugin *plugin = archive_plugins[i];
|
||||
if (archive_plugins_enabled[i] && plugin->finish != NULL)
|
||||
archive_plugins[i]->finish();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -20,8 +20,6 @@
|
||||
#ifndef MPD_ARCHIVE_LIST_H
|
||||
#define MPD_ARCHIVE_LIST_H
|
||||
|
||||
#include "archive_api.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
struct archive_plugin;
|
||||
|
||||
92
src/archive_plugin.c
Normal file
92
src/archive_plugin.c
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "archive_plugin.h"
|
||||
#include "archive_internal.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
struct archive_file *
|
||||
archive_file_open(const struct archive_plugin *plugin, const char *path,
|
||||
GError **error_r)
|
||||
{
|
||||
struct archive_file *file;
|
||||
|
||||
assert(plugin != NULL);
|
||||
assert(plugin->open != NULL);
|
||||
assert(path != NULL);
|
||||
assert(error_r == NULL || *error_r == NULL);
|
||||
|
||||
file = plugin->open(path, error_r);
|
||||
|
||||
if (file != NULL) {
|
||||
assert(file->plugin != NULL);
|
||||
assert(file->plugin->close != NULL);
|
||||
assert(file->plugin->scan_reset != NULL);
|
||||
assert(file->plugin->scan_next != NULL);
|
||||
assert(file->plugin->open_stream != NULL);
|
||||
assert(error_r == NULL || *error_r == NULL);
|
||||
} else {
|
||||
assert(error_r == NULL || *error_r != NULL);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
void
|
||||
archive_file_close(struct archive_file *file)
|
||||
{
|
||||
assert(file != NULL);
|
||||
assert(file->plugin != NULL);
|
||||
assert(file->plugin->close != NULL);
|
||||
|
||||
file->plugin->close(file);
|
||||
}
|
||||
|
||||
void
|
||||
archive_file_scan_reset(struct archive_file *file)
|
||||
{
|
||||
assert(file != NULL);
|
||||
assert(file->plugin != NULL);
|
||||
assert(file->plugin->scan_reset != NULL);
|
||||
assert(file->plugin->scan_next != NULL);
|
||||
|
||||
file->plugin->scan_reset(file);
|
||||
}
|
||||
|
||||
char *
|
||||
archive_file_scan_next(struct archive_file *file)
|
||||
{
|
||||
assert(file != NULL);
|
||||
assert(file->plugin != NULL);
|
||||
assert(file->plugin->scan_next != NULL);
|
||||
|
||||
return file->plugin->scan_next(file);
|
||||
}
|
||||
|
||||
struct input_stream *
|
||||
archive_file_open_stream(struct archive_file *file,
|
||||
const char *path, GError **error_r)
|
||||
{
|
||||
assert(file != NULL);
|
||||
assert(file->plugin != NULL);
|
||||
assert(file->plugin->open_stream != NULL);
|
||||
|
||||
return file->plugin->open_stream(file, path, error_r);
|
||||
}
|
||||
107
src/archive_plugin.h
Normal file
107
src/archive_plugin.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_ARCHIVE_PLUGIN_H
|
||||
#define MPD_ARCHIVE_PLUGIN_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
struct input_stream;
|
||||
struct archive_file;
|
||||
|
||||
struct archive_plugin {
|
||||
const char *name;
|
||||
|
||||
/**
|
||||
* optional, set this to NULL if the archive plugin doesn't
|
||||
* have/need one this must false if there is an error and
|
||||
* true otherwise
|
||||
*/
|
||||
bool (*init)(void);
|
||||
|
||||
/**
|
||||
* optional, set this to NULL if the archive plugin doesn't
|
||||
* have/need one
|
||||
*/
|
||||
void (*finish)(void);
|
||||
|
||||
/**
|
||||
* tryes to open archive file and associates handle with archive
|
||||
* returns pointer to handle used is all operations with this archive
|
||||
* or NULL when opening fails
|
||||
*/
|
||||
struct archive_file *(*open)(const char *path_fs, GError **error_r);
|
||||
|
||||
/**
|
||||
* reset routine will move current read index in archive to default
|
||||
* position and then the filenames from archives can be read
|
||||
* via scan_next routine
|
||||
*/
|
||||
void (*scan_reset)(struct archive_file *);
|
||||
|
||||
/**
|
||||
* the read method will return corresponding files from archive
|
||||
* (as pathnames) and move read index to next file. When there is no
|
||||
* next file it return NULL.
|
||||
*/
|
||||
char *(*scan_next)(struct archive_file *);
|
||||
|
||||
/**
|
||||
* Opens an input_stream of a file within the archive.
|
||||
*
|
||||
* @param path the path within the archive
|
||||
* @param error_r location to store the error occuring, or
|
||||
* NULL to ignore errors
|
||||
*/
|
||||
struct input_stream *(*open_stream)(struct archive_file *af,
|
||||
const char *path,
|
||||
GError **error_r);
|
||||
|
||||
/**
|
||||
* closes archive file.
|
||||
*/
|
||||
void (*close)(struct archive_file *);
|
||||
|
||||
/**
|
||||
* suffixes handled by this plugin.
|
||||
* last element in these arrays must always be a NULL
|
||||
*/
|
||||
const char *const*suffixes;
|
||||
};
|
||||
|
||||
struct archive_file *
|
||||
archive_file_open(const struct archive_plugin *plugin, const char *path,
|
||||
GError **error_r);
|
||||
|
||||
void
|
||||
archive_file_close(struct archive_file *file);
|
||||
|
||||
void
|
||||
archive_file_scan_reset(struct archive_file *file);
|
||||
|
||||
char *
|
||||
archive_file_scan_next(struct archive_file *file);
|
||||
|
||||
struct input_stream *
|
||||
archive_file_open_stream(struct archive_file *file,
|
||||
const char *path, GError **error_r);
|
||||
|
||||
#endif
|
||||
17
src/audio.c
17
src/audio.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "audio.h"
|
||||
#include "audio_format.h"
|
||||
#include "audio_parser.h"
|
||||
@@ -35,9 +36,8 @@ static struct audio_format configured_audio_format;
|
||||
void getOutputAudioFormat(const struct audio_format *inAudioFormat,
|
||||
struct audio_format *outAudioFormat)
|
||||
{
|
||||
*outAudioFormat = audio_format_defined(&configured_audio_format)
|
||||
? configured_audio_format
|
||||
: *inAudioFormat;
|
||||
*outAudioFormat = *inAudioFormat;
|
||||
audio_format_mask_apply(outAudioFormat, &configured_audio_format);
|
||||
}
|
||||
|
||||
void initAudioConfig(void)
|
||||
@@ -46,17 +46,12 @@ void initAudioConfig(void)
|
||||
GError *error = NULL;
|
||||
bool ret;
|
||||
|
||||
if (NULL == param || NULL == param->value)
|
||||
if (param == NULL)
|
||||
return;
|
||||
|
||||
ret = audio_format_parse(&configured_audio_format, param->value,
|
||||
&error);
|
||||
true, &error);
|
||||
if (!ret)
|
||||
g_error("error parsing \"%s\" at line %i: %s",
|
||||
CONF_AUDIO_OUTPUT_FORMAT, param->line, error->message);
|
||||
}
|
||||
|
||||
void finishAudioConfig(void)
|
||||
{
|
||||
audio_format_clear(&configured_audio_format);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -30,6 +30,4 @@ void getOutputAudioFormat(const struct audio_format *inFormat,
|
||||
/* make sure initPlayerData is called before this function!! */
|
||||
void initAudioConfig(void);
|
||||
|
||||
void finishAudioConfig(void);
|
||||
|
||||
#endif
|
||||
|
||||
74
src/audio_check.c
Normal file
74
src/audio_check.c
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "audio_check.h"
|
||||
#include "audio_format.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
bool
|
||||
audio_check_sample_rate(unsigned long sample_rate, GError **error_r)
|
||||
{
|
||||
if (!audio_valid_sample_rate(sample_rate)) {
|
||||
g_set_error(error_r, audio_format_quark(), 0,
|
||||
"Invalid sample rate: %lu", sample_rate);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
audio_check_sample_format(enum sample_format sample_format, GError **error_r)
|
||||
{
|
||||
if (!audio_valid_sample_format(sample_format)) {
|
||||
g_set_error(error_r, audio_format_quark(), 0,
|
||||
"Invalid sample format: %u", sample_format);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
audio_check_channel_count(unsigned channels, GError **error_r)
|
||||
{
|
||||
if (!audio_valid_channel_count(channels)) {
|
||||
g_set_error(error_r, audio_format_quark(), 0,
|
||||
"Invalid channel count: %u", channels);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
|
||||
enum sample_format sample_format, unsigned channels,
|
||||
GError **error_r)
|
||||
{
|
||||
if (audio_check_sample_rate(sample_rate, error_r) &&
|
||||
audio_check_sample_format(sample_format, error_r) &&
|
||||
audio_check_channel_count(channels, error_r)) {
|
||||
audio_format_init(af, sample_rate, sample_format, channels);
|
||||
assert(audio_format_valid(af));
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
54
src/audio_check.h
Normal file
54
src/audio_check.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_AUDIO_CHECK_H
|
||||
#define MPD_AUDIO_CHECK_H
|
||||
|
||||
#include "audio_format.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* The GLib quark used for errors reported by this library.
|
||||
*/
|
||||
static inline GQuark
|
||||
audio_format_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("audio_format");
|
||||
}
|
||||
|
||||
bool
|
||||
audio_check_sample_rate(unsigned long sample_rate, GError **error_r);
|
||||
|
||||
bool
|
||||
audio_check_sample_format(unsigned sample_format, GError **error_r);
|
||||
|
||||
bool
|
||||
audio_check_channel_count(unsigned sample_format, GError **error_r);
|
||||
|
||||
/**
|
||||
* Wrapper for audio_format_init(), which checks all attributes.
|
||||
*/
|
||||
bool
|
||||
audio_format_init_checked(struct audio_format *af, unsigned long sample_rate,
|
||||
enum sample_format sample_format, unsigned channels,
|
||||
GError **error_r);
|
||||
|
||||
#endif
|
||||
72
src/audio_format.c
Normal file
72
src/audio_format.c
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "audio_format.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if G_BYTE_ORDER == G_BIG_ENDIAN
|
||||
#define REVERSE_ENDIAN_SUFFIX "_le"
|
||||
#else
|
||||
#define REVERSE_ENDIAN_SUFFIX "_be"
|
||||
#endif
|
||||
|
||||
const char *
|
||||
sample_format_to_string(enum sample_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case SAMPLE_FORMAT_UNDEFINED:
|
||||
return "?";
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
return "8";
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
return "16";
|
||||
|
||||
case SAMPLE_FORMAT_S24:
|
||||
return "24_3";
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
return "24";
|
||||
|
||||
case SAMPLE_FORMAT_S32:
|
||||
return "32";
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
assert(false);
|
||||
return "?";
|
||||
}
|
||||
|
||||
const char *
|
||||
audio_format_to_string(const struct audio_format *af,
|
||||
struct audio_format_string *s)
|
||||
{
|
||||
assert(af != NULL);
|
||||
assert(s != NULL);
|
||||
|
||||
snprintf(s->buffer, sizeof(s->buffer), "%u:%s%s:%u",
|
||||
af->sample_rate, sample_format_to_string(af->format),
|
||||
af->reverse_endian ? REVERSE_ENDIAN_SUFFIX : "",
|
||||
af->channels);
|
||||
|
||||
return s->buffer;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -23,24 +23,122 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct audio_format {
|
||||
uint32_t sample_rate;
|
||||
uint8_t bits;
|
||||
uint8_t channels;
|
||||
enum sample_format {
|
||||
SAMPLE_FORMAT_UNDEFINED = 0,
|
||||
|
||||
SAMPLE_FORMAT_S8,
|
||||
SAMPLE_FORMAT_S16,
|
||||
|
||||
/**
|
||||
* Signed 24 bit integer samples, without padding.
|
||||
*/
|
||||
SAMPLE_FORMAT_S24,
|
||||
|
||||
/**
|
||||
* Signed 24 bit integer samples, packed in 32 bit integers
|
||||
* (the most significant byte is filled with the sign bit).
|
||||
*/
|
||||
SAMPLE_FORMAT_S24_P32,
|
||||
|
||||
SAMPLE_FORMAT_S32,
|
||||
};
|
||||
|
||||
/**
|
||||
* This structure describes the format of a raw PCM stream.
|
||||
*/
|
||||
struct audio_format {
|
||||
/**
|
||||
* The sample rate in Hz. A better name for this attribute is
|
||||
* "frame rate", because technically, you have two samples per
|
||||
* frame in stereo sound.
|
||||
*/
|
||||
uint32_t sample_rate;
|
||||
|
||||
/**
|
||||
* The format samples are stored in. See the #sample_format
|
||||
* enum for valid values.
|
||||
*/
|
||||
uint8_t format;
|
||||
|
||||
/**
|
||||
* The number of channels. Only mono (1) and stereo (2) are
|
||||
* fully supported currently.
|
||||
*/
|
||||
uint8_t channels;
|
||||
|
||||
/**
|
||||
* If zero, then samples are stored in host byte order. If
|
||||
* nonzero, then samples are stored in the reverse host byte
|
||||
* order.
|
||||
*/
|
||||
uint8_t reverse_endian;
|
||||
};
|
||||
|
||||
/**
|
||||
* Buffer for audio_format_string().
|
||||
*/
|
||||
struct audio_format_string {
|
||||
char buffer[24];
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears the #audio_format object, i.e. sets all attributes to an
|
||||
* undefined (invalid) value.
|
||||
*/
|
||||
static inline void audio_format_clear(struct audio_format *af)
|
||||
{
|
||||
af->sample_rate = 0;
|
||||
af->bits = 0;
|
||||
af->format = SAMPLE_FORMAT_UNDEFINED;
|
||||
af->channels = 0;
|
||||
af->reverse_endian = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an #audio_format object, i.e. sets all
|
||||
* attributes to valid values.
|
||||
*/
|
||||
static inline void audio_format_init(struct audio_format *af,
|
||||
uint32_t sample_rate,
|
||||
enum sample_format format, uint8_t channels)
|
||||
{
|
||||
af->sample_rate = sample_rate;
|
||||
af->format = (uint8_t)format;
|
||||
af->channels = channels;
|
||||
af->reverse_endian = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the specified #audio_format object has a defined
|
||||
* value.
|
||||
*/
|
||||
static inline bool audio_format_defined(const struct audio_format *af)
|
||||
{
|
||||
return af->sample_rate != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the specified #audio_format object is full, i.e. all
|
||||
* attributes are defined. This is more complete than
|
||||
* audio_format_defined(), but slower.
|
||||
*/
|
||||
static inline bool
|
||||
audio_format_fully_defined(const struct audio_format *af)
|
||||
{
|
||||
return af->sample_rate != 0 && af->format != SAMPLE_FORMAT_UNDEFINED &&
|
||||
af->channels != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the specified #audio_format object has at least one
|
||||
* defined value.
|
||||
*/
|
||||
static inline bool
|
||||
audio_format_mask_defined(const struct audio_format *af)
|
||||
{
|
||||
return af->sample_rate != 0 || af->format != SAMPLE_FORMAT_UNDEFINED ||
|
||||
af->channels != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the sample rate is valid.
|
||||
*
|
||||
@@ -58,9 +156,21 @@ audio_valid_sample_rate(unsigned sample_rate)
|
||||
* @param bits the number of significant bits per sample
|
||||
*/
|
||||
static inline bool
|
||||
audio_valid_sample_format(unsigned bits)
|
||||
audio_valid_sample_format(enum sample_format format)
|
||||
{
|
||||
return bits == 16 || bits == 24 || bits == 32 || bits == 8;
|
||||
switch (format) {
|
||||
case SAMPLE_FORMAT_S8:
|
||||
case SAMPLE_FORMAT_S16:
|
||||
case SAMPLE_FORMAT_S24:
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
case SAMPLE_FORMAT_S32:
|
||||
return true;
|
||||
|
||||
case SAMPLE_FORMAT_UNDEFINED:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,16 +189,44 @@ audio_valid_channel_count(unsigned channels)
|
||||
static inline bool audio_format_valid(const struct audio_format *af)
|
||||
{
|
||||
return audio_valid_sample_rate(af->sample_rate) &&
|
||||
audio_valid_sample_format(af->bits) &&
|
||||
audio_valid_sample_format((enum sample_format)af->format) &&
|
||||
audio_valid_channel_count(af->channels);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if the format mask is not valid for playback with
|
||||
* MPD. This function performs some basic validity checks.
|
||||
*/
|
||||
static inline bool audio_format_mask_valid(const struct audio_format *af)
|
||||
{
|
||||
return (af->sample_rate == 0 ||
|
||||
audio_valid_sample_rate(af->sample_rate)) &&
|
||||
(af->format == SAMPLE_FORMAT_UNDEFINED ||
|
||||
audio_valid_sample_format((enum sample_format)af->format)) &&
|
||||
(af->channels == 0 || audio_valid_channel_count(af->channels));
|
||||
}
|
||||
|
||||
static inline bool audio_format_equals(const struct audio_format *a,
|
||||
const struct audio_format *b)
|
||||
{
|
||||
return a->sample_rate == b->sample_rate &&
|
||||
a->bits == b->bits &&
|
||||
a->channels == b->channels;
|
||||
a->format == b->format &&
|
||||
a->channels == b->channels &&
|
||||
a->reverse_endian == b->reverse_endian;
|
||||
}
|
||||
|
||||
static inline void
|
||||
audio_format_mask_apply(struct audio_format *af,
|
||||
const struct audio_format *mask)
|
||||
{
|
||||
if (mask->sample_rate != 0)
|
||||
af->sample_rate = mask->sample_rate;
|
||||
|
||||
if (mask->format != SAMPLE_FORMAT_UNDEFINED)
|
||||
af->format = mask->format;
|
||||
|
||||
if (mask->channels != 0)
|
||||
af->channels = mask->channels;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,28 +234,65 @@ static inline bool audio_format_equals(const struct audio_format *a,
|
||||
*/
|
||||
static inline unsigned audio_format_sample_size(const struct audio_format *af)
|
||||
{
|
||||
if (af->bits <= 8)
|
||||
switch (af->format) {
|
||||
case SAMPLE_FORMAT_S8:
|
||||
return 1;
|
||||
else if (af->bits <= 16)
|
||||
|
||||
case SAMPLE_FORMAT_S16:
|
||||
return 2;
|
||||
else
|
||||
|
||||
case SAMPLE_FORMAT_S24:
|
||||
return 3;
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
case SAMPLE_FORMAT_S32:
|
||||
return 4;
|
||||
|
||||
case SAMPLE_FORMAT_UNDEFINED:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size of each full frame in bytes.
|
||||
*/
|
||||
static inline unsigned
|
||||
audio_format_frame_size(const struct audio_format *af)
|
||||
{
|
||||
return audio_format_sample_size(af) * af->channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the floating point factor which converts a time span to a
|
||||
* storage size in bytes.
|
||||
*/
|
||||
static inline double audio_format_time_to_size(const struct audio_format *af)
|
||||
{
|
||||
return af->sample_rate * audio_format_frame_size(af);
|
||||
}
|
||||
|
||||
static inline double audioFormatSizeToTime(const struct audio_format *af)
|
||||
{
|
||||
return 1.0 / audio_format_time_to_size(af);
|
||||
}
|
||||
/**
|
||||
* Renders a #sample_format enum into a string, e.g. for printing it
|
||||
* in a log file.
|
||||
*
|
||||
* @param format a #sample_format enum value
|
||||
* @return the string
|
||||
*/
|
||||
const char *
|
||||
sample_format_to_string(enum sample_format format);
|
||||
|
||||
/**
|
||||
* Renders the #audio_format object into a string, e.g. for printing
|
||||
* it in a log file.
|
||||
*
|
||||
* @param af the #audio_format object
|
||||
* @param s a buffer to print into
|
||||
* @return the string, or NULL if the #audio_format object is invalid
|
||||
*/
|
||||
const char *
|
||||
audio_format_to_string(const struct audio_format *af,
|
||||
struct audio_format_string *s);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -22,9 +22,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "audio_parser.h"
|
||||
#include "audio_format.h"
|
||||
#include "audio_check.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
@@ -36,64 +40,158 @@ audio_parser_quark(void)
|
||||
return g_quark_from_static_string("audio_parser");
|
||||
}
|
||||
|
||||
bool
|
||||
audio_format_parse(struct audio_format *dest, const char *src, GError **error)
|
||||
static bool
|
||||
parse_sample_rate(const char *src, bool mask, uint32_t *sample_rate_r,
|
||||
const char **endptr_r, GError **error_r)
|
||||
{
|
||||
char *endptr;
|
||||
unsigned long value;
|
||||
char *endptr;
|
||||
|
||||
if (mask && *src == '*') {
|
||||
*sample_rate_r = 0;
|
||||
*endptr_r = src + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = strtoul(src, &endptr, 10);
|
||||
if (endptr == src) {
|
||||
g_set_error(error_r, audio_parser_quark(), 0,
|
||||
"Failed to parse the sample rate");
|
||||
return false;
|
||||
} else if (!audio_check_sample_rate(value, error_r))
|
||||
return false;
|
||||
|
||||
*sample_rate_r = value;
|
||||
*endptr_r = endptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_sample_format(const char *src, bool mask,
|
||||
enum sample_format *sample_format_r,
|
||||
const char **endptr_r, GError **error_r)
|
||||
{
|
||||
unsigned long value;
|
||||
char *endptr;
|
||||
enum sample_format sample_format;
|
||||
|
||||
if (mask && *src == '*') {
|
||||
*sample_format_r = SAMPLE_FORMAT_UNDEFINED;
|
||||
*endptr_r = src + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = strtoul(src, &endptr, 10);
|
||||
if (endptr == src) {
|
||||
g_set_error(error_r, audio_parser_quark(), 0,
|
||||
"Failed to parse the sample format");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case 8:
|
||||
sample_format = SAMPLE_FORMAT_S8;
|
||||
break;
|
||||
|
||||
case 16:
|
||||
sample_format = SAMPLE_FORMAT_S16;
|
||||
break;
|
||||
|
||||
case 24:
|
||||
if (memcmp(endptr, "_3", 2) == 0) {
|
||||
sample_format = SAMPLE_FORMAT_S24;
|
||||
endptr += 2;
|
||||
} else
|
||||
sample_format = SAMPLE_FORMAT_S24_P32;
|
||||
break;
|
||||
|
||||
case 32:
|
||||
sample_format = SAMPLE_FORMAT_S32;
|
||||
break;
|
||||
|
||||
default:
|
||||
g_set_error(error_r, audio_parser_quark(), 0,
|
||||
"Invalid sample format: %lu", value);
|
||||
return false;
|
||||
}
|
||||
|
||||
assert(audio_valid_sample_format(sample_format));
|
||||
|
||||
*sample_format_r = sample_format;
|
||||
*endptr_r = endptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
parse_channel_count(const char *src, bool mask, uint8_t *channels_r,
|
||||
const char **endptr_r, GError **error_r)
|
||||
{
|
||||
unsigned long value;
|
||||
char *endptr;
|
||||
|
||||
if (mask && *src == '*') {
|
||||
*channels_r = 0;
|
||||
*endptr_r = src + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = strtoul(src, &endptr, 10);
|
||||
if (endptr == src) {
|
||||
g_set_error(error_r, audio_parser_quark(), 0,
|
||||
"Failed to parse the channel count");
|
||||
return false;
|
||||
} else if (!audio_check_channel_count(value, error_r))
|
||||
return false;
|
||||
|
||||
*channels_r = value;
|
||||
*endptr_r = endptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
audio_format_parse(struct audio_format *dest, const char *src,
|
||||
bool mask, GError **error_r)
|
||||
{
|
||||
uint32_t rate;
|
||||
enum sample_format sample_format;
|
||||
uint8_t channels;
|
||||
|
||||
audio_format_clear(dest);
|
||||
|
||||
/* parse sample rate */
|
||||
|
||||
value = strtoul(src, &endptr, 10);
|
||||
if (endptr == src) {
|
||||
g_set_error(error, audio_parser_quark(), 0,
|
||||
"Sample rate missing");
|
||||
if (!parse_sample_rate(src, mask, &rate, &src, error_r))
|
||||
return false;
|
||||
} else if (*endptr != ':') {
|
||||
g_set_error(error, audio_parser_quark(), 0,
|
||||
|
||||
if (*src++ != ':') {
|
||||
g_set_error(error_r, audio_parser_quark(), 0,
|
||||
"Sample format missing");
|
||||
return false;
|
||||
} else if (!audio_valid_sample_rate(value)) {
|
||||
g_set_error(error, audio_parser_quark(), 0,
|
||||
"Invalid sample rate: %lu", value);
|
||||
return false;
|
||||
}
|
||||
|
||||
dest->sample_rate = value;
|
||||
|
||||
/* parse sample format */
|
||||
|
||||
src = endptr + 1;
|
||||
value = strtoul(src, &endptr, 10);
|
||||
if (endptr == src) {
|
||||
g_set_error(error, audio_parser_quark(), 0,
|
||||
"Sample format missing");
|
||||
if (!parse_sample_format(src, mask, &sample_format, &src, error_r))
|
||||
return false;
|
||||
} else if (*endptr != ':') {
|
||||
g_set_error(error, audio_parser_quark(), 0,
|
||||
|
||||
if (*src++ != ':') {
|
||||
g_set_error(error_r, audio_parser_quark(), 0,
|
||||
"Channel count missing");
|
||||
return false;
|
||||
} else if (!audio_valid_sample_format(value)) {
|
||||
g_set_error(error, audio_parser_quark(), 0,
|
||||
"Invalid sample format: %lu", value);
|
||||
return false;
|
||||
}
|
||||
|
||||
dest->bits = value;
|
||||
|
||||
/* parse channel count */
|
||||
|
||||
src = endptr + 1;
|
||||
value = strtoul(src, &endptr, 10);
|
||||
if (*endptr != 0 || !audio_valid_channel_count(value)) {
|
||||
g_set_error(error, audio_parser_quark(), 0,
|
||||
"Invalid channel count: %s", src);
|
||||
if (!parse_channel_count(src, mask, &channels, &src, error_r))
|
||||
return false;
|
||||
|
||||
if (*src != 0) {
|
||||
g_set_error(error_r, audio_parser_quark(), 0,
|
||||
"Extra data after channel count: %s", src);
|
||||
return false;
|
||||
}
|
||||
|
||||
dest->channels = value;
|
||||
audio_format_init(dest, rate, sample_format, channels);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -37,11 +37,13 @@ struct audio_format;
|
||||
*
|
||||
* @param dest the destination #audio_format struct
|
||||
* @param src the input string
|
||||
* @param error location to store the error occuring, or NULL to
|
||||
* @param mask if true, then "*" is allowed for any number of items
|
||||
* @param error_r location to store the error occuring, or NULL to
|
||||
* ignore errors
|
||||
* @return true on success
|
||||
*/
|
||||
bool
|
||||
audio_format_parse(struct audio_format *dest, const char *src, GError **error);
|
||||
audio_format_parse(struct audio_format *dest, const char *src,
|
||||
bool mask, GError **error_r);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "buffer.h"
|
||||
#include "chunk.h"
|
||||
#include "poison.h"
|
||||
@@ -117,6 +118,9 @@ music_buffer_return(struct music_buffer *buffer, struct music_chunk *chunk)
|
||||
assert(buffer != NULL);
|
||||
assert(chunk != NULL);
|
||||
|
||||
if (chunk->other != NULL)
|
||||
music_buffer_return(buffer, chunk->other);
|
||||
|
||||
g_mutex_lock(buffer->mutex);
|
||||
|
||||
music_chunk_free(chunk);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "buffer2array.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
int buffer2array(char *buffer, char *array[], const int max)
|
||||
{
|
||||
int i = 0;
|
||||
char *c = buffer;
|
||||
|
||||
while (*c != '\0' && i < max) {
|
||||
if (*c == '\"') {
|
||||
array[i++] = ++c;
|
||||
while (*c != '\0') {
|
||||
if (*c == '\"') {
|
||||
*(c++) = '\0';
|
||||
break;
|
||||
}
|
||||
else if (*(c++) == '\\' && *c != '\0') {
|
||||
memmove(c - 1, c, strlen(c) + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c = g_strchug(c);
|
||||
if (*c == '\0')
|
||||
return i;
|
||||
|
||||
array[i++] = c++;
|
||||
|
||||
while (!g_ascii_isspace(*c) && *c != '\0')
|
||||
++c;
|
||||
}
|
||||
if (*c == '\0')
|
||||
return i;
|
||||
*(c++) = '\0';
|
||||
|
||||
c = g_strchug(c);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
#ifdef UNIT_TEST
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
char *a[4] = { NULL };
|
||||
char *b;
|
||||
int max;
|
||||
|
||||
b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\"");
|
||||
assert(b);
|
||||
max = buffer2array(b, a, 4);
|
||||
assert( !strcmp("lsinfo", a[0]) );
|
||||
assert( !strcmp("/some/dir/name \"test\"", a[1]) );
|
||||
assert( !a[2] );
|
||||
|
||||
b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\"");
|
||||
assert(b);
|
||||
max = buffer2array(b, a, 4);
|
||||
assert( !strcmp("lsinfo", a[0]) );
|
||||
assert( !strcmp("/some/dir/name \"test\" something else", a[1]) );
|
||||
assert( !a[2] );
|
||||
|
||||
b = strdup("lsinfo \"/some/dir\\\\name\"");
|
||||
assert(b);
|
||||
max = buffer2array(b, a, 4);
|
||||
assert( !strcmp("lsinfo", a[0]) );
|
||||
assert( !strcmp("/some/dir\\name", a[1]) );
|
||||
assert( !a[2] );
|
||||
|
||||
b = strdup("lsinfo \"/some/dir name\"");
|
||||
assert(b);
|
||||
max = buffer2array(b, a, 4);
|
||||
assert( !strcmp("lsinfo", a[0]) );
|
||||
assert( !strcmp("/some/dir name", a[1]) );
|
||||
assert( !a[2] );
|
||||
|
||||
b = strdup("lsinfo \"\\\"/some/dir\\\"\"");
|
||||
assert(b);
|
||||
max = buffer2array(b, a, 4);
|
||||
assert( !strcmp("lsinfo", a[0]) );
|
||||
assert( !strcmp("\"/some/dir\"", a[1]) );
|
||||
assert( !a[2] );
|
||||
|
||||
b = strdup("lsinfo \"\\\"/some/dir\\\" x\"");
|
||||
assert(b);
|
||||
max = buffer2array(b, a, 4);
|
||||
assert( !strcmp("lsinfo", a[0]) );
|
||||
assert( !strcmp("\"/some/dir\" x", a[1]) );
|
||||
assert( !a[2] );
|
||||
|
||||
b = strdup("lsinfo \"single quote\\'d from php magicquotes\"");
|
||||
assert(b);
|
||||
max = buffer2array(b, a, 4);
|
||||
assert( !strcmp("lsinfo", a[0]) );
|
||||
assert( !strcmp("single quote\'d from php magicquotes", a[1]) );
|
||||
assert( !a[2] );
|
||||
|
||||
b = strdup("lsinfo \"double quote\\\"d from php magicquotes\"");
|
||||
assert(b);
|
||||
max = buffer2array(b, a, 4);
|
||||
assert( !strcmp("lsinfo", a[0]) );
|
||||
assert( !strcmp("double quote\"d from php magicquotes", a[1]) );
|
||||
assert( !a[2] );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
47
src/check.h
Normal file
47
src/check.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_CHECK_H
|
||||
#define MPD_CHECK_H
|
||||
|
||||
/*
|
||||
* All sources must include config.h on the first line to ensure that
|
||||
* Large File Support is configured properly. This header checks
|
||||
* whether this has happened.
|
||||
*
|
||||
* Usage: include this header before you use any of the above types.
|
||||
* It will stop the compiler if something went wrong.
|
||||
*
|
||||
* This is Linux/glibc specific, and only enabled in the debug build,
|
||||
* so bugs in this headers don't affect users with production builds.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PACKAGE_VERSION
|
||||
#error config.h missing
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) && !defined(NDEBUG) && defined(ENABLE_LARGEFILE) && \
|
||||
defined(_FEATURES_H) && defined(__i386__) && \
|
||||
!defined(__USE_FILE_OFFSET64)
|
||||
/* on i386, check if LFS is enabled */
|
||||
#error config.h was included too late
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "chunk.h"
|
||||
#include "audio_format.h"
|
||||
#include "tag.h"
|
||||
@@ -26,8 +27,10 @@
|
||||
void
|
||||
music_chunk_init(struct music_chunk *chunk)
|
||||
{
|
||||
chunk->other = NULL;
|
||||
chunk->length = 0;
|
||||
chunk->tag = NULL;
|
||||
chunk->replay_gain_serial = 0;
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
29
src/chunk.h
29
src/chunk.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -20,6 +20,8 @@
|
||||
#ifndef MPD_CHUNK_H
|
||||
#define MPD_CHUNK_H
|
||||
|
||||
#include "replay_gain_info.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include "audio_format.h"
|
||||
#endif
|
||||
@@ -42,6 +44,18 @@ struct music_chunk {
|
||||
/** the next chunk in a linked list */
|
||||
struct music_chunk *next;
|
||||
|
||||
/**
|
||||
* An optional chunk which should be mixed into this chunk.
|
||||
* This is used for cross-fading.
|
||||
*/
|
||||
struct music_chunk *other;
|
||||
|
||||
/**
|
||||
* The current mix ratio for cross-fading: 1.0 means play 100%
|
||||
* of this chunk, 0.0 means play 100% of the "other" chunk.
|
||||
*/
|
||||
float mix_ratio;
|
||||
|
||||
/** number of bytes stored in this chunk */
|
||||
uint16_t length;
|
||||
|
||||
@@ -59,6 +73,19 @@ struct music_chunk {
|
||||
*/
|
||||
struct tag *tag;
|
||||
|
||||
/**
|
||||
* Replay gain information associated with this chunk.
|
||||
* Only valid if the serial is not 0.
|
||||
*/
|
||||
struct replay_gain_info replay_gain_info;
|
||||
|
||||
/**
|
||||
* A serial number for checking if replay gain info has
|
||||
* changed since the last chunk. The magic value 0 indicates
|
||||
* that there is no replay gain info available.
|
||||
*/
|
||||
unsigned replay_gain_serial;
|
||||
|
||||
/** the data (probably PCM) */
|
||||
char data[CHUNK_SIZE];
|
||||
|
||||
|
||||
885
src/client.c
885
src/client.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,110 +17,8 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "client.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "command.h"
|
||||
#include "conf.h"
|
||||
#include "listen.h"
|
||||
#include "socket_util.h"
|
||||
#include "permission.h"
|
||||
#include "event_pipe.h"
|
||||
#include "idle.h"
|
||||
#include "main.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#include <winsock.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "client"
|
||||
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
|
||||
|
||||
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
|
||||
|
||||
#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
|
||||
#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
|
||||
#define CLIENT_LIST_MODE_END "command_list_end"
|
||||
#define CLIENT_TIMEOUT_DEFAULT (60)
|
||||
#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
|
||||
#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
|
||||
#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
|
||||
|
||||
/* set this to zero to indicate we have no possible clients */
|
||||
static unsigned int client_max_connections; /*CLIENT_MAX_CONNECTIONS_DEFAULT; */
|
||||
static int client_timeout;
|
||||
static size_t client_max_command_list_size;
|
||||
static size_t client_max_output_buffer_size;
|
||||
|
||||
struct deferred_buffer {
|
||||
size_t size;
|
||||
char data[sizeof(long)];
|
||||
};
|
||||
|
||||
struct client {
|
||||
GIOChannel *channel;
|
||||
guint source_id;
|
||||
|
||||
/** the buffer for reading lines from the #channel */
|
||||
struct fifo_buffer *input;
|
||||
|
||||
unsigned permission;
|
||||
|
||||
/** the uid of the client process, or -1 if unknown */
|
||||
int uid;
|
||||
|
||||
/**
|
||||
* How long since the last activity from this client?
|
||||
*/
|
||||
GTimer *last_activity;
|
||||
|
||||
GSList *cmd_list; /* for when in list mode */
|
||||
int cmd_list_OK; /* print OK after each command execution */
|
||||
size_t cmd_list_size; /* mem cmd_list consumes */
|
||||
GQueue *deferred_send; /* for output if client is slow */
|
||||
size_t deferred_bytes; /* mem deferred_send consumes */
|
||||
unsigned int num; /* client number */
|
||||
|
||||
char send_buf[4096];
|
||||
size_t send_buf_used; /* bytes used this instance */
|
||||
|
||||
/** is this client waiting for an "idle" response? */
|
||||
bool idle_waiting;
|
||||
|
||||
/** idle flags pending on this client, to be sent as soon as
|
||||
the client enters "idle" */
|
||||
unsigned idle_flags;
|
||||
|
||||
/** idle flags that the client wants to receive */
|
||||
unsigned idle_subscriptions;
|
||||
};
|
||||
|
||||
static GList *clients;
|
||||
static unsigned num_clients;
|
||||
static guint expire_source_id;
|
||||
|
||||
static void client_write_deferred(struct client *client);
|
||||
|
||||
static void client_write_output(struct client *client);
|
||||
|
||||
static void client_manager_expire(void);
|
||||
|
||||
static gboolean
|
||||
client_in_event(GIOChannel *source, GIOCondition condition, gpointer data);
|
||||
#include "client_internal.h"
|
||||
|
||||
bool client_is_expired(const struct client *client)
|
||||
{
|
||||
@@ -141,782 +39,3 @@ void client_set_permission(struct client *client, unsigned permission)
|
||||
{
|
||||
client->permission = permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* An idle event which calls client_manager_expire().
|
||||
*/
|
||||
static gboolean
|
||||
client_manager_expire_event(G_GNUC_UNUSED gpointer data)
|
||||
{
|
||||
expire_source_id = 0;
|
||||
client_manager_expire();
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void client_set_expired(struct client *client)
|
||||
{
|
||||
if (expire_source_id == 0 && !client_is_expired(client))
|
||||
/* delayed deletion */
|
||||
expire_source_id = g_idle_add(client_manager_expire_event,
|
||||
NULL);
|
||||
|
||||
if (client->source_id != 0) {
|
||||
g_source_remove(client->source_id);
|
||||
client->source_id = 0;
|
||||
}
|
||||
|
||||
if (client->channel != NULL) {
|
||||
g_io_channel_unref(client->channel);
|
||||
client->channel = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void client_init(struct client *client, int fd)
|
||||
{
|
||||
static unsigned int next_client_num;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
client->cmd_list_size = 0;
|
||||
client->cmd_list_OK = -1;
|
||||
|
||||
#ifndef G_OS_WIN32
|
||||
client->channel = g_io_channel_unix_new(fd);
|
||||
#else
|
||||
client->channel = g_io_channel_win32_new_socket(fd);
|
||||
#endif
|
||||
/* GLib is responsible for closing the file descriptor */
|
||||
g_io_channel_set_close_on_unref(client->channel, true);
|
||||
/* NULL encoding means the stream is binary safe; the MPD
|
||||
protocol is UTF-8 only, but we are doing this call anyway
|
||||
to prevent GLib from messing around with the stream */
|
||||
g_io_channel_set_encoding(client->channel, NULL, NULL);
|
||||
/* we prefer to do buffering */
|
||||
g_io_channel_set_buffered(client->channel, false);
|
||||
|
||||
client->source_id = g_io_add_watch(client->channel,
|
||||
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
||||
client_in_event, client);
|
||||
|
||||
client->input = fifo_buffer_new(4096);
|
||||
|
||||
client->cmd_list = NULL;
|
||||
client->deferred_send = g_queue_new();
|
||||
client->deferred_bytes = 0;
|
||||
client->num = next_client_num++;
|
||||
client->send_buf_used = 0;
|
||||
|
||||
client->permission = getDefaultPermissions();
|
||||
|
||||
(void)write(fd, GREETING, sizeof(GREETING) - 1);
|
||||
}
|
||||
|
||||
static void free_cmd_list(GSList *list)
|
||||
{
|
||||
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||
g_free(tmp->data);
|
||||
|
||||
g_slist_free(list);
|
||||
}
|
||||
|
||||
static void new_cmd_list_ptr(struct client *client, char *s)
|
||||
{
|
||||
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
|
||||
}
|
||||
|
||||
static void
|
||||
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
struct deferred_buffer *buffer = data;
|
||||
g_free(buffer);
|
||||
}
|
||||
|
||||
static void client_close(struct client *client)
|
||||
{
|
||||
assert(num_clients > 0);
|
||||
assert(clients != NULL);
|
||||
|
||||
clients = g_list_remove(clients, client);
|
||||
--num_clients;
|
||||
|
||||
client_set_expired(client);
|
||||
|
||||
g_timer_destroy(client->last_activity);
|
||||
|
||||
if (client->cmd_list) {
|
||||
free_cmd_list(client->cmd_list);
|
||||
client->cmd_list = NULL;
|
||||
}
|
||||
|
||||
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
|
||||
g_queue_free(client->deferred_send);
|
||||
|
||||
fifo_buffer_free(client->input);
|
||||
|
||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||
"[%u] closed", client->num);
|
||||
g_free(client);
|
||||
}
|
||||
|
||||
void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
|
||||
{
|
||||
struct client *client;
|
||||
char *remote;
|
||||
|
||||
if (num_clients >= client_max_connections) {
|
||||
g_warning("Max Connections Reached!");
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
client = g_new0(struct client, 1);
|
||||
clients = g_list_prepend(clients, client);
|
||||
++num_clients;
|
||||
|
||||
client_init(client, fd);
|
||||
client->uid = uid;
|
||||
|
||||
client->last_activity = g_timer_new();
|
||||
|
||||
remote = sockaddr_to_string(sa, sa_length, NULL);
|
||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||
"[%u] opened from %s", client->num, remote);
|
||||
g_free(remote);
|
||||
}
|
||||
|
||||
static int client_process_line(struct client *client, char *line)
|
||||
{
|
||||
int ret = 1;
|
||||
|
||||
if (strcmp(line, "noidle") == 0) {
|
||||
if (client->idle_waiting) {
|
||||
/* send empty idle response and leave idle mode */
|
||||
client->idle_waiting = false;
|
||||
command_success(client);
|
||||
client_write_output(client);
|
||||
}
|
||||
|
||||
/* do nothing if the client wasn't idling: the client
|
||||
has already received the full idle response from
|
||||
client_idle_notify(), which he can now evaluate */
|
||||
|
||||
return 0;
|
||||
} else if (client->idle_waiting) {
|
||||
/* during idle mode, clients must not send anything
|
||||
except "noidle" */
|
||||
g_warning("[%u] command \"%s\" during idle",
|
||||
client->num, line);
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
if (client->cmd_list_OK >= 0) {
|
||||
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
|
||||
g_debug("[%u] process command list",
|
||||
client->num);
|
||||
|
||||
/* for scalability reasons, we have prepended
|
||||
each new command; now we have to reverse it
|
||||
to restore the correct order */
|
||||
client->cmd_list = g_slist_reverse(client->cmd_list);
|
||||
|
||||
ret = command_process_list(client,
|
||||
client->cmd_list_OK,
|
||||
client->cmd_list);
|
||||
g_debug("[%u] process command "
|
||||
"list returned %i", client->num, ret);
|
||||
|
||||
if (ret == COMMAND_RETURN_CLOSE ||
|
||||
client_is_expired(client))
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
|
||||
if (ret == 0)
|
||||
command_success(client);
|
||||
|
||||
client_write_output(client);
|
||||
free_cmd_list(client->cmd_list);
|
||||
client->cmd_list = NULL;
|
||||
client->cmd_list_OK = -1;
|
||||
} else {
|
||||
size_t len = strlen(line) + 1;
|
||||
client->cmd_list_size += len;
|
||||
if (client->cmd_list_size >
|
||||
client_max_command_list_size) {
|
||||
g_warning("[%u] command list size (%lu) "
|
||||
"is larger than the max (%lu)",
|
||||
client->num,
|
||||
(unsigned long)client->cmd_list_size,
|
||||
(unsigned long)client_max_command_list_size);
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
} else
|
||||
new_cmd_list_ptr(client, line);
|
||||
}
|
||||
} else {
|
||||
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
|
||||
client->cmd_list_OK = 0;
|
||||
ret = 1;
|
||||
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
|
||||
client->cmd_list_OK = 1;
|
||||
ret = 1;
|
||||
} else {
|
||||
g_debug("[%u] process command \"%s\"",
|
||||
client->num, line);
|
||||
ret = command_process(client, line);
|
||||
g_debug("[%u] command returned %i",
|
||||
client->num, ret);
|
||||
|
||||
if (ret == COMMAND_RETURN_CLOSE ||
|
||||
client_is_expired(client))
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
|
||||
if (ret == 0)
|
||||
command_success(client);
|
||||
|
||||
client_write_output(client);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *
|
||||
client_read_line(struct client *client)
|
||||
{
|
||||
const char *p, *newline;
|
||||
size_t length;
|
||||
char *line;
|
||||
|
||||
p = fifo_buffer_read(client->input, &length);
|
||||
if (p == NULL)
|
||||
return NULL;
|
||||
|
||||
newline = memchr(p, '\n', length);
|
||||
if (newline == NULL)
|
||||
return NULL;
|
||||
|
||||
line = g_strndup(p, newline - p);
|
||||
fifo_buffer_consume(client->input, newline - p + 1);
|
||||
|
||||
return g_strchomp(line);
|
||||
}
|
||||
|
||||
static int client_input_received(struct client *client, size_t bytesRead)
|
||||
{
|
||||
char *line;
|
||||
int ret;
|
||||
|
||||
fifo_buffer_append(client->input, bytesRead);
|
||||
|
||||
/* process all lines */
|
||||
|
||||
while ((line = client_read_line(client)) != NULL) {
|
||||
ret = client_process_line(client, line);
|
||||
g_free(line);
|
||||
|
||||
if (ret == COMMAND_RETURN_KILL ||
|
||||
ret == COMMAND_RETURN_CLOSE)
|
||||
return ret;
|
||||
if (client_is_expired(client))
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int client_read(struct client *client)
|
||||
{
|
||||
char *p;
|
||||
size_t max_length;
|
||||
GError *error = NULL;
|
||||
GIOStatus status;
|
||||
gsize bytes_read;
|
||||
|
||||
assert(client != NULL);
|
||||
assert(client->channel != NULL);
|
||||
|
||||
p = fifo_buffer_write(client->input, &max_length);
|
||||
if (p == NULL) {
|
||||
g_warning("[%u] buffer overflow", client->num);
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
status = g_io_channel_read_chars(client->channel, p, max_length,
|
||||
&bytes_read, &error);
|
||||
switch (status) {
|
||||
case G_IO_STATUS_NORMAL:
|
||||
return client_input_received(client, bytes_read);
|
||||
|
||||
case G_IO_STATUS_AGAIN:
|
||||
/* try again later, after select() */
|
||||
return 0;
|
||||
|
||||
case G_IO_STATUS_EOF:
|
||||
/* peer disconnected */
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
|
||||
case G_IO_STATUS_ERROR:
|
||||
/* I/O error */
|
||||
g_warning("failed to read from client %d: %s",
|
||||
client->num, error->message);
|
||||
g_error_free(error);
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
||||
gpointer data);
|
||||
|
||||
static gboolean
|
||||
client_in_event(G_GNUC_UNUSED GIOChannel *source,
|
||||
GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
struct client *client = data;
|
||||
int ret;
|
||||
|
||||
assert(!client_is_expired(client));
|
||||
|
||||
if (condition != G_IO_IN) {
|
||||
client_set_expired(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_timer_start(client->last_activity);
|
||||
|
||||
ret = client_read(client);
|
||||
switch (ret) {
|
||||
case COMMAND_RETURN_KILL:
|
||||
client_close(client);
|
||||
g_main_loop_quit(main_loop);
|
||||
return false;
|
||||
|
||||
case COMMAND_RETURN_CLOSE:
|
||||
client_close(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (client_is_expired(client)) {
|
||||
client_close(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!g_queue_is_empty(client->deferred_send)) {
|
||||
/* deferred buffers exist: schedule write */
|
||||
client->source_id = g_io_add_watch(client->channel,
|
||||
G_IO_OUT|G_IO_ERR|G_IO_HUP,
|
||||
client_out_event, client);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* read more */
|
||||
return true;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
struct client *client = data;
|
||||
|
||||
assert(!client_is_expired(client));
|
||||
|
||||
if (condition != G_IO_OUT) {
|
||||
client_set_expired(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
client_write_deferred(client);
|
||||
|
||||
if (client_is_expired(client)) {
|
||||
client_close(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_timer_start(client->last_activity);
|
||||
|
||||
if (g_queue_is_empty(client->deferred_send)) {
|
||||
/* done sending deferred buffers exist: schedule
|
||||
read */
|
||||
client->source_id = g_io_add_watch(client->channel,
|
||||
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
||||
client_in_event, client);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* write more */
|
||||
return true;
|
||||
}
|
||||
|
||||
void client_manager_init(void)
|
||||
{
|
||||
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
|
||||
CLIENT_TIMEOUT_DEFAULT);
|
||||
client_max_connections =
|
||||
config_get_positive(CONF_MAX_CONN,
|
||||
CLIENT_MAX_CONNECTIONS_DEFAULT);
|
||||
client_max_command_list_size =
|
||||
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
|
||||
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
|
||||
* 1024;
|
||||
|
||||
client_max_output_buffer_size =
|
||||
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
|
||||
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
|
||||
* 1024;
|
||||
}
|
||||
|
||||
static void client_close_all(void)
|
||||
{
|
||||
while (clients != NULL) {
|
||||
struct client *client = clients->data;
|
||||
|
||||
client_close(client);
|
||||
}
|
||||
|
||||
assert(num_clients == 0);
|
||||
}
|
||||
|
||||
void client_manager_deinit(void)
|
||||
{
|
||||
client_close_all();
|
||||
|
||||
client_max_connections = 0;
|
||||
|
||||
if (expire_source_id != 0)
|
||||
g_source_remove(expire_source_id);
|
||||
}
|
||||
|
||||
static void
|
||||
client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
struct client *client = data;
|
||||
|
||||
if (client_is_expired(client)) {
|
||||
g_debug("[%u] expired", client->num);
|
||||
client_close(client);
|
||||
} else if (!client->idle_waiting && /* idle clients
|
||||
never expire */
|
||||
(int)g_timer_elapsed(client->last_activity, NULL) >
|
||||
client_timeout) {
|
||||
g_debug("[%u] timeout", client->num);
|
||||
client_close(client);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
client_manager_expire(void)
|
||||
{
|
||||
g_list_foreach(clients, client_check_expired_callback, NULL);
|
||||
}
|
||||
|
||||
static size_t
|
||||
client_write_deferred_buffer(struct client *client,
|
||||
const struct deferred_buffer *buffer)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GIOStatus status;
|
||||
gsize bytes_written;
|
||||
|
||||
assert(client != NULL);
|
||||
assert(client->channel != NULL);
|
||||
assert(buffer != NULL);
|
||||
|
||||
status = g_io_channel_write_chars
|
||||
(client->channel, buffer->data, buffer->size,
|
||||
&bytes_written, &error);
|
||||
switch (status) {
|
||||
case G_IO_STATUS_NORMAL:
|
||||
return bytes_written;
|
||||
|
||||
case G_IO_STATUS_AGAIN:
|
||||
return 0;
|
||||
|
||||
case G_IO_STATUS_EOF:
|
||||
/* client has disconnected */
|
||||
|
||||
client_set_expired(client);
|
||||
return 0;
|
||||
|
||||
case G_IO_STATUS_ERROR:
|
||||
/* I/O error */
|
||||
|
||||
client_set_expired(client);
|
||||
g_warning("failed to flush buffer for %i: %s",
|
||||
client->num, error->message);
|
||||
g_error_free(error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void client_write_deferred(struct client *client)
|
||||
{
|
||||
size_t ret;
|
||||
|
||||
while (!g_queue_is_empty(client->deferred_send)) {
|
||||
struct deferred_buffer *buf =
|
||||
g_queue_peek_head(client->deferred_send);
|
||||
|
||||
assert(buf->size > 0);
|
||||
assert(buf->size <= client->deferred_bytes);
|
||||
|
||||
ret = client_write_deferred_buffer(client, buf);
|
||||
if (ret == 0)
|
||||
break;
|
||||
|
||||
if (ret < buf->size) {
|
||||
assert(client->deferred_bytes >= (size_t)ret);
|
||||
client->deferred_bytes -= ret;
|
||||
buf->size -= ret;
|
||||
memmove(buf->data, buf->data + ret, buf->size);
|
||||
break;
|
||||
} else {
|
||||
size_t decr = sizeof(*buf) -
|
||||
sizeof(buf->data) + buf->size;
|
||||
|
||||
assert(client->deferred_bytes >= decr);
|
||||
client->deferred_bytes -= decr;
|
||||
g_free(buf);
|
||||
g_queue_pop_head(client->deferred_send);
|
||||
}
|
||||
|
||||
g_timer_start(client->last_activity);
|
||||
}
|
||||
|
||||
if (g_queue_is_empty(client->deferred_send)) {
|
||||
g_debug("[%u] buffer empty %lu", client->num,
|
||||
(unsigned long)client->deferred_bytes);
|
||||
assert(client->deferred_bytes == 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void client_defer_output(struct client *client,
|
||||
const void *data, size_t length)
|
||||
{
|
||||
size_t alloc;
|
||||
struct deferred_buffer *buf;
|
||||
|
||||
assert(length > 0);
|
||||
|
||||
alloc = sizeof(*buf) - sizeof(buf->data) + length;
|
||||
client->deferred_bytes += alloc;
|
||||
if (client->deferred_bytes > client_max_output_buffer_size) {
|
||||
g_warning("[%u] output buffer size (%lu) is "
|
||||
"larger than the max (%lu)",
|
||||
client->num,
|
||||
(unsigned long)client->deferred_bytes,
|
||||
(unsigned long)client_max_output_buffer_size);
|
||||
/* cause client to close */
|
||||
client_set_expired(client);
|
||||
return;
|
||||
}
|
||||
|
||||
buf = g_malloc(alloc);
|
||||
buf->size = length;
|
||||
memcpy(buf->data, data, length);
|
||||
|
||||
g_queue_push_tail(client->deferred_send, buf);
|
||||
}
|
||||
|
||||
static void client_write_direct(struct client *client,
|
||||
const char *data, size_t length)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GIOStatus status;
|
||||
gsize bytes_written;
|
||||
|
||||
assert(client != NULL);
|
||||
assert(client->channel != NULL);
|
||||
assert(data != NULL);
|
||||
assert(length > 0);
|
||||
assert(g_queue_is_empty(client->deferred_send));
|
||||
|
||||
status = g_io_channel_write_chars(client->channel, data, length,
|
||||
&bytes_written, &error);
|
||||
switch (status) {
|
||||
case G_IO_STATUS_NORMAL:
|
||||
case G_IO_STATUS_AGAIN:
|
||||
break;
|
||||
|
||||
case G_IO_STATUS_EOF:
|
||||
/* client has disconnected */
|
||||
|
||||
client_set_expired(client);
|
||||
return;
|
||||
|
||||
case G_IO_STATUS_ERROR:
|
||||
/* I/O error */
|
||||
|
||||
client_set_expired(client);
|
||||
g_warning("failed to write to %i: %s",
|
||||
client->num, error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytes_written < length)
|
||||
client_defer_output(client, data + bytes_written,
|
||||
length - bytes_written);
|
||||
|
||||
if (!g_queue_is_empty(client->deferred_send))
|
||||
g_debug("[%u] buffer created", client->num);
|
||||
}
|
||||
|
||||
static void client_write_output(struct client *client)
|
||||
{
|
||||
if (client_is_expired(client) || !client->send_buf_used)
|
||||
return;
|
||||
|
||||
if (!g_queue_is_empty(client->deferred_send)) {
|
||||
client_defer_output(client, client->send_buf,
|
||||
client->send_buf_used);
|
||||
|
||||
if (client_is_expired(client))
|
||||
return;
|
||||
|
||||
/* try to flush the deferred buffers now; the current
|
||||
server command may take too long to finish, and
|
||||
meanwhile try to feed output to the client,
|
||||
otherwise it will time out. One reason why
|
||||
deferring is slow might be that currently each
|
||||
client_write() allocates a new deferred buffer.
|
||||
This should be optimized after MPD 0.14. */
|
||||
client_write_deferred(client);
|
||||
} else
|
||||
client_write_direct(client, client->send_buf,
|
||||
client->send_buf_used);
|
||||
|
||||
client->send_buf_used = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a block of data to the client.
|
||||
*/
|
||||
static void client_write(struct client *client, const char *buffer, size_t buflen)
|
||||
{
|
||||
/* if the client is going to be closed, do nothing */
|
||||
if (client_is_expired(client))
|
||||
return;
|
||||
|
||||
while (buflen > 0 && !client_is_expired(client)) {
|
||||
size_t copylen;
|
||||
|
||||
assert(client->send_buf_used < sizeof(client->send_buf));
|
||||
|
||||
copylen = sizeof(client->send_buf) - client->send_buf_used;
|
||||
if (copylen > buflen)
|
||||
copylen = buflen;
|
||||
|
||||
memcpy(client->send_buf + client->send_buf_used, buffer,
|
||||
copylen);
|
||||
buflen -= copylen;
|
||||
client->send_buf_used += copylen;
|
||||
buffer += copylen;
|
||||
if (client->send_buf_used >= sizeof(client->send_buf))
|
||||
client_write_output(client);
|
||||
}
|
||||
}
|
||||
|
||||
void client_puts(struct client *client, const char *s)
|
||||
{
|
||||
client_write(client, s, strlen(s));
|
||||
}
|
||||
|
||||
void client_vprintf(struct client *client, const char *fmt, va_list args)
|
||||
{
|
||||
va_list tmp;
|
||||
int length;
|
||||
char *buffer;
|
||||
|
||||
va_copy(tmp, args);
|
||||
length = vsnprintf(NULL, 0, fmt, tmp);
|
||||
va_end(tmp);
|
||||
|
||||
if (length <= 0)
|
||||
/* wtf.. */
|
||||
return;
|
||||
|
||||
buffer = g_malloc(length + 1);
|
||||
vsnprintf(buffer, length + 1, fmt, args);
|
||||
client_write(client, buffer, length);
|
||||
g_free(buffer);
|
||||
}
|
||||
|
||||
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
client_vprintf(client, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "idle" response to this client.
|
||||
*/
|
||||
static void
|
||||
client_idle_notify(struct client *client)
|
||||
{
|
||||
unsigned flags, i;
|
||||
const char *const* idle_names;
|
||||
|
||||
assert(client->idle_waiting);
|
||||
assert(client->idle_flags != 0);
|
||||
|
||||
flags = client->idle_flags;
|
||||
client->idle_flags = 0;
|
||||
client->idle_waiting = false;
|
||||
|
||||
idle_names = idle_get_names();
|
||||
for (i = 0; idle_names[i]; ++i) {
|
||||
if (flags & (1 << i) & client->idle_subscriptions)
|
||||
client_printf(client, "changed: %s\n",
|
||||
idle_names[i]);
|
||||
}
|
||||
|
||||
client_puts(client, "OK\n");
|
||||
g_timer_start(client->last_activity);
|
||||
}
|
||||
|
||||
static void
|
||||
client_idle_callback(gpointer data, gpointer user_data)
|
||||
{
|
||||
struct client *client = data;
|
||||
unsigned flags = GPOINTER_TO_UINT(user_data);
|
||||
|
||||
if (client_is_expired(client))
|
||||
return;
|
||||
|
||||
client->idle_flags |= flags;
|
||||
if (client->idle_waiting
|
||||
&& (client->idle_flags & client->idle_subscriptions)) {
|
||||
client_idle_notify(client);
|
||||
client_write_output(client);
|
||||
}
|
||||
}
|
||||
|
||||
void client_manager_idle_add(unsigned flags)
|
||||
{
|
||||
assert(flags != 0);
|
||||
|
||||
g_list_foreach(clients, client_idle_callback, GUINT_TO_POINTER(flags));
|
||||
}
|
||||
|
||||
bool client_idle_wait(struct client *client, unsigned flags)
|
||||
{
|
||||
assert(!client->idle_waiting);
|
||||
|
||||
client->idle_waiting = true;
|
||||
client->idle_subscriptions = flags;
|
||||
|
||||
if (client->idle_flags & client->idle_subscriptions) {
|
||||
client_idle_notify(client);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
||||
108
src/client_event.c
Normal file
108
src/client_event.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
#include "main.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static gboolean
|
||||
client_out_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
struct client *client = data;
|
||||
|
||||
assert(!client_is_expired(client));
|
||||
|
||||
if (condition != G_IO_OUT) {
|
||||
client_set_expired(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
client_write_deferred(client);
|
||||
|
||||
if (client_is_expired(client)) {
|
||||
client_close(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_timer_start(client->last_activity);
|
||||
|
||||
if (g_queue_is_empty(client->deferred_send)) {
|
||||
/* done sending deferred buffers exist: schedule
|
||||
read */
|
||||
client->source_id = g_io_add_watch(client->channel,
|
||||
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
||||
client_in_event, client);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* write more */
|
||||
return true;
|
||||
}
|
||||
|
||||
gboolean
|
||||
client_in_event(G_GNUC_UNUSED GIOChannel *source, GIOCondition condition,
|
||||
gpointer data)
|
||||
{
|
||||
struct client *client = data;
|
||||
enum command_return ret;
|
||||
|
||||
assert(!client_is_expired(client));
|
||||
|
||||
if (condition != G_IO_IN) {
|
||||
client_set_expired(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_timer_start(client->last_activity);
|
||||
|
||||
ret = client_read(client);
|
||||
switch (ret) {
|
||||
case COMMAND_RETURN_OK:
|
||||
case COMMAND_RETURN_ERROR:
|
||||
break;
|
||||
|
||||
case COMMAND_RETURN_KILL:
|
||||
client_close(client);
|
||||
g_main_loop_quit(main_loop);
|
||||
return false;
|
||||
|
||||
case COMMAND_RETURN_CLOSE:
|
||||
client_close(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (client_is_expired(client)) {
|
||||
client_close(client);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!g_queue_is_empty(client->deferred_send)) {
|
||||
/* deferred buffers exist: schedule write */
|
||||
client->source_id = g_io_add_watch(client->channel,
|
||||
G_IO_OUT|G_IO_ERR|G_IO_HUP,
|
||||
client_out_event, client);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* read more */
|
||||
return true;
|
||||
}
|
||||
90
src/client_expire.c
Normal file
90
src/client_expire.c
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
|
||||
static guint expire_source_id;
|
||||
|
||||
void
|
||||
client_set_expired(struct client *client)
|
||||
{
|
||||
if (!client_is_expired(client))
|
||||
client_schedule_expire();
|
||||
|
||||
if (client->source_id != 0) {
|
||||
g_source_remove(client->source_id);
|
||||
client->source_id = 0;
|
||||
}
|
||||
|
||||
if (client->channel != NULL) {
|
||||
g_io_channel_unref(client->channel);
|
||||
client->channel = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
client_check_expired_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
struct client *client = data;
|
||||
|
||||
if (client_is_expired(client)) {
|
||||
g_debug("[%u] expired", client->num);
|
||||
client_close(client);
|
||||
} else if (!client->idle_waiting && /* idle clients
|
||||
never expire */
|
||||
(int)g_timer_elapsed(client->last_activity, NULL) >
|
||||
client_timeout) {
|
||||
g_debug("[%u] timeout", client->num);
|
||||
client_close(client);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
client_manager_expire(void)
|
||||
{
|
||||
client_list_foreach(client_check_expired_callback, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* An idle event which calls client_manager_expire().
|
||||
*/
|
||||
static gboolean
|
||||
client_manager_expire_event(G_GNUC_UNUSED gpointer data)
|
||||
{
|
||||
expire_source_id = 0;
|
||||
client_manager_expire();
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
client_schedule_expire(void)
|
||||
{
|
||||
if (expire_source_id == 0)
|
||||
/* delayed deletion */
|
||||
expire_source_id = g_idle_add(client_manager_expire_event,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void
|
||||
client_deinit_expire(void)
|
||||
{
|
||||
if (expire_source_id != 0)
|
||||
g_source_remove(expire_source_id);
|
||||
}
|
||||
73
src/client_global.c
Normal file
73
src/client_global.c
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
#include "conf.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#define CLIENT_TIMEOUT_DEFAULT (60)
|
||||
#define CLIENT_MAX_CONNECTIONS_DEFAULT (10)
|
||||
#define CLIENT_MAX_COMMAND_LIST_DEFAULT (2048*1024)
|
||||
#define CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024)
|
||||
|
||||
/* set this to zero to indicate we have no possible clients */
|
||||
unsigned int client_max_connections;
|
||||
int client_timeout;
|
||||
size_t client_max_command_list_size;
|
||||
size_t client_max_output_buffer_size;
|
||||
|
||||
void client_manager_init(void)
|
||||
{
|
||||
client_timeout = config_get_positive(CONF_CONN_TIMEOUT,
|
||||
CLIENT_TIMEOUT_DEFAULT);
|
||||
client_max_connections =
|
||||
config_get_positive(CONF_MAX_CONN,
|
||||
CLIENT_MAX_CONNECTIONS_DEFAULT);
|
||||
client_max_command_list_size =
|
||||
config_get_positive(CONF_MAX_COMMAND_LIST_SIZE,
|
||||
CLIENT_MAX_COMMAND_LIST_DEFAULT / 1024)
|
||||
* 1024;
|
||||
|
||||
client_max_output_buffer_size =
|
||||
config_get_positive(CONF_MAX_OUTPUT_BUFFER_SIZE,
|
||||
CLIENT_MAX_OUTPUT_BUFFER_SIZE_DEFAULT / 1024)
|
||||
* 1024;
|
||||
}
|
||||
|
||||
static void client_close_all(void)
|
||||
{
|
||||
while (!client_list_is_empty()) {
|
||||
struct client *client = client_list_get_first();
|
||||
|
||||
client_close(client);
|
||||
}
|
||||
|
||||
assert(client_list_is_empty());
|
||||
}
|
||||
|
||||
void client_manager_deinit(void)
|
||||
{
|
||||
client_close_all();
|
||||
|
||||
client_max_connections = 0;
|
||||
|
||||
client_deinit_expire();
|
||||
}
|
||||
89
src/client_idle.c
Normal file
89
src/client_idle.c
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
#include "idle.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/**
|
||||
* Send "idle" response to this client.
|
||||
*/
|
||||
static void
|
||||
client_idle_notify(struct client *client)
|
||||
{
|
||||
unsigned flags, i;
|
||||
const char *const* idle_names;
|
||||
|
||||
assert(client->idle_waiting);
|
||||
assert(client->idle_flags != 0);
|
||||
|
||||
flags = client->idle_flags;
|
||||
client->idle_flags = 0;
|
||||
client->idle_waiting = false;
|
||||
|
||||
idle_names = idle_get_names();
|
||||
for (i = 0; idle_names[i]; ++i) {
|
||||
if (flags & (1 << i) & client->idle_subscriptions)
|
||||
client_printf(client, "changed: %s\n",
|
||||
idle_names[i]);
|
||||
}
|
||||
|
||||
client_puts(client, "OK\n");
|
||||
g_timer_start(client->last_activity);
|
||||
}
|
||||
|
||||
static void
|
||||
client_idle_callback(gpointer data, gpointer user_data)
|
||||
{
|
||||
struct client *client = data;
|
||||
unsigned flags = GPOINTER_TO_UINT(user_data);
|
||||
|
||||
if (client_is_expired(client))
|
||||
return;
|
||||
|
||||
client->idle_flags |= flags;
|
||||
if (client->idle_waiting
|
||||
&& (client->idle_flags & client->idle_subscriptions)) {
|
||||
client_idle_notify(client);
|
||||
client_write_output(client);
|
||||
}
|
||||
}
|
||||
|
||||
void client_manager_idle_add(unsigned flags)
|
||||
{
|
||||
assert(flags != 0);
|
||||
|
||||
client_list_foreach(client_idle_callback, GUINT_TO_POINTER(flags));
|
||||
}
|
||||
|
||||
bool client_idle_wait(struct client *client, unsigned flags)
|
||||
{
|
||||
assert(!client->idle_waiting);
|
||||
|
||||
client->idle_waiting = true;
|
||||
client->idle_subscriptions = flags;
|
||||
|
||||
if (client->idle_flags & client->idle_subscriptions) {
|
||||
client_idle_notify(client);
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
145
src/client_internal.h
Normal file
145
src/client_internal.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_CLIENT_INTERNAL_H
|
||||
#define MPD_CLIENT_INTERNAL_H
|
||||
|
||||
#include "client.h"
|
||||
#include "command.h"
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "client"
|
||||
|
||||
struct deferred_buffer {
|
||||
size_t size;
|
||||
char data[sizeof(long)];
|
||||
};
|
||||
|
||||
struct client {
|
||||
GIOChannel *channel;
|
||||
guint source_id;
|
||||
|
||||
/** the buffer for reading lines from the #channel */
|
||||
struct fifo_buffer *input;
|
||||
|
||||
unsigned permission;
|
||||
|
||||
/** the uid of the client process, or -1 if unknown */
|
||||
int uid;
|
||||
|
||||
/**
|
||||
* How long since the last activity from this client?
|
||||
*/
|
||||
GTimer *last_activity;
|
||||
|
||||
GSList *cmd_list; /* for when in list mode */
|
||||
int cmd_list_OK; /* print OK after each command execution */
|
||||
size_t cmd_list_size; /* mem cmd_list consumes */
|
||||
GQueue *deferred_send; /* for output if client is slow */
|
||||
size_t deferred_bytes; /* mem deferred_send consumes */
|
||||
unsigned int num; /* client number */
|
||||
|
||||
char send_buf[16384];
|
||||
size_t send_buf_used; /* bytes used this instance */
|
||||
|
||||
/** is this client waiting for an "idle" response? */
|
||||
bool idle_waiting;
|
||||
|
||||
/** idle flags pending on this client, to be sent as soon as
|
||||
the client enters "idle" */
|
||||
unsigned idle_flags;
|
||||
|
||||
/** idle flags that the client wants to receive */
|
||||
unsigned idle_subscriptions;
|
||||
};
|
||||
|
||||
extern unsigned int client_max_connections;
|
||||
extern int client_timeout;
|
||||
extern size_t client_max_command_list_size;
|
||||
extern size_t client_max_output_buffer_size;
|
||||
|
||||
bool
|
||||
client_list_is_empty(void);
|
||||
|
||||
bool
|
||||
client_list_is_full(void);
|
||||
|
||||
struct client *
|
||||
client_list_get_first(void);
|
||||
|
||||
void
|
||||
client_list_add(struct client *client);
|
||||
|
||||
void
|
||||
client_list_foreach(GFunc func, gpointer user_data);
|
||||
|
||||
void
|
||||
client_list_remove(struct client *client);
|
||||
|
||||
void
|
||||
client_close(struct client *client);
|
||||
|
||||
static inline void
|
||||
new_cmd_list_ptr(struct client *client, const char *s)
|
||||
{
|
||||
client->cmd_list = g_slist_prepend(client->cmd_list, g_strdup(s));
|
||||
}
|
||||
|
||||
static inline void
|
||||
free_cmd_list(GSList *list)
|
||||
{
|
||||
for (GSList *tmp = list; tmp != NULL; tmp = g_slist_next(tmp))
|
||||
g_free(tmp->data);
|
||||
|
||||
g_slist_free(list);
|
||||
}
|
||||
|
||||
void
|
||||
client_set_expired(struct client *client);
|
||||
|
||||
/**
|
||||
* Schedule an "expired" check for all clients: permanently delete
|
||||
* clients which have been set "expired" with client_set_expired().
|
||||
*/
|
||||
void
|
||||
client_schedule_expire(void);
|
||||
|
||||
/**
|
||||
* Removes a scheduled "expired" check.
|
||||
*/
|
||||
void
|
||||
client_deinit_expire(void);
|
||||
|
||||
enum command_return
|
||||
client_read(struct client *client);
|
||||
|
||||
enum command_return
|
||||
client_process_line(struct client *client, char *line);
|
||||
|
||||
void
|
||||
client_write_deferred(struct client *client);
|
||||
|
||||
void
|
||||
client_write_output(struct client *client);
|
||||
|
||||
gboolean
|
||||
client_in_event(GIOChannel *source, GIOCondition condition,
|
||||
gpointer data);
|
||||
|
||||
#endif
|
||||
69
src/client_list.c
Normal file
69
src/client_list.c
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static GList *clients;
|
||||
static unsigned num_clients;
|
||||
|
||||
bool
|
||||
client_list_is_empty(void)
|
||||
{
|
||||
return num_clients == 0;
|
||||
}
|
||||
|
||||
bool
|
||||
client_list_is_full(void)
|
||||
{
|
||||
return num_clients >= client_max_connections;
|
||||
}
|
||||
|
||||
struct client *
|
||||
client_list_get_first(void)
|
||||
{
|
||||
assert(clients != NULL);
|
||||
|
||||
return clients->data;
|
||||
}
|
||||
|
||||
void
|
||||
client_list_add(struct client *client)
|
||||
{
|
||||
clients = g_list_prepend(clients, client);
|
||||
++num_clients;
|
||||
}
|
||||
|
||||
void
|
||||
client_list_foreach(GFunc func, gpointer user_data)
|
||||
{
|
||||
g_list_foreach(clients, func, user_data);
|
||||
}
|
||||
|
||||
void
|
||||
client_list_remove(struct client *client)
|
||||
{
|
||||
assert(num_clients > 0);
|
||||
assert(clients != NULL);
|
||||
|
||||
clients = g_list_remove(clients, client);
|
||||
--num_clients;
|
||||
}
|
||||
159
src/client_new.c
Normal file
159
src/client_new.c
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
#include "fifo_buffer.h"
|
||||
#include "socket_util.h"
|
||||
#include "permission.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef HAVE_LIBWRAP
|
||||
#include <tcpd.h>
|
||||
#endif
|
||||
|
||||
|
||||
#define LOG_LEVEL_SECURE G_LOG_LEVEL_INFO
|
||||
|
||||
static const char GREETING[] = "OK MPD " PROTOCOL_VERSION "\n";
|
||||
|
||||
void client_new(int fd, const struct sockaddr *sa, size_t sa_length, int uid)
|
||||
{
|
||||
static unsigned int next_client_num;
|
||||
struct client *client;
|
||||
char *remote;
|
||||
|
||||
assert(fd >= 0);
|
||||
|
||||
#ifdef HAVE_LIBWRAP
|
||||
if (sa->sa_family != AF_UNIX) {
|
||||
char *hostaddr = sockaddr_to_string(sa, sa_length, NULL);
|
||||
const char *progname = g_get_prgname();
|
||||
|
||||
struct request_info req;
|
||||
request_init(&req, RQ_FILE, fd, RQ_DAEMON, progname, 0);
|
||||
|
||||
fromhost(&req);
|
||||
|
||||
if (!hosts_access(&req)) {
|
||||
/* tcp wrappers says no */
|
||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||
"libwrap refused connection (libwrap=%s) from %s",
|
||||
progname, hostaddr);
|
||||
|
||||
g_free(hostaddr);
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
g_free(hostaddr);
|
||||
}
|
||||
#endif /* HAVE_WRAP */
|
||||
|
||||
if (client_list_is_full()) {
|
||||
g_warning("Max Connections Reached!");
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
client = g_new0(struct client, 1);
|
||||
|
||||
#ifndef G_OS_WIN32
|
||||
client->channel = g_io_channel_unix_new(fd);
|
||||
#else
|
||||
client->channel = g_io_channel_win32_new_socket(fd);
|
||||
#endif
|
||||
/* GLib is responsible for closing the file descriptor */
|
||||
g_io_channel_set_close_on_unref(client->channel, true);
|
||||
/* NULL encoding means the stream is binary safe; the MPD
|
||||
protocol is UTF-8 only, but we are doing this call anyway
|
||||
to prevent GLib from messing around with the stream */
|
||||
g_io_channel_set_encoding(client->channel, NULL, NULL);
|
||||
/* we prefer to do buffering */
|
||||
g_io_channel_set_buffered(client->channel, false);
|
||||
|
||||
client->source_id = g_io_add_watch(client->channel,
|
||||
G_IO_IN|G_IO_ERR|G_IO_HUP,
|
||||
client_in_event, client);
|
||||
|
||||
client->input = fifo_buffer_new(4096);
|
||||
|
||||
client->permission = getDefaultPermissions();
|
||||
client->uid = uid;
|
||||
|
||||
client->last_activity = g_timer_new();
|
||||
|
||||
client->cmd_list = NULL;
|
||||
client->cmd_list_OK = -1;
|
||||
client->cmd_list_size = 0;
|
||||
|
||||
client->deferred_send = g_queue_new();
|
||||
client->deferred_bytes = 0;
|
||||
client->num = next_client_num++;
|
||||
|
||||
client->send_buf_used = 0;
|
||||
|
||||
(void)send(fd, GREETING, sizeof(GREETING) - 1, 0);
|
||||
|
||||
client_list_add(client);
|
||||
|
||||
remote = sockaddr_to_string(sa, sa_length, NULL);
|
||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||
"[%u] opened from %s", client->num, remote);
|
||||
g_free(remote);
|
||||
}
|
||||
|
||||
static void
|
||||
deferred_buffer_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
struct deferred_buffer *buffer = data;
|
||||
g_free(buffer);
|
||||
}
|
||||
|
||||
void
|
||||
client_close(struct client *client)
|
||||
{
|
||||
client_list_remove(client);
|
||||
|
||||
client_set_expired(client);
|
||||
|
||||
g_timer_destroy(client->last_activity);
|
||||
|
||||
if (client->cmd_list) {
|
||||
free_cmd_list(client->cmd_list);
|
||||
client->cmd_list = NULL;
|
||||
}
|
||||
|
||||
g_queue_foreach(client->deferred_send, deferred_buffer_free, NULL);
|
||||
g_queue_free(client->deferred_send);
|
||||
|
||||
fifo_buffer_free(client->input);
|
||||
|
||||
g_log(G_LOG_DOMAIN, LOG_LEVEL_SECURE,
|
||||
"[%u] closed", client->num);
|
||||
g_free(client);
|
||||
}
|
||||
146
src/client_process.c
Normal file
146
src/client_process.c
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define CLIENT_LIST_MODE_BEGIN "command_list_begin"
|
||||
#define CLIENT_LIST_OK_MODE_BEGIN "command_list_ok_begin"
|
||||
#define CLIENT_LIST_MODE_END "command_list_end"
|
||||
|
||||
static enum command_return
|
||||
client_process_command_list(struct client *client, bool list_ok, GSList *list)
|
||||
{
|
||||
enum command_return ret = COMMAND_RETURN_OK;
|
||||
unsigned num = 0;
|
||||
|
||||
for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
|
||||
char *cmd = cur->data;
|
||||
|
||||
g_debug("command_process_list: process command \"%s\"",
|
||||
cmd);
|
||||
ret = command_process(client, num++, cmd);
|
||||
g_debug("command_process_list: command returned %i", ret);
|
||||
if (ret != COMMAND_RETURN_OK || client_is_expired(client))
|
||||
break;
|
||||
else if (list_ok)
|
||||
client_puts(client, "list_OK\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum command_return
|
||||
client_process_line(struct client *client, char *line)
|
||||
{
|
||||
enum command_return ret;
|
||||
|
||||
if (strcmp(line, "noidle") == 0) {
|
||||
if (client->idle_waiting) {
|
||||
/* send empty idle response and leave idle mode */
|
||||
client->idle_waiting = false;
|
||||
command_success(client);
|
||||
client_write_output(client);
|
||||
}
|
||||
|
||||
/* do nothing if the client wasn't idling: the client
|
||||
has already received the full idle response from
|
||||
client_idle_notify(), which he can now evaluate */
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
} else if (client->idle_waiting) {
|
||||
/* during idle mode, clients must not send anything
|
||||
except "noidle" */
|
||||
g_warning("[%u] command \"%s\" during idle",
|
||||
client->num, line);
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
if (client->cmd_list_OK >= 0) {
|
||||
if (strcmp(line, CLIENT_LIST_MODE_END) == 0) {
|
||||
g_debug("[%u] process command list",
|
||||
client->num);
|
||||
|
||||
/* for scalability reasons, we have prepended
|
||||
each new command; now we have to reverse it
|
||||
to restore the correct order */
|
||||
client->cmd_list = g_slist_reverse(client->cmd_list);
|
||||
|
||||
ret = client_process_command_list(client,
|
||||
client->cmd_list_OK,
|
||||
client->cmd_list);
|
||||
g_debug("[%u] process command "
|
||||
"list returned %i", client->num, ret);
|
||||
|
||||
if (ret == COMMAND_RETURN_CLOSE ||
|
||||
client_is_expired(client))
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
|
||||
if (ret == COMMAND_RETURN_OK)
|
||||
command_success(client);
|
||||
|
||||
client_write_output(client);
|
||||
free_cmd_list(client->cmd_list);
|
||||
client->cmd_list = NULL;
|
||||
client->cmd_list_OK = -1;
|
||||
} else {
|
||||
size_t len = strlen(line) + 1;
|
||||
client->cmd_list_size += len;
|
||||
if (client->cmd_list_size >
|
||||
client_max_command_list_size) {
|
||||
g_warning("[%u] command list size (%lu) "
|
||||
"is larger than the max (%lu)",
|
||||
client->num,
|
||||
(unsigned long)client->cmd_list_size,
|
||||
(unsigned long)client_max_command_list_size);
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
new_cmd_list_ptr(client, line);
|
||||
ret = COMMAND_RETURN_OK;
|
||||
}
|
||||
} else {
|
||||
if (strcmp(line, CLIENT_LIST_MODE_BEGIN) == 0) {
|
||||
client->cmd_list_OK = 0;
|
||||
ret = COMMAND_RETURN_OK;
|
||||
} else if (strcmp(line, CLIENT_LIST_OK_MODE_BEGIN) == 0) {
|
||||
client->cmd_list_OK = 1;
|
||||
ret = COMMAND_RETURN_OK;
|
||||
} else {
|
||||
g_debug("[%u] process command \"%s\"",
|
||||
client->num, line);
|
||||
ret = command_process(client, 0, line);
|
||||
g_debug("[%u] command returned %i",
|
||||
client->num, ret);
|
||||
|
||||
if (ret == COMMAND_RETURN_CLOSE ||
|
||||
client_is_expired(client))
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
|
||||
if (ret == COMMAND_RETURN_OK)
|
||||
command_success(client);
|
||||
|
||||
client_write_output(client);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
113
src/client_read.c
Normal file
113
src/client_read.c
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
#include "fifo_buffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
static char *
|
||||
client_read_line(struct client *client)
|
||||
{
|
||||
const char *p, *newline;
|
||||
size_t length;
|
||||
char *line;
|
||||
|
||||
p = fifo_buffer_read(client->input, &length);
|
||||
if (p == NULL)
|
||||
return NULL;
|
||||
|
||||
newline = memchr(p, '\n', length);
|
||||
if (newline == NULL)
|
||||
return NULL;
|
||||
|
||||
line = g_strndup(p, newline - p);
|
||||
fifo_buffer_consume(client->input, newline - p + 1);
|
||||
|
||||
return g_strchomp(line);
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
client_input_received(struct client *client, size_t bytesRead)
|
||||
{
|
||||
char *line;
|
||||
|
||||
fifo_buffer_append(client->input, bytesRead);
|
||||
|
||||
/* process all lines */
|
||||
|
||||
while ((line = client_read_line(client)) != NULL) {
|
||||
enum command_return ret = client_process_line(client, line);
|
||||
g_free(line);
|
||||
|
||||
if (ret == COMMAND_RETURN_KILL ||
|
||||
ret == COMMAND_RETURN_CLOSE)
|
||||
return ret;
|
||||
if (client_is_expired(client))
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
enum command_return
|
||||
client_read(struct client *client)
|
||||
{
|
||||
char *p;
|
||||
size_t max_length;
|
||||
GError *error = NULL;
|
||||
GIOStatus status;
|
||||
gsize bytes_read;
|
||||
|
||||
assert(client != NULL);
|
||||
assert(client->channel != NULL);
|
||||
|
||||
p = fifo_buffer_write(client->input, &max_length);
|
||||
if (p == NULL) {
|
||||
g_warning("[%u] buffer overflow", client->num);
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
status = g_io_channel_read_chars(client->channel, p, max_length,
|
||||
&bytes_read, &error);
|
||||
switch (status) {
|
||||
case G_IO_STATUS_NORMAL:
|
||||
return client_input_received(client, bytes_read);
|
||||
|
||||
case G_IO_STATUS_AGAIN:
|
||||
/* try again later, after select() */
|
||||
return COMMAND_RETURN_OK;
|
||||
|
||||
case G_IO_STATUS_EOF:
|
||||
/* peer disconnected */
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
|
||||
case G_IO_STATUS_ERROR:
|
||||
/* I/O error */
|
||||
g_warning("failed to read from client %d: %s",
|
||||
client->num, error->message);
|
||||
g_error_free(error);
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
return COMMAND_RETURN_CLOSE;
|
||||
}
|
||||
284
src/client_write.c
Normal file
284
src/client_write.c
Normal file
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "client_internal.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static size_t
|
||||
client_write_deferred_buffer(struct client *client,
|
||||
const struct deferred_buffer *buffer)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GIOStatus status;
|
||||
gsize bytes_written;
|
||||
|
||||
assert(client != NULL);
|
||||
assert(client->channel != NULL);
|
||||
assert(buffer != NULL);
|
||||
|
||||
status = g_io_channel_write_chars
|
||||
(client->channel, buffer->data, buffer->size,
|
||||
&bytes_written, &error);
|
||||
switch (status) {
|
||||
case G_IO_STATUS_NORMAL:
|
||||
return bytes_written;
|
||||
|
||||
case G_IO_STATUS_AGAIN:
|
||||
return 0;
|
||||
|
||||
case G_IO_STATUS_EOF:
|
||||
/* client has disconnected */
|
||||
|
||||
client_set_expired(client);
|
||||
return 0;
|
||||
|
||||
case G_IO_STATUS_ERROR:
|
||||
/* I/O error */
|
||||
|
||||
client_set_expired(client);
|
||||
g_warning("failed to flush buffer for %i: %s",
|
||||
client->num, error->message);
|
||||
g_error_free(error);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* unreachable */
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
client_write_deferred(struct client *client)
|
||||
{
|
||||
size_t ret;
|
||||
|
||||
while (!g_queue_is_empty(client->deferred_send)) {
|
||||
struct deferred_buffer *buf =
|
||||
g_queue_peek_head(client->deferred_send);
|
||||
|
||||
assert(buf->size > 0);
|
||||
assert(buf->size <= client->deferred_bytes);
|
||||
|
||||
ret = client_write_deferred_buffer(client, buf);
|
||||
if (ret == 0)
|
||||
break;
|
||||
|
||||
if (ret < buf->size) {
|
||||
assert(client->deferred_bytes >= (size_t)ret);
|
||||
client->deferred_bytes -= ret;
|
||||
buf->size -= ret;
|
||||
memmove(buf->data, buf->data + ret, buf->size);
|
||||
break;
|
||||
} else {
|
||||
size_t decr = sizeof(*buf) -
|
||||
sizeof(buf->data) + buf->size;
|
||||
|
||||
assert(client->deferred_bytes >= decr);
|
||||
client->deferred_bytes -= decr;
|
||||
g_free(buf);
|
||||
g_queue_pop_head(client->deferred_send);
|
||||
}
|
||||
|
||||
g_timer_start(client->last_activity);
|
||||
}
|
||||
|
||||
if (g_queue_is_empty(client->deferred_send)) {
|
||||
g_debug("[%u] buffer empty %lu", client->num,
|
||||
(unsigned long)client->deferred_bytes);
|
||||
assert(client->deferred_bytes == 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void client_defer_output(struct client *client,
|
||||
const void *data, size_t length)
|
||||
{
|
||||
size_t alloc;
|
||||
struct deferred_buffer *buf;
|
||||
|
||||
assert(length > 0);
|
||||
|
||||
alloc = sizeof(*buf) - sizeof(buf->data) + length;
|
||||
client->deferred_bytes += alloc;
|
||||
if (client->deferred_bytes > client_max_output_buffer_size) {
|
||||
g_warning("[%u] output buffer size (%lu) is "
|
||||
"larger than the max (%lu)",
|
||||
client->num,
|
||||
(unsigned long)client->deferred_bytes,
|
||||
(unsigned long)client_max_output_buffer_size);
|
||||
/* cause client to close */
|
||||
client_set_expired(client);
|
||||
return;
|
||||
}
|
||||
|
||||
buf = g_malloc(alloc);
|
||||
buf->size = length;
|
||||
memcpy(buf->data, data, length);
|
||||
|
||||
g_queue_push_tail(client->deferred_send, buf);
|
||||
}
|
||||
|
||||
static void client_write_direct(struct client *client,
|
||||
const char *data, size_t length)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GIOStatus status;
|
||||
gsize bytes_written;
|
||||
|
||||
assert(client != NULL);
|
||||
assert(client->channel != NULL);
|
||||
assert(data != NULL);
|
||||
assert(length > 0);
|
||||
assert(g_queue_is_empty(client->deferred_send));
|
||||
|
||||
status = g_io_channel_write_chars(client->channel, data, length,
|
||||
&bytes_written, &error);
|
||||
switch (status) {
|
||||
case G_IO_STATUS_NORMAL:
|
||||
case G_IO_STATUS_AGAIN:
|
||||
break;
|
||||
|
||||
case G_IO_STATUS_EOF:
|
||||
/* client has disconnected */
|
||||
|
||||
client_set_expired(client);
|
||||
return;
|
||||
|
||||
case G_IO_STATUS_ERROR:
|
||||
/* I/O error */
|
||||
|
||||
client_set_expired(client);
|
||||
g_warning("failed to write to %i: %s",
|
||||
client->num, error->message);
|
||||
g_error_free(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bytes_written < length)
|
||||
client_defer_output(client, data + bytes_written,
|
||||
length - bytes_written);
|
||||
|
||||
if (!g_queue_is_empty(client->deferred_send))
|
||||
g_debug("[%u] buffer created", client->num);
|
||||
}
|
||||
|
||||
void
|
||||
client_write_output(struct client *client)
|
||||
{
|
||||
if (client_is_expired(client) || !client->send_buf_used)
|
||||
return;
|
||||
|
||||
if (!g_queue_is_empty(client->deferred_send)) {
|
||||
client_defer_output(client, client->send_buf,
|
||||
client->send_buf_used);
|
||||
|
||||
if (client_is_expired(client))
|
||||
return;
|
||||
|
||||
/* try to flush the deferred buffers now; the current
|
||||
server command may take too long to finish, and
|
||||
meanwhile try to feed output to the client,
|
||||
otherwise it will time out. One reason why
|
||||
deferring is slow might be that currently each
|
||||
client_write() allocates a new deferred buffer.
|
||||
This should be optimized after MPD 0.14. */
|
||||
client_write_deferred(client);
|
||||
} else
|
||||
client_write_direct(client, client->send_buf,
|
||||
client->send_buf_used);
|
||||
|
||||
client->send_buf_used = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a block of data to the client.
|
||||
*/
|
||||
static void client_write(struct client *client, const char *buffer, size_t buflen)
|
||||
{
|
||||
/* if the client is going to be closed, do nothing */
|
||||
if (client_is_expired(client))
|
||||
return;
|
||||
|
||||
while (buflen > 0 && !client_is_expired(client)) {
|
||||
size_t copylen;
|
||||
|
||||
assert(client->send_buf_used < sizeof(client->send_buf));
|
||||
|
||||
copylen = sizeof(client->send_buf) - client->send_buf_used;
|
||||
if (copylen > buflen)
|
||||
copylen = buflen;
|
||||
|
||||
memcpy(client->send_buf + client->send_buf_used, buffer,
|
||||
copylen);
|
||||
buflen -= copylen;
|
||||
client->send_buf_used += copylen;
|
||||
buffer += copylen;
|
||||
if (client->send_buf_used >= sizeof(client->send_buf))
|
||||
client_write_output(client);
|
||||
}
|
||||
}
|
||||
|
||||
void client_puts(struct client *client, const char *s)
|
||||
{
|
||||
client_write(client, s, strlen(s));
|
||||
}
|
||||
|
||||
void client_vprintf(struct client *client, const char *fmt, va_list args)
|
||||
{
|
||||
#ifndef G_OS_WIN32
|
||||
va_list tmp;
|
||||
int length;
|
||||
char *buffer;
|
||||
|
||||
va_copy(tmp, args);
|
||||
length = vsnprintf(NULL, 0, fmt, tmp);
|
||||
va_end(tmp);
|
||||
|
||||
if (length <= 0)
|
||||
/* wtf.. */
|
||||
return;
|
||||
|
||||
buffer = g_malloc(length + 1);
|
||||
vsnprintf(buffer, length + 1, fmt, args);
|
||||
client_write(client, buffer, length);
|
||||
g_free(buffer);
|
||||
#else
|
||||
/* On mingw32, snprintf() expects a 64 bit integer instead of
|
||||
a "long int" for "%li". This is not consistent with our
|
||||
expectation, so we're using plain sprintf() here, hoping
|
||||
the static buffer is large enough. Sorry for this hack,
|
||||
but WIN32 development is so painful, I'm not in the mood to
|
||||
do it properly now. */
|
||||
|
||||
static char buffer[4096];
|
||||
vsprintf(buffer, fmt, args);
|
||||
client_write(client, buffer, strlen(buffer));
|
||||
#endif
|
||||
}
|
||||
|
||||
G_GNUC_PRINTF(2, 3) void client_printf(struct client *client, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, fmt);
|
||||
client_vprintf(client, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
134
src/cmdline.c
134
src/cmdline.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,15 +17,20 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "cmdline.h"
|
||||
#include "path.h"
|
||||
#include "log.h"
|
||||
#include "conf.h"
|
||||
#include "decoder_list.h"
|
||||
#include "config.h"
|
||||
#include "decoder_plugin.h"
|
||||
#include "output_list.h"
|
||||
#include "ls.h"
|
||||
|
||||
#ifdef ENABLE_ENCODER
|
||||
#include "encoder_list.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
#include "archive_list.h"
|
||||
#endif
|
||||
@@ -35,9 +40,37 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf"
|
||||
#ifdef G_OS_WIN32
|
||||
#define CONFIG_FILE_LOCATION "\\mpd\\mpd.conf"
|
||||
#else /* G_OS_WIN32 */
|
||||
#define USER_CONFIG_FILE_LOCATION1 ".mpdconf"
|
||||
#define USER_CONFIG_FILE_LOCATION2 ".mpd/mpd.conf"
|
||||
#endif
|
||||
|
||||
static GQuark
|
||||
cmdline_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("cmdline");
|
||||
}
|
||||
|
||||
static void
|
||||
print_all_decoders(FILE *fp)
|
||||
{
|
||||
for (unsigned i = 0; decoder_plugins[i] != NULL; ++i) {
|
||||
const struct decoder_plugin *plugin = decoder_plugins[i];
|
||||
const char *const*suffixes;
|
||||
|
||||
fprintf(fp, "[%s]", plugin->name);
|
||||
|
||||
for (suffixes = plugin->suffixes;
|
||||
suffixes != NULL && *suffixes != NULL;
|
||||
++suffixes) {
|
||||
fprintf(fp, " %s", *suffixes);
|
||||
}
|
||||
|
||||
fprintf(fp, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
G_GNUC_NORETURN
|
||||
static void version(void)
|
||||
@@ -45,19 +78,25 @@ static void version(void)
|
||||
puts(PACKAGE " (MPD: Music Player Daemon) " VERSION " \n"
|
||||
"\n"
|
||||
"Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"
|
||||
"Copyright (C) 2008 Max Kellermann <max@duempel.org>\n"
|
||||
"Copyright (C) 2008-2010 Max Kellermann <max@duempel.org>\n"
|
||||
"This is free software; see the source for copying conditions. There is NO\n"
|
||||
"warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
|
||||
"\n"
|
||||
"Supported decoders:\n");
|
||||
|
||||
decoder_plugin_init_all();
|
||||
decoder_plugin_print_all_decoders(stdout);
|
||||
print_all_decoders(stdout);
|
||||
|
||||
puts("\n"
|
||||
"Supported outputs:\n");
|
||||
audio_output_plugin_print_all_types(stdout);
|
||||
|
||||
#ifdef ENABLE_ENCODER
|
||||
puts("\n"
|
||||
"Supported encoders:\n");
|
||||
encoder_plugin_print_all_types(stdout);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef ENABLE_ARCHIVE
|
||||
puts("\n"
|
||||
"Supported archives:\n");
|
||||
@@ -72,31 +111,29 @@ static void version(void)
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
#if GLIB_CHECK_VERSION(2,12,0)
|
||||
static const char *summary =
|
||||
"Music Player Daemon - a daemon for playing music.";
|
||||
#endif
|
||||
|
||||
void parseOptions(int argc, char **argv, Options *options)
|
||||
bool
|
||||
parse_cmdline(int argc, char **argv, struct options *options,
|
||||
GError **error_r)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GOptionContext *context;
|
||||
bool ret;
|
||||
static gboolean option_version,
|
||||
option_create_db, option_no_create_db, option_no_daemon,
|
||||
option_no_daemon,
|
||||
option_no_config;
|
||||
const GOptionEntry entries[] = {
|
||||
{ "create-db", 0, 0, G_OPTION_ARG_NONE, &option_create_db,
|
||||
"force (re)creation of database", NULL },
|
||||
{ "kill", 0, 0, G_OPTION_ARG_NONE, &options->kill,
|
||||
"kill the currently running mpd session", NULL },
|
||||
{ "no-config", 0, 0, G_OPTION_ARG_NONE, &option_no_config,
|
||||
"don't read from config", NULL },
|
||||
{ "no-create-db", 0, 0, G_OPTION_ARG_NONE, &option_no_create_db,
|
||||
"don't create database, even if it doesn't exist", NULL },
|
||||
{ "no-daemon", 0, 0, G_OPTION_ARG_NONE, &option_no_daemon,
|
||||
"don't detach from console", NULL },
|
||||
{ "stdout", 0, 0, G_OPTION_ARG_NONE, &options->stdOutput,
|
||||
{ "stdout", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
|
||||
NULL, NULL },
|
||||
{ "stderr", 0, 0, G_OPTION_ARG_NONE, &options->log_stderr,
|
||||
"print messages to stderr", NULL },
|
||||
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &options->verbose,
|
||||
"verbose logging", NULL },
|
||||
@@ -107,16 +144,13 @@ void parseOptions(int argc, char **argv, Options *options)
|
||||
|
||||
options->kill = false;
|
||||
options->daemon = true;
|
||||
options->stdOutput = false;
|
||||
options->log_stderr = false;
|
||||
options->verbose = false;
|
||||
options->createDB = 0;
|
||||
|
||||
context = g_option_context_new("[path/to/mpd.conf]");
|
||||
g_option_context_add_main_entries(context, entries, NULL);
|
||||
|
||||
#if GLIB_CHECK_VERSION(2,12,0)
|
||||
g_option_context_set_summary(context, summary);
|
||||
#endif
|
||||
|
||||
ret = g_option_context_parse(context, &argc, &argv, &error);
|
||||
g_option_context_free(context);
|
||||
@@ -133,39 +167,71 @@ void parseOptions(int argc, char **argv, Options *options)
|
||||
parser can use it already */
|
||||
log_early_init(options->verbose);
|
||||
|
||||
if (option_create_db && option_no_create_db)
|
||||
g_error("Cannot use both --create-db and --no-create-db\n");
|
||||
|
||||
if (option_no_create_db)
|
||||
options->createDB = -1;
|
||||
else if (option_create_db)
|
||||
options->createDB = 1;
|
||||
|
||||
options->daemon = !option_no_daemon;
|
||||
|
||||
if (option_no_config) {
|
||||
g_debug("Ignoring config, using daemon defaults\n");
|
||||
return true;
|
||||
} else if (argc <= 1) {
|
||||
/* default configuration file path */
|
||||
char *path1;
|
||||
char *path2;
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
path1 = g_build_filename(g_get_user_config_dir(),
|
||||
CONFIG_FILE_LOCATION, NULL);
|
||||
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
|
||||
ret = config_read_file(path1, error_r);
|
||||
else {
|
||||
int i = 0;
|
||||
char *system_path = NULL;
|
||||
const char * const *system_config_dirs;
|
||||
|
||||
system_config_dirs = g_get_system_config_dirs();
|
||||
|
||||
while(system_config_dirs[i] != NULL) {
|
||||
system_path = g_build_filename(system_config_dirs[i],
|
||||
CONFIG_FILE_LOCATION,
|
||||
NULL);
|
||||
if(g_file_test(system_path,
|
||||
G_FILE_TEST_IS_REGULAR)) {
|
||||
ret = config_read_file(system_path,error_r);
|
||||
g_free(system_path);
|
||||
g_free(&system_config_dirs);
|
||||
break;
|
||||
}
|
||||
++i;;
|
||||
}
|
||||
g_free(system_path);
|
||||
g_free(&system_config_dirs);
|
||||
}
|
||||
#else /* G_OS_WIN32 */
|
||||
char *path2;
|
||||
path1 = g_build_filename(g_get_home_dir(),
|
||||
USER_CONFIG_FILE_LOCATION1, NULL);
|
||||
path2 = g_build_filename(g_get_home_dir(),
|
||||
USER_CONFIG_FILE_LOCATION2, NULL);
|
||||
if (g_file_test(path1, G_FILE_TEST_IS_REGULAR))
|
||||
config_read_file(path1);
|
||||
ret = config_read_file(path1, error_r);
|
||||
else if (g_file_test(path2, G_FILE_TEST_IS_REGULAR))
|
||||
config_read_file(path2);
|
||||
ret = config_read_file(path2, error_r);
|
||||
else if (g_file_test(SYSTEM_CONFIG_FILE_LOCATION,
|
||||
G_FILE_TEST_IS_REGULAR))
|
||||
config_read_file(SYSTEM_CONFIG_FILE_LOCATION);
|
||||
ret = config_read_file(SYSTEM_CONFIG_FILE_LOCATION,
|
||||
error_r);
|
||||
#endif
|
||||
|
||||
g_free(path1);
|
||||
#ifndef G_OS_WIN32
|
||||
g_free(path2);
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
} else if (argc == 2) {
|
||||
/* specified configuration file */
|
||||
config_read_file(argv[1]);
|
||||
} else
|
||||
g_error("too many arguments");
|
||||
return config_read_file(argv[1], error_r);
|
||||
} else {
|
||||
g_set_error(error_r, cmdline_quark(), 0,
|
||||
"too many arguments");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -22,14 +22,17 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
typedef struct _Options {
|
||||
#include <stdbool.h>
|
||||
|
||||
struct options {
|
||||
gboolean kill;
|
||||
gboolean daemon;
|
||||
gboolean stdOutput;
|
||||
gboolean log_stderr;
|
||||
gboolean verbose;
|
||||
int createDB;
|
||||
} Options;
|
||||
};
|
||||
|
||||
void parseOptions(int argc, char **argv, Options *options);
|
||||
bool
|
||||
parse_cmdline(int argc, char **argv, struct options *options,
|
||||
GError **error_r);
|
||||
|
||||
#endif
|
||||
|
||||
445
src/command.c
445
src/command.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,14 +17,17 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "command.h"
|
||||
#include "player_control.h"
|
||||
#include "playlist.h"
|
||||
#include "playlist_print.h"
|
||||
#include "playlist_save.h"
|
||||
#include "playlist_queue.h"
|
||||
#include "queue_print.h"
|
||||
#include "ls.h"
|
||||
#include "uri.h"
|
||||
#include "decoder_print.h"
|
||||
#include "directory.h"
|
||||
#include "directory_print.h"
|
||||
#include "database.h"
|
||||
@@ -32,7 +35,7 @@
|
||||
#include "volume.h"
|
||||
#include "stats.h"
|
||||
#include "permission.h"
|
||||
#include "buffer2array.h"
|
||||
#include "tokenizer.h"
|
||||
#include "stored_playlist.h"
|
||||
#include "ack.h"
|
||||
#include "output_command.h"
|
||||
@@ -43,8 +46,8 @@
|
||||
#include "client.h"
|
||||
#include "tag_print.h"
|
||||
#include "path.h"
|
||||
#include "replay_gain_config.h"
|
||||
#include "idle.h"
|
||||
#include "config.h"
|
||||
|
||||
#ifdef ENABLE_SQLITE
|
||||
#include "sticker.h"
|
||||
@@ -58,7 +61,6 @@
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
|
||||
#define COMMAND_STATUS_VOLUME "volume"
|
||||
#define COMMAND_STATUS_STATE "state"
|
||||
#define COMMAND_STATUS_REPEAT "repeat"
|
||||
#define COMMAND_STATUS_SINGLE "single"
|
||||
@@ -74,6 +76,8 @@
|
||||
#define COMMAND_STATUS_BITRATE "bitrate"
|
||||
#define COMMAND_STATUS_ERROR "error"
|
||||
#define COMMAND_STATUS_CROSSFADE "xfade"
|
||||
#define COMMAND_STATUS_MIXRAMPDB "mixrampdb"
|
||||
#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay"
|
||||
#define COMMAND_STATUS_AUDIO "audio"
|
||||
#define COMMAND_STATUS_UPDATING_DB "updating_db"
|
||||
|
||||
@@ -166,8 +170,8 @@ check_int(struct client *client, int *value_r,
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LONG_MAX > INT_MAX
|
||||
if (value < INT_MIN || value > INT_MAX) {
|
||||
#if G_MAXLONG > G_MAXINT
|
||||
if (value < G_MININT || value > G_MAXINT) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number too large: %s", s);
|
||||
return false;
|
||||
@@ -198,7 +202,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
|
||||
/* compatibility with older MPD versions: specifying
|
||||
"-1" makes MPD display the whole list */
|
||||
*value_r1 = 0;
|
||||
*value_r2 = UINT_MAX;
|
||||
*value_r2 = G_MAXUINT;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -208,8 +212,8 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LONG_MAX > UINT_MAX
|
||||
if (value > UINT_MAX) {
|
||||
#if G_MAXLONG > G_MAXUINT
|
||||
if (value > G_MAXUINT) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number too large: %s", s);
|
||||
return false;
|
||||
@@ -220,7 +224,7 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
|
||||
|
||||
if (*test == ':') {
|
||||
value = strtol(++test, &test2, 10);
|
||||
if (*test2 != '\0' || test == test2) {
|
||||
if (*test2 != '\0') {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
command_error_v(client, ACK_ERROR_ARG, fmt, args);
|
||||
@@ -228,14 +232,17 @@ check_range(struct client *client, unsigned *value_r1, unsigned *value_r2,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (test == test2)
|
||||
value = G_MAXUINT;
|
||||
|
||||
if (value < 0) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number is negative: %s", s);
|
||||
return false;
|
||||
}
|
||||
|
||||
#if LONG_MAX > UINT_MAX
|
||||
if (value > UINT_MAX) {
|
||||
#if G_MAXLONG > G_MAXUINT
|
||||
if (value > G_MAXUINT) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number too large: %s", s);
|
||||
return false;
|
||||
@@ -262,7 +269,7 @@ check_unsigned(struct client *client, unsigned *value_r, const char *s)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value > UINT_MAX) {
|
||||
if (value > G_MAXUINT) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Number too large: %s", s);
|
||||
return false;
|
||||
@@ -289,6 +296,23 @@ check_bool(struct client *client, bool *value_r, const char *s)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
check_float(struct client *client, float *value_r, const char *s)
|
||||
{
|
||||
float value;
|
||||
char *endptr;
|
||||
|
||||
value = strtof(s, &endptr);
|
||||
if (*endptr != 0 && endptr == s) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Float expected: %s", s);
|
||||
return false;
|
||||
}
|
||||
|
||||
*value_r = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
print_playlist_result(struct client *client,
|
||||
enum playlist_result result)
|
||||
@@ -363,10 +387,12 @@ print_spl_list(struct client *client, GPtrArray *list)
|
||||
client_printf(client, "playlist: %s\n", playlist->name);
|
||||
|
||||
t = playlist->mtime;
|
||||
strftime(timestamp, sizeof(timestamp), "%FT%TZ",
|
||||
#ifdef WIN32
|
||||
strftime(timestamp, sizeof(timestamp),
|
||||
#ifdef G_OS_WIN32
|
||||
"%Y-%m-%dT%H:%M:%SZ",
|
||||
gmtime(&t)
|
||||
#else
|
||||
"%FT%TZ",
|
||||
gmtime_r(&t, &tm)
|
||||
#endif
|
||||
);
|
||||
@@ -384,6 +410,14 @@ handle_urlhandlers(struct client *client,
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_decoders(struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
{
|
||||
decoder_list_print(client);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_tagtypes(struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
@@ -400,7 +434,7 @@ handle_play(struct client *client, int argc, char *argv[])
|
||||
|
||||
if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
result = playPlaylist(&g_playlist, song);
|
||||
result = playlist_play(&g_playlist, song);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -413,7 +447,7 @@ handle_playid(struct client *client, int argc, char *argv[])
|
||||
if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
|
||||
result = playPlaylistById(&g_playlist, id);
|
||||
result = playlist_play_id(&g_playlist, id);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -421,7 +455,7 @@ static enum command_return
|
||||
handle_stop(G_GNUC_UNUSED struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
{
|
||||
stopPlaylist(&g_playlist);
|
||||
playlist_stop(&g_playlist);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -441,11 +475,11 @@ handle_pause(struct client *client,
|
||||
bool pause_flag;
|
||||
if (!check_bool(client, &pause_flag, argv[1]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
playerSetPause(pause_flag);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
playerPause();
|
||||
pc_set_pause(pause_flag);
|
||||
} else
|
||||
pc_pause();
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -454,10 +488,14 @@ handle_status(struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
{
|
||||
const char *state = NULL;
|
||||
struct player_status player_status;
|
||||
int updateJobId;
|
||||
char *error;
|
||||
int song;
|
||||
|
||||
switch (getPlayerState()) {
|
||||
pc_get_status(&player_status);
|
||||
|
||||
switch (player_status.state) {
|
||||
case PLAYER_STATE_STOP:
|
||||
state = "stop";
|
||||
break;
|
||||
@@ -470,7 +508,7 @@ handle_status(struct client *client,
|
||||
}
|
||||
|
||||
client_printf(client,
|
||||
COMMAND_STATUS_VOLUME ": %i\n"
|
||||
"volume: %i\n"
|
||||
COMMAND_STATUS_REPEAT ": %i\n"
|
||||
COMMAND_STATUS_RANDOM ": %i\n"
|
||||
COMMAND_STATUS_SINGLE ": %i\n"
|
||||
@@ -478,34 +516,43 @@ handle_status(struct client *client,
|
||||
COMMAND_STATUS_PLAYLIST ": %li\n"
|
||||
COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
|
||||
COMMAND_STATUS_CROSSFADE ": %i\n"
|
||||
COMMAND_STATUS_MIXRAMPDB ": %f\n"
|
||||
COMMAND_STATUS_MIXRAMPDELAY ": %f\n"
|
||||
COMMAND_STATUS_STATE ": %s\n",
|
||||
volume_level_get(),
|
||||
getPlaylistRepeatStatus(&g_playlist),
|
||||
getPlaylistRandomStatus(&g_playlist),
|
||||
getPlaylistSingleStatus(&g_playlist),
|
||||
getPlaylistConsumeStatus(&g_playlist),
|
||||
getPlaylistVersion(&g_playlist),
|
||||
getPlaylistLength(&g_playlist),
|
||||
(int)(getPlayerCrossFade() + 0.5),
|
||||
playlist_get_repeat(&g_playlist),
|
||||
playlist_get_random(&g_playlist),
|
||||
playlist_get_single(&g_playlist),
|
||||
playlist_get_consume(&g_playlist),
|
||||
playlist_get_version(&g_playlist),
|
||||
playlist_get_length(&g_playlist),
|
||||
(int)(pc_get_cross_fade() + 0.5),
|
||||
pc_get_mixramp_db(),
|
||||
pc_get_mixramp_delay(),
|
||||
state);
|
||||
|
||||
song = getPlaylistCurrentSong(&g_playlist);
|
||||
song = playlist_get_current_song(&g_playlist);
|
||||
if (song >= 0) {
|
||||
client_printf(client,
|
||||
COMMAND_STATUS_SONG ": %i\n"
|
||||
COMMAND_STATUS_SONGID ": %u\n",
|
||||
song, getPlaylistSongId(&g_playlist, song));
|
||||
song, playlist_get_song_id(&g_playlist, song));
|
||||
}
|
||||
|
||||
if (getPlayerState() != PLAYER_STATE_STOP) {
|
||||
const struct audio_format *af = player_get_audio_format();
|
||||
if (player_status.state != PLAYER_STATE_STOP) {
|
||||
struct audio_format_string af_string;
|
||||
|
||||
client_printf(client,
|
||||
COMMAND_STATUS_TIME ": %i:%i\n"
|
||||
COMMAND_STATUS_BITRATE ": %li\n"
|
||||
COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
|
||||
getPlayerElapsedTime(), getPlayerTotalTime(),
|
||||
getPlayerBitRate(),
|
||||
af->sample_rate, af->bits, af->channels);
|
||||
"elapsed: %1.3f\n"
|
||||
COMMAND_STATUS_BITRATE ": %u\n"
|
||||
COMMAND_STATUS_AUDIO ": %s\n",
|
||||
(int)(player_status.elapsed_time + 0.5),
|
||||
(int)(player_status.total_time + 0.5),
|
||||
player_status.elapsed_time,
|
||||
player_status.bit_rate,
|
||||
audio_format_to_string(&player_status.audio_format,
|
||||
&af_string));
|
||||
}
|
||||
|
||||
if ((updateJobId = isUpdatingDB())) {
|
||||
@@ -514,18 +561,20 @@ handle_status(struct client *client,
|
||||
updateJobId);
|
||||
}
|
||||
|
||||
if (getPlayerError() != PLAYER_ERROR_NOERROR) {
|
||||
error = pc_get_error_message();
|
||||
if (error != NULL) {
|
||||
client_printf(client,
|
||||
COMMAND_STATUS_ERROR ": %s\n",
|
||||
getPlayerErrorStr());
|
||||
error);
|
||||
g_free(error);
|
||||
}
|
||||
|
||||
song = getPlaylistNextSong(&g_playlist);
|
||||
song = playlist_get_next_song(&g_playlist);
|
||||
if (song >= 0) {
|
||||
client_printf(client,
|
||||
COMMAND_STATUS_NEXTSONG ": %i\n"
|
||||
COMMAND_STATUS_NEXTSONGID ": %u\n",
|
||||
song, getPlaylistSongId(&g_playlist, song));
|
||||
song, playlist_get_song_id(&g_playlist, song));
|
||||
}
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
@@ -569,7 +618,7 @@ handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
result = addToPlaylist(&g_playlist, uri, NULL);
|
||||
result = playlist_append_uri(&g_playlist, uri, NULL);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -605,7 +654,7 @@ handle_addid(struct client *client, int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
result = addToPlaylist(&g_playlist, uri, &added_id);
|
||||
result = playlist_append_uri(&g_playlist, uri, &added_id);
|
||||
}
|
||||
|
||||
if (result != PLAYLIST_RESULT_SUCCESS)
|
||||
@@ -615,11 +664,11 @@ handle_addid(struct client *client, int argc, char *argv[])
|
||||
int to;
|
||||
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
result = moveSongInPlaylistById(&g_playlist, added_id, to);
|
||||
result = playlist_move_id(&g_playlist, added_id, to);
|
||||
if (result != PLAYLIST_RESULT_SUCCESS) {
|
||||
enum command_return ret =
|
||||
print_playlist_result(client, result);
|
||||
deleteFromPlaylistById(&g_playlist, added_id);
|
||||
playlist_delete_id(&g_playlist, added_id);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -631,13 +680,13 @@ handle_addid(struct client *client, int argc, char *argv[])
|
||||
static enum command_return
|
||||
handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
int song;
|
||||
unsigned start, end;
|
||||
enum playlist_result result;
|
||||
|
||||
if (!check_int(client, &song, argv[1], need_positive))
|
||||
if (!check_range(client, &start, &end, argv[1], need_range))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
|
||||
result = deleteFromPlaylist(&g_playlist, song);
|
||||
result = playlist_delete_range(&g_playlist, start, end);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -650,7 +699,7 @@ handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
if (!check_int(client, &id, argv[1], need_positive))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
|
||||
result = deleteFromPlaylistById(&g_playlist, id);
|
||||
result = playlist_delete_id(&g_playlist, id);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -671,7 +720,7 @@ handle_shuffle(G_GNUC_UNUSED struct client *client,
|
||||
argv[1], need_range))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
|
||||
shufflePlaylist(&g_playlist, start, end);
|
||||
playlist_shuffle(&g_playlist, start, end);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -679,7 +728,7 @@ static enum command_return
|
||||
handle_clear(G_GNUC_UNUSED struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
{
|
||||
clearPlaylist(&g_playlist);
|
||||
playlist_clear(&g_playlist);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -698,6 +747,10 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
enum playlist_result result;
|
||||
|
||||
result = playlist_open_into_queue(argv[1], &g_playlist);
|
||||
if (result != PLAYLIST_RESULT_NO_SUCH_LIST)
|
||||
return result;
|
||||
|
||||
result = playlist_load_spl(&g_playlist, argv[1]);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
@@ -705,6 +758,9 @@ handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
static enum command_return
|
||||
handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
if (playlist_file_print(client, argv[1], false))
|
||||
return COMMAND_RETURN_OK;
|
||||
|
||||
bool ret;
|
||||
|
||||
ret = spl_print(client, argv[1], false);
|
||||
@@ -720,6 +776,9 @@ static enum command_return
|
||||
handle_listplaylistinfo(struct client *client,
|
||||
G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
if (playlist_file_print(client, argv[1], true))
|
||||
return COMMAND_RETURN_OK;
|
||||
|
||||
bool ret;
|
||||
|
||||
ret = spl_print(client, argv[1], true);
|
||||
@@ -808,7 +867,7 @@ handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[
|
||||
static enum command_return
|
||||
handle_playlistinfo(struct client *client, int argc, char *argv[])
|
||||
{
|
||||
unsigned start = 0, end = UINT_MAX;
|
||||
unsigned start = 0, end = G_MAXUINT;
|
||||
bool ret;
|
||||
|
||||
if (argc == 2 && !check_range(client, &start, &end,
|
||||
@@ -837,7 +896,7 @@ handle_playlistid(struct client *client, int argc, char *argv[])
|
||||
return print_playlist_result(client,
|
||||
PLAYLIST_RESULT_NO_SUCH_SONG);
|
||||
} else {
|
||||
playlist_print_info(client, &g_playlist, 0, UINT_MAX);
|
||||
playlist_print_info(client, &g_playlist, 0, G_MAXUINT);
|
||||
}
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
@@ -868,6 +927,30 @@ handle_find(struct client *client, int argc, char *argv[])
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_findadd(struct client *client, int argc, char *argv[])
|
||||
{
|
||||
int ret;
|
||||
struct locate_item_list *list =
|
||||
locate_item_list_parse(argv + 1, argc - 1);
|
||||
if (list == NULL || list->length == 0) {
|
||||
if (list != NULL)
|
||||
locate_item_list_free(list);
|
||||
|
||||
command_error(client, ACK_ERROR_ARG, "incorrect arguments");
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
ret = findAddIn(client, NULL, list);
|
||||
if (ret == -1)
|
||||
command_error(client, ACK_ERROR_NO_EXIST,
|
||||
"directory or file not found");
|
||||
|
||||
locate_item_list_free(list);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_search(struct client *client, int argc, char *argv[])
|
||||
{
|
||||
@@ -993,14 +1076,52 @@ handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
static enum command_return
|
||||
handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
char *path = NULL;
|
||||
const char *path = NULL;
|
||||
unsigned ret;
|
||||
|
||||
assert(argc <= 2);
|
||||
if (argc == 2)
|
||||
path = g_strdup(argv[1]);
|
||||
if (argc == 2) {
|
||||
path = argv[1];
|
||||
|
||||
ret = directory_update_init(path);
|
||||
if (*path == 0 || strcmp(path, "/") == 0)
|
||||
/* backwards compatibility with MPD 0.15 */
|
||||
path = NULL;
|
||||
else if (!uri_safe_local(path)) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Malformed path");
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ret = update_enqueue(path, false);
|
||||
if (ret > 0) {
|
||||
client_printf(client, "updating_db: %i\n", ret);
|
||||
return COMMAND_RETURN_OK;
|
||||
} else {
|
||||
command_error(client, ACK_ERROR_UPDATE_ALREADY,
|
||||
"already updating");
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_rescan(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
const char *path = NULL;
|
||||
unsigned ret;
|
||||
|
||||
assert(argc <= 2);
|
||||
if (argc == 2) {
|
||||
path = argv[1];
|
||||
|
||||
if (!uri_safe_local(path)) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Malformed path");
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
ret = update_enqueue(path, true);
|
||||
if (ret > 0) {
|
||||
client_printf(client, "updating_db: %i\n", ret);
|
||||
return COMMAND_RETURN_OK;
|
||||
@@ -1020,7 +1141,7 @@ handle_next(G_GNUC_UNUSED struct client *client,
|
||||
int single = g_playlist.queue.single;
|
||||
g_playlist.queue.single = false;
|
||||
|
||||
nextSongInPlaylist(&g_playlist);
|
||||
playlist_next(&g_playlist);
|
||||
|
||||
g_playlist.queue.single = single;
|
||||
return COMMAND_RETURN_OK;
|
||||
@@ -1030,7 +1151,7 @@ static enum command_return
|
||||
handle_previous(G_GNUC_UNUSED struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
{
|
||||
previousSongInPlaylist(&g_playlist);
|
||||
playlist_previous(&g_playlist);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -1051,25 +1172,6 @@ handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
int change;
|
||||
bool success;
|
||||
|
||||
if (!check_int(client, &change, argv[1], need_integer))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
|
||||
success = volume_level_change(change, true);
|
||||
if (!success) {
|
||||
command_error(client, ACK_ERROR_SYSTEM,
|
||||
"problems setting volume");
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
@@ -1079,7 +1181,12 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
if (!check_int(client, &level, argv[1], need_integer))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
|
||||
success = volume_level_change(level, 0);
|
||||
if (level < 0 || level > 100) {
|
||||
command_error(client, ACK_ERROR_ARG, "Invalid volume value");
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
success = volume_level_change(level);
|
||||
if (!success) {
|
||||
command_error(client, ACK_ERROR_SYSTEM,
|
||||
"problems setting volume");
|
||||
@@ -1103,7 +1210,7 @@ handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
setPlaylistRepeatStatus(&g_playlist, status);
|
||||
playlist_set_repeat(&g_playlist, status);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -1121,7 +1228,7 @@ handle_single(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
setPlaylistSingleStatus(&g_playlist, status);
|
||||
playlist_set_single(&g_playlist, status);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -1139,7 +1246,7 @@ handle_consume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
setPlaylistConsumeStatus(&g_playlist, status);
|
||||
playlist_set_consume(&g_playlist, status);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -1157,7 +1264,7 @@ handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
setPlaylistRandomStatus(&g_playlist, status);
|
||||
playlist_set_random(&g_playlist, status);
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -1172,7 +1279,7 @@ static enum command_return
|
||||
handle_clearerror(G_GNUC_UNUSED struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
{
|
||||
clearPlayerError();
|
||||
pc_clear_error();
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
@@ -1196,17 +1303,17 @@ handle_list(struct client *client, int argc, char *argv[])
|
||||
|
||||
/* for compatibility with < 0.12.0 */
|
||||
if (argc == 3) {
|
||||
if (tagType != TAG_ITEM_ALBUM) {
|
||||
if (tagType != TAG_ALBUM) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"should be \"%s\" for 3 arguments",
|
||||
tag_item_names[TAG_ITEM_ALBUM]);
|
||||
tag_item_names[TAG_ALBUM]);
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
locate_item_list_parse(argv + 1, argc - 1);
|
||||
|
||||
conditionals = locate_item_list_new(1);
|
||||
conditionals->items[0].tag = TAG_ITEM_ARTIST;
|
||||
conditionals->items[0].tag = TAG_ARTIST;
|
||||
conditionals->items[0].needle = g_strdup(argv[2]);
|
||||
} else {
|
||||
conditionals =
|
||||
@@ -1241,7 +1348,7 @@ handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
result = moveSongRangeInPlaylist(&g_playlist, start, end, to);
|
||||
result = playlist_move_range(&g_playlist, start, end, to);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -1255,7 +1362,7 @@ handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
if (!check_int(client, &to, argv[2], check_integer, argv[2]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
result = moveSongInPlaylistById(&g_playlist, id, to);
|
||||
result = playlist_move_id(&g_playlist, id, to);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -1269,7 +1376,7 @@ handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
if (!check_int(client, &song2, argv[2], check_integer, argv[2]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
result = swapSongsInPlaylist(&g_playlist, song1, song2);
|
||||
result = playlist_swap_songs(&g_playlist, song1, song2);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -1283,7 +1390,7 @@ handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
return COMMAND_RETURN_ERROR;
|
||||
if (!check_int(client, &id2, argv[2], check_integer, argv[2]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
result = swapSongsInPlaylistById(&g_playlist, id1, id2);
|
||||
result = playlist_swap_songs_id(&g_playlist, id1, id2);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -1298,7 +1405,7 @@ handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
|
||||
result = seekSongInPlaylist(&g_playlist, song, seek_time);
|
||||
result = playlist_seek_song(&g_playlist, song, seek_time);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -1313,7 +1420,7 @@ handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
|
||||
result = seekSongInPlaylistById(&g_playlist, id, seek_time);
|
||||
result = playlist_seek_song_id(&g_playlist, id, seek_time);
|
||||
return print_playlist_result(client, result);
|
||||
}
|
||||
|
||||
@@ -1363,7 +1470,31 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
|
||||
if (!check_unsigned(client, &xfade_time, argv[1]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
setPlayerCrossFade(xfade_time);
|
||||
pc_set_cross_fade(xfade_time);
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
float db;
|
||||
|
||||
if (!check_float(client, &db, argv[1]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
pc_set_mixramp_db(db);
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
float delay_secs;
|
||||
|
||||
if (!check_float(client, &delay_secs, argv[1]))
|
||||
return COMMAND_RETURN_ERROR;
|
||||
pc_set_mixramp_delay(delay_secs);
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
@@ -1476,6 +1607,28 @@ handle_listplaylists(struct client *client,
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_replay_gain_mode(struct client *client,
|
||||
G_GNUC_UNUSED int argc, char *argv[])
|
||||
{
|
||||
if (!replay_gain_set_mode_string(argv[1])) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"Unrecognized replay gain mode");
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_replay_gain_status(struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
{
|
||||
client_printf(client, "replay_gain_mode: %s\n",
|
||||
replay_gain_get_mode_string());
|
||||
return COMMAND_RETURN_OK;
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_idle(struct client *client,
|
||||
G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
|
||||
@@ -1519,13 +1672,14 @@ sticker_song_find_print_cb(struct song *song, const char *value,
|
||||
{
|
||||
struct sticker_song_find_data *data = user_data;
|
||||
|
||||
song_print_url(data->client, song);
|
||||
song_print_uri(data->client, song);
|
||||
sticker_print_value(data->client, data->name, value);
|
||||
}
|
||||
|
||||
static enum command_return
|
||||
handle_sticker_song(struct client *client, int argc, char *argv[])
|
||||
{
|
||||
/* get song song_id key */
|
||||
if (argc == 5 && strcmp(argv[1], "get") == 0) {
|
||||
struct song *song;
|
||||
char *value;
|
||||
@@ -1548,6 +1702,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
|
||||
g_free(value);
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
/* list song song_id */
|
||||
} else if (argc == 4 && strcmp(argv[1], "list") == 0) {
|
||||
struct song *song;
|
||||
struct sticker *sticker;
|
||||
@@ -1570,6 +1725,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
|
||||
sticker_free(sticker);
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
/* set song song_id id key */
|
||||
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
|
||||
struct song *song;
|
||||
bool ret;
|
||||
@@ -1589,6 +1745,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
|
||||
}
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
/* delete song song_id [key] */
|
||||
} else if ((argc == 4 || argc == 5) &&
|
||||
strcmp(argv[1], "delete") == 0) {
|
||||
struct song *song;
|
||||
@@ -1611,6 +1768,7 @@ handle_sticker_song(struct client *client, int argc, char *argv[])
|
||||
}
|
||||
|
||||
return COMMAND_RETURN_OK;
|
||||
/* find song dir key */
|
||||
} else if (argc == 5 && strcmp(argv[1], "find") == 0) {
|
||||
/* "sticker find song a/directory name" */
|
||||
struct directory *directory;
|
||||
@@ -1679,11 +1837,13 @@ static const struct command commands[] = {
|
||||
{ "count", PERMISSION_READ, 2, -1, handle_count },
|
||||
{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
|
||||
{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
|
||||
{ "decoders", PERMISSION_READ, 0, 0, handle_decoders },
|
||||
{ "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
|
||||
{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
|
||||
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
|
||||
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
|
||||
{ "find", PERMISSION_READ, 2, -1, handle_find },
|
||||
{ "findadd", PERMISSION_READ, 2, -1, handle_findadd},
|
||||
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
|
||||
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
|
||||
{ "list", PERMISSION_READ, 1, -1, handle_list },
|
||||
@@ -1694,6 +1854,8 @@ static const struct command commands[] = {
|
||||
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
|
||||
{ "load", PERMISSION_ADD, 1, 1, handle_load },
|
||||
{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
|
||||
{ "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
|
||||
{ "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
|
||||
{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
|
||||
{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
|
||||
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
|
||||
@@ -1719,6 +1881,11 @@ static const struct command commands[] = {
|
||||
{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
|
||||
{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
|
||||
{ "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
|
||||
{ "replay_gain_mode", PERMISSION_CONTROL, 1, 1,
|
||||
handle_replay_gain_mode },
|
||||
{ "replay_gain_status", PERMISSION_READ, 0, 0,
|
||||
handle_replay_gain_status },
|
||||
{ "rescan", PERMISSION_ADMIN, 0, 1, handle_rescan },
|
||||
{ "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
|
||||
{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
|
||||
{ "search", PERMISSION_READ, 2, -1, handle_search },
|
||||
@@ -1738,7 +1905,6 @@ static const struct command commands[] = {
|
||||
{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
|
||||
{ "update", PERMISSION_ADMIN, 0, 1, handle_update },
|
||||
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
|
||||
{ "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
|
||||
};
|
||||
|
||||
static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
|
||||
@@ -1892,15 +2058,63 @@ command_checked_lookup(struct client *client, unsigned permission,
|
||||
}
|
||||
|
||||
enum command_return
|
||||
command_process(struct client *client, char *commandString)
|
||||
command_process(struct client *client, unsigned num, char *line)
|
||||
{
|
||||
GError *error = NULL;
|
||||
int argc;
|
||||
char *argv[COMMAND_ARGV_MAX] = { NULL };
|
||||
const struct command *cmd;
|
||||
enum command_return ret = COMMAND_RETURN_ERROR;
|
||||
|
||||
if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX)))
|
||||
return COMMAND_RETURN_OK;
|
||||
command_list_num = num;
|
||||
|
||||
/* get the command name (first word on the line) */
|
||||
|
||||
argv[0] = tokenizer_next_word(&line, &error);
|
||||
if (argv[0] == NULL) {
|
||||
current_command = "";
|
||||
if (*line == 0)
|
||||
command_error(client, ACK_ERROR_UNKNOWN,
|
||||
"No command given");
|
||||
else {
|
||||
command_error(client, ACK_ERROR_UNKNOWN,
|
||||
"%s", error->message);
|
||||
g_error_free(error);
|
||||
}
|
||||
current_command = NULL;
|
||||
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
argc = 1;
|
||||
|
||||
/* now parse the arguments (quoted or unquoted) */
|
||||
|
||||
while (argc < (int)G_N_ELEMENTS(argv) &&
|
||||
(argv[argc] =
|
||||
tokenizer_next_param(&line, &error)) != NULL)
|
||||
++argc;
|
||||
|
||||
/* some error checks; we have to set current_command because
|
||||
command_error() expects it to be set */
|
||||
|
||||
current_command = argv[0];
|
||||
|
||||
if (argc >= (int)G_N_ELEMENTS(argv)) {
|
||||
command_error(client, ACK_ERROR_ARG, "Too many arguments");
|
||||
current_command = NULL;
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
if (*line != 0) {
|
||||
command_error(client, ACK_ERROR_ARG,
|
||||
"%s", error->message);
|
||||
current_command = NULL;
|
||||
g_error_free(error);
|
||||
return COMMAND_RETURN_ERROR;
|
||||
}
|
||||
|
||||
/* look up and invoke the command handler */
|
||||
|
||||
cmd = command_checked_lookup(client, client_get_permission(client),
|
||||
argc, argv);
|
||||
@@ -1908,32 +2122,7 @@ command_process(struct client *client, char *commandString)
|
||||
ret = cmd->handler(client, argc, argv);
|
||||
|
||||
current_command = NULL;
|
||||
command_list_num = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum command_return
|
||||
command_process_list(struct client *client,
|
||||
bool list_ok, GSList *list)
|
||||
{
|
||||
enum command_return ret = COMMAND_RETURN_OK;
|
||||
|
||||
command_list_num = 0;
|
||||
|
||||
for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
|
||||
char *cmd = cur->data;
|
||||
|
||||
g_debug("command_process_list: process command \"%s\"",
|
||||
cmd);
|
||||
ret = command_process(client, cmd);
|
||||
g_debug("command_process_list: command returned %i", ret);
|
||||
if (ret != COMMAND_RETURN_OK || client_is_expired(client))
|
||||
break;
|
||||
else if (list_ok)
|
||||
client_puts(client, "list_OK\n");
|
||||
command_list_num++;
|
||||
}
|
||||
|
||||
command_list_num = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -39,11 +39,7 @@ void command_init(void);
|
||||
void command_finish(void);
|
||||
|
||||
enum command_return
|
||||
command_process_list(struct client *client,
|
||||
bool list_ok, GSList *list);
|
||||
|
||||
enum command_return
|
||||
command_process(struct client *client, char *commandString);
|
||||
command_process(struct client *client, unsigned num, char *line);
|
||||
|
||||
void command_success(struct client *client);
|
||||
|
||||
|
||||
410
src/compress.c
410
src/compress.c
@@ -1,410 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
|
||||
*/
|
||||
|
||||
#include "compress.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef USE_X
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/Xutil.h>
|
||||
|
||||
static Display *display;
|
||||
static Window window;
|
||||
static Visual *visual;
|
||||
static int screen;
|
||||
static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC;
|
||||
#endif
|
||||
|
||||
static int *peaks;
|
||||
static int gainCurrent, gainTarget;
|
||||
|
||||
static struct {
|
||||
int show_mon;
|
||||
int anticlip;
|
||||
int target;
|
||||
int gainmax;
|
||||
int gainsmooth;
|
||||
unsigned buckets;
|
||||
} prefs;
|
||||
|
||||
#ifdef USE_X
|
||||
static int mon_init;
|
||||
#endif
|
||||
|
||||
void CompressCfg(int show_mon, int anticlip, int target, int gainmax,
|
||||
int gainsmooth, unsigned buckets)
|
||||
{
|
||||
static unsigned lastsize;
|
||||
|
||||
prefs.show_mon = show_mon;
|
||||
prefs.anticlip = anticlip;
|
||||
prefs.target = target;
|
||||
prefs.gainmax = gainmax;
|
||||
prefs.gainsmooth = gainsmooth;
|
||||
prefs.buckets = buckets;
|
||||
|
||||
/* Allocate the peak structure */
|
||||
peaks = g_realloc(peaks, sizeof(int)*prefs.buckets);
|
||||
|
||||
if (prefs.buckets > lastsize)
|
||||
memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets
|
||||
- lastsize));
|
||||
lastsize = prefs.buckets;
|
||||
|
||||
#ifdef USE_X
|
||||
/* Configure the monitor window if needed */
|
||||
if (show_mon && !mon_init)
|
||||
{
|
||||
display = XOpenDisplay(getenv("DISPLAY"));
|
||||
|
||||
/* We really shouldn't try to init X if there's no X */
|
||||
if (!display)
|
||||
{
|
||||
fprintf(stderr,
|
||||
"X not detected; disabling monitor window\n");
|
||||
show_mon = prefs.show_mon = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (show_mon && !mon_init)
|
||||
{
|
||||
XGCValues gcv;
|
||||
XColor col;
|
||||
|
||||
gainCurrent = gainTarget = (1 << GAINSHIFT);
|
||||
|
||||
|
||||
|
||||
screen = DefaultScreen(display);
|
||||
visual = DefaultVisual(display, screen);
|
||||
window = XCreateSimpleWindow(display,
|
||||
RootWindow(display, screen),
|
||||
0, 0, prefs.buckets, 128 + 8, 0,
|
||||
BlackPixel(display, screen),
|
||||
WhitePixel(display, screen));
|
||||
XStoreName(display, window, "AudioCompress monitor");
|
||||
|
||||
gcv.foreground = BlackPixel(display, screen);
|
||||
blackGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||
gcv.foreground = WhitePixel(display, screen);
|
||||
whiteGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||
col.red = 0;
|
||||
col.green = 0;
|
||||
col.blue = 65535;
|
||||
XAllocColor(display, DefaultColormap(display, screen), &col);
|
||||
gcv.foreground = col.pixel;
|
||||
blueGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||
col.red = 65535;
|
||||
col.green = 65535;
|
||||
col.blue = 0;
|
||||
XAllocColor(display, DefaultColormap(display, screen), &col);
|
||||
gcv.foreground = col.pixel;
|
||||
yellowGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||
col.red = 32767;
|
||||
col.green = 32767;
|
||||
col.blue = 0;
|
||||
XAllocColor(display, DefaultColormap(display, screen), &col);
|
||||
gcv.foreground = col.pixel;
|
||||
dkyellowGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||
col.red = 65535;
|
||||
col.green = 0;
|
||||
col.blue = 0;
|
||||
XAllocColor(display, DefaultColormap(display, screen), &col);
|
||||
gcv.foreground = col.pixel;
|
||||
redGC = XCreateGC(display, window, GCForeground, &gcv);
|
||||
mon_init = 1;
|
||||
}
|
||||
|
||||
if (mon_init)
|
||||
{
|
||||
if (show_mon)
|
||||
XMapWindow(display, window);
|
||||
else
|
||||
XUnmapWindow(display, window);
|
||||
XResizeWindow(display, window, prefs.buckets, 128 + 8);
|
||||
XFlush(display);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void CompressFree(void)
|
||||
{
|
||||
#ifdef USE_X
|
||||
if (mon_init)
|
||||
{
|
||||
XFreeGC(display, blackGC);
|
||||
XFreeGC(display, whiteGC);
|
||||
XFreeGC(display, blueGC);
|
||||
XFreeGC(display, yellowGC);
|
||||
XFreeGC(display, dkyellowGC);
|
||||
XFreeGC(display, redGC);
|
||||
XDestroyWindow(display, window);
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
#endif
|
||||
|
||||
g_free(peaks);
|
||||
}
|
||||
|
||||
void CompressDo(void *data, unsigned int length)
|
||||
{
|
||||
int16_t *audio = (int16_t *)data, *ap;
|
||||
int peak;
|
||||
unsigned int i, pos;
|
||||
int gr, gf, gn;
|
||||
static int pn = -1;
|
||||
#ifdef STATS
|
||||
static int clip;
|
||||
#endif
|
||||
static int clipped;
|
||||
|
||||
if (!peaks)
|
||||
return;
|
||||
|
||||
if (pn == -1)
|
||||
{
|
||||
for (i = 0; i < prefs.buckets; i++)
|
||||
peaks[i] = 0;
|
||||
}
|
||||
pn = (pn + 1)%prefs.buckets;
|
||||
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data,
|
||||
length);
|
||||
#endif
|
||||
|
||||
/* Determine peak's value and position */
|
||||
peak = 1;
|
||||
pos = 0;
|
||||
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "finding peak(b=%d)\n", pn);
|
||||
#endif
|
||||
|
||||
ap = audio;
|
||||
for (i = 0; i < length/2; i++)
|
||||
{
|
||||
int val = *ap;
|
||||
if (val > peak)
|
||||
{
|
||||
peak = val;
|
||||
pos = i;
|
||||
} else if (-val > peak)
|
||||
{
|
||||
peak = -val;
|
||||
pos = i;
|
||||
}
|
||||
ap++;
|
||||
}
|
||||
peaks[pn] = peak;
|
||||
|
||||
/* Only draw if needed, of course */
|
||||
#ifdef USE_X
|
||||
if (prefs.show_mon)
|
||||
{
|
||||
/* current amplitude */
|
||||
XDrawLine(display, window, whiteGC,
|
||||
pn, 0,
|
||||
pn,
|
||||
127 -
|
||||
(peaks[pn]*gainCurrent >> (GAINSHIFT + 8)));
|
||||
|
||||
/* amplification */
|
||||
XDrawLine(display, window, yellowGC,
|
||||
pn,
|
||||
127 - (peaks[pn]*gainCurrent
|
||||
>> (GAINSHIFT + 8)),
|
||||
pn, 127);
|
||||
|
||||
/* peak */
|
||||
XDrawLine(display, window, blackGC,
|
||||
pn, 127 - (peaks[pn] >> 8), pn, 127);
|
||||
|
||||
/* clip indicator */
|
||||
if (clipped)
|
||||
XDrawLine(display, window, redGC,
|
||||
(pn + prefs.buckets - 1)%prefs.buckets,
|
||||
126 - clipped/(length*512),
|
||||
(pn + prefs.buckets - 1)%prefs.buckets,
|
||||
127);
|
||||
clipped = 0;
|
||||
|
||||
/* target line */
|
||||
/* XDrawPoint(display, window, redGC, */
|
||||
/* pn, 127 - TARGET/256); */
|
||||
/* amplification edge */
|
||||
XDrawLine(display, window, dkyellowGC,
|
||||
pn,
|
||||
127 - (peaks[pn]*gainCurrent
|
||||
>> (GAINSHIFT + 8)),
|
||||
pn - 1,
|
||||
127 -
|
||||
(peaks[(pn + prefs.buckets
|
||||
- 1)%prefs.buckets]*gainCurrent
|
||||
>> (GAINSHIFT + 8)));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 0; i < prefs.buckets; i++)
|
||||
{
|
||||
if (peaks[i] > peak)
|
||||
{
|
||||
peak = peaks[i];
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine target gain */
|
||||
gn = (1 << GAINSHIFT)*prefs.target/peak;
|
||||
|
||||
if (gn <(1 << GAINSHIFT))
|
||||
gn = 1 << GAINSHIFT;
|
||||
|
||||
gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn)
|
||||
>> prefs.gainsmooth;
|
||||
|
||||
/* Give it an extra insignifigant nudge to counteract possible
|
||||
** rounding error
|
||||
*/
|
||||
|
||||
if (gn < gainTarget)
|
||||
gainTarget--;
|
||||
else if (gn > gainTarget)
|
||||
gainTarget++;
|
||||
|
||||
if (gainTarget > prefs.gainmax << GAINSHIFT)
|
||||
gainTarget = prefs.gainmax << GAINSHIFT;
|
||||
|
||||
|
||||
#ifdef USE_X
|
||||
if (prefs.show_mon)
|
||||
{
|
||||
int x;
|
||||
|
||||
/* peak*gain */
|
||||
XDrawPoint(display, window, redGC,
|
||||
pn,
|
||||
127 - (peak*gainCurrent
|
||||
>> (GAINSHIFT + 8)));
|
||||
|
||||
/* gain indicator */
|
||||
XFillRectangle(display, window, whiteGC, 0, 128,
|
||||
prefs.buckets, 8);
|
||||
x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets
|
||||
/ ((prefs.gainmax - 1) << GAINSHIFT);
|
||||
XDrawLine(display, window, redGC, x,
|
||||
128, x, 128 + 8);
|
||||
|
||||
x = (gn - (1 << GAINSHIFT))*prefs.buckets
|
||||
/ ((prefs.gainmax - 1) << GAINSHIFT);
|
||||
|
||||
XDrawLine(display, window, blackGC,
|
||||
x, 132 - 1,
|
||||
x, 132 + 1);
|
||||
|
||||
/* blue peak line */
|
||||
XDrawLine(display, window, blueGC,
|
||||
0, 127 - (peak >> 8), prefs.buckets,
|
||||
127 - (peak >> 8));
|
||||
XFlush(display);
|
||||
XDrawLine(display, window, whiteGC,
|
||||
0, 127 - (peak >> 8), prefs.buckets,
|
||||
127 - (peak >> 8));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* See if a peak is going to clip */
|
||||
gn = (1 << GAINSHIFT)*32768/peak;
|
||||
|
||||
if (gn < gainTarget)
|
||||
{
|
||||
gainTarget = gn;
|
||||
|
||||
if (prefs.anticlip)
|
||||
pos = 0;
|
||||
|
||||
} else
|
||||
{
|
||||
/* We're ramping up, so draw it out over the whole frame */
|
||||
pos = length;
|
||||
}
|
||||
|
||||
/* Determine gain rate necessary to make target */
|
||||
if (!pos)
|
||||
pos = 1;
|
||||
|
||||
gr = ((gainTarget - gainCurrent) << 16)/(int)pos;
|
||||
|
||||
/* Do the shiznit */
|
||||
gf = gainCurrent << 16;
|
||||
|
||||
#ifdef STATS
|
||||
fprintf(stderr, "\rgain = %2.2f%+.2e ",
|
||||
gainCurrent*1.0/(1 << GAINSHIFT),
|
||||
(gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT));
|
||||
#endif
|
||||
|
||||
ap = audio;
|
||||
for (i = 0; i < length/2; i++)
|
||||
{
|
||||
int sample;
|
||||
|
||||
/* Interpolate the gain */
|
||||
gainCurrent = gf >> 16;
|
||||
if (i < pos)
|
||||
gf += gr;
|
||||
else if (i == pos)
|
||||
gf = gainTarget << 16;
|
||||
|
||||
/* Amplify */
|
||||
sample = (*ap)*gainCurrent >> GAINSHIFT;
|
||||
if (sample < -32768)
|
||||
{
|
||||
#ifdef STATS
|
||||
clip++;
|
||||
#endif
|
||||
clipped += -32768 - sample;
|
||||
sample = -32768;
|
||||
} else if (sample > 32767)
|
||||
{
|
||||
#ifdef STATS
|
||||
clip++;
|
||||
#endif
|
||||
clipped += sample - 32767;
|
||||
sample = 32767;
|
||||
}
|
||||
*ap++ = sample;
|
||||
}
|
||||
#ifdef STATS
|
||||
fprintf(stderr, "clip %d b%-3d ", clip, pn);
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
fprintf(stderr, "\ndone\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Imported from AudioCompress by J. Shagam <fluffy@beesbuzz.biz>
|
||||
*/
|
||||
|
||||
#ifndef MPD_COMPRESS_H
|
||||
#define MPD_COMPRESS_H
|
||||
|
||||
/* These are copied from the AudioCompress config.h, mainly because CompressDo
|
||||
* needs GAINSHIFT defined. The rest are here so they can be used as defaults
|
||||
* to pass to CompressCfg(). -- jat */
|
||||
#define ANTICLIP 0 /* Strict clipping protection */
|
||||
#define TARGET 25000 /* Target level */
|
||||
#define GAINMAX 32 /* The maximum amount to amplify by */
|
||||
#define GAINSHIFT 10 /* How fine-grained the gain is */
|
||||
#define GAINSMOOTH 8 /* How much inertia ramping has*/
|
||||
#define BUCKETS 400 /* How long of a history to store */
|
||||
|
||||
void CompressCfg(int monitor,
|
||||
int anticlip,
|
||||
int target,
|
||||
int maxgain,
|
||||
int smooth,
|
||||
unsigned buckets);
|
||||
|
||||
void CompressDo(void *data, unsigned int numSamples);
|
||||
|
||||
void CompressFree(void);
|
||||
|
||||
#endif
|
||||
521
src/conf.c
521
src/conf.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,10 +17,12 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "conf.h"
|
||||
#include "utils.h"
|
||||
#include "buffer2array.h"
|
||||
#include "tokenizer.h"
|
||||
#include "path.h"
|
||||
#include "glib_compat.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -36,37 +38,84 @@
|
||||
#define MAX_STRING_SIZE MPD_PATH_MAX+80
|
||||
|
||||
#define CONF_COMMENT '#'
|
||||
#define CONF_BLOCK_BEGIN "{"
|
||||
#define CONF_BLOCK_END "}"
|
||||
|
||||
#define CONF_REPEATABLE_MASK 0x01
|
||||
#define CONF_BLOCK_MASK 0x02
|
||||
#define CONF_LINE_TOKEN_MAX 3
|
||||
|
||||
struct config_entry {
|
||||
const char *name;
|
||||
unsigned char mask;
|
||||
const char *const name;
|
||||
const bool repeatable;
|
||||
const bool block;
|
||||
|
||||
GSList *params;
|
||||
};
|
||||
|
||||
static GSList *config_entries;
|
||||
static struct config_entry config_entries[] = {
|
||||
{ .name = CONF_MUSIC_DIR, false, false },
|
||||
{ .name = CONF_PLAYLIST_DIR, false, false },
|
||||
{ .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false },
|
||||
{ .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false },
|
||||
{ .name = CONF_DB_FILE, false, false },
|
||||
{ .name = CONF_STICKER_FILE, false, false },
|
||||
{ .name = CONF_LOG_FILE, false, false },
|
||||
{ .name = CONF_PID_FILE, false, false },
|
||||
{ .name = CONF_STATE_FILE, false, false },
|
||||
{ .name = CONF_USER, false, false },
|
||||
{ .name = CONF_GROUP, false, false },
|
||||
{ .name = CONF_BIND_TO_ADDRESS, true, false },
|
||||
{ .name = CONF_PORT, false, false },
|
||||
{ .name = CONF_LOG_LEVEL, false, false },
|
||||
{ .name = CONF_ZEROCONF_NAME, false, false },
|
||||
{ .name = CONF_ZEROCONF_ENABLED, false, false },
|
||||
{ .name = CONF_PASSWORD, true, false },
|
||||
{ .name = CONF_DEFAULT_PERMS, false, false },
|
||||
{ .name = CONF_AUDIO_OUTPUT, true, true },
|
||||
{ .name = CONF_AUDIO_OUTPUT_FORMAT, false, false },
|
||||
{ .name = CONF_MIXER_TYPE, false, false },
|
||||
{ .name = CONF_REPLAYGAIN, false, false },
|
||||
{ .name = CONF_REPLAYGAIN_PREAMP, false, false },
|
||||
{ .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false },
|
||||
{ .name = CONF_REPLAYGAIN_LIMIT, false, false },
|
||||
{ .name = CONF_VOLUME_NORMALIZATION, false, false },
|
||||
{ .name = CONF_SAMPLERATE_CONVERTER, false, false },
|
||||
{ .name = CONF_AUDIO_BUFFER_SIZE, false, false },
|
||||
{ .name = CONF_BUFFER_BEFORE_PLAY, false, false },
|
||||
{ .name = CONF_HTTP_PROXY_HOST, false, false },
|
||||
{ .name = CONF_HTTP_PROXY_PORT, false, false },
|
||||
{ .name = CONF_HTTP_PROXY_USER, false, false },
|
||||
{ .name = CONF_HTTP_PROXY_PASSWORD, false, false },
|
||||
{ .name = CONF_CONN_TIMEOUT, false, false },
|
||||
{ .name = CONF_MAX_CONN, false, false },
|
||||
{ .name = CONF_MAX_PLAYLIST_LENGTH, false, false },
|
||||
{ .name = CONF_MAX_COMMAND_LIST_SIZE, false, false },
|
||||
{ .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false },
|
||||
{ .name = CONF_FS_CHARSET, false, false },
|
||||
{ .name = CONF_ID3V1_ENCODING, false, false },
|
||||
{ .name = CONF_METADATA_TO_USE, false, false },
|
||||
{ .name = CONF_SAVE_ABSOLUTE_PATHS, false, false },
|
||||
{ .name = CONF_DECODER, true, true },
|
||||
{ .name = CONF_INPUT, true, true },
|
||||
{ .name = CONF_GAPLESS_MP3_PLAYBACK, false, false },
|
||||
{ .name = CONF_PLAYLIST_PLUGIN, true, true },
|
||||
{ .name = CONF_AUTO_UPDATE, false, false },
|
||||
{ .name = CONF_AUTO_UPDATE_DEPTH, false, false },
|
||||
{ .name = "filter", true, true },
|
||||
};
|
||||
|
||||
static int get_bool(const char *value)
|
||||
static bool
|
||||
get_bool(const char *value, bool *value_r)
|
||||
{
|
||||
const char **x;
|
||||
static const char *t[] = { "yes", "true", "1", NULL };
|
||||
static const char *f[] = { "no", "false", "0", NULL };
|
||||
|
||||
for (x = t; *x; x++) {
|
||||
if (!g_ascii_strcasecmp(*x, value))
|
||||
return 1;
|
||||
if (string_array_contains(t, value)) {
|
||||
*value_r = true;
|
||||
return true;
|
||||
}
|
||||
for (x = f; *x; x++) {
|
||||
if (!g_ascii_strcasecmp(*x, value))
|
||||
return 0;
|
||||
|
||||
if (string_array_contains(f, value)) {
|
||||
*value_r = false;
|
||||
return true;
|
||||
}
|
||||
return CONF_BOOL_INVALID;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct config_param *
|
||||
@@ -83,15 +132,14 @@ config_new_param(const char *value, int line)
|
||||
|
||||
ret->num_block_params = 0;
|
||||
ret->block_params = NULL;
|
||||
ret->used = false;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
config_param_free(struct config_param *param)
|
||||
{
|
||||
struct config_param *param = data;
|
||||
|
||||
g_free(param->value);
|
||||
|
||||
for (unsigned i = 0; i < param->num_block_params; i++) {
|
||||
@@ -105,42 +153,19 @@ config_param_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
g_free(param);
|
||||
}
|
||||
|
||||
static struct config_entry *
|
||||
newConfigEntry(const char *name, int repeatable, int block)
|
||||
{
|
||||
struct config_entry *ret = g_new(struct config_entry, 1);
|
||||
|
||||
ret->name = name;
|
||||
ret->mask = 0;
|
||||
ret->params = NULL;
|
||||
|
||||
if (repeatable)
|
||||
ret->mask |= CONF_REPEATABLE_MASK;
|
||||
if (block)
|
||||
ret->mask |= CONF_BLOCK_MASK;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
config_entry_free(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
struct config_entry *entry = data;
|
||||
struct config_param *param = data;
|
||||
|
||||
g_slist_foreach(entry->params, config_param_free, NULL);
|
||||
g_slist_free(entry->params);
|
||||
|
||||
g_free(entry);
|
||||
config_param_free(param);
|
||||
}
|
||||
|
||||
static struct config_entry *
|
||||
config_entry_get(const char *name)
|
||||
{
|
||||
GSList *list;
|
||||
|
||||
for (list = config_entries; list != NULL;
|
||||
list = g_slist_next(list)) {
|
||||
struct config_entry *entry = list->data;
|
||||
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
|
||||
struct config_entry *entry = &config_entries[i];
|
||||
if (strcmp(entry->name, name) == 0)
|
||||
return entry;
|
||||
}
|
||||
@@ -148,82 +173,65 @@ config_entry_get(const char *name)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void registerConfigParam(const char *name, int repeatable, int block)
|
||||
{
|
||||
struct config_entry *entry;
|
||||
|
||||
entry = config_entry_get(name);
|
||||
if (entry != NULL)
|
||||
g_error("config parameter \"%s\" already registered\n", name);
|
||||
|
||||
entry = newConfigEntry(name, repeatable, block);
|
||||
config_entries = g_slist_prepend(config_entries, entry);
|
||||
}
|
||||
|
||||
void config_global_finish(void)
|
||||
{
|
||||
g_slist_foreach(config_entries, config_entry_free, NULL);
|
||||
g_slist_free(config_entries);
|
||||
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
|
||||
struct config_entry *entry = &config_entries[i];
|
||||
|
||||
g_slist_foreach(entry->params,
|
||||
config_param_free_callback, NULL);
|
||||
g_slist_free(entry->params);
|
||||
}
|
||||
}
|
||||
|
||||
void config_global_init(void)
|
||||
{
|
||||
config_entries = NULL;
|
||||
|
||||
/* registerConfigParam(name, repeatable, block); */
|
||||
registerConfigParam(CONF_MUSIC_DIR, 0, 0);
|
||||
registerConfigParam(CONF_PLAYLIST_DIR, 0, 0);
|
||||
registerConfigParam(CONF_FOLLOW_INSIDE_SYMLINKS, 0, 0);
|
||||
registerConfigParam(CONF_FOLLOW_OUTSIDE_SYMLINKS, 0, 0);
|
||||
registerConfigParam(CONF_DB_FILE, 0, 0);
|
||||
registerConfigParam(CONF_STICKER_FILE, false, false);
|
||||
registerConfigParam(CONF_LOG_FILE, 0, 0);
|
||||
registerConfigParam(CONF_ERROR_FILE, 0, 0);
|
||||
registerConfigParam(CONF_PID_FILE, 0, 0);
|
||||
registerConfigParam(CONF_STATE_FILE, 0, 0);
|
||||
registerConfigParam(CONF_USER, 0, 0);
|
||||
registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0);
|
||||
registerConfigParam(CONF_PORT, 0, 0);
|
||||
registerConfigParam(CONF_LOG_LEVEL, 0, 0);
|
||||
registerConfigParam(CONF_ZEROCONF_NAME, 0, 0);
|
||||
registerConfigParam(CONF_ZEROCONF_ENABLED, 0, 0);
|
||||
registerConfigParam(CONF_PASSWORD, 1, 0);
|
||||
registerConfigParam(CONF_DEFAULT_PERMS, 0, 0);
|
||||
registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1);
|
||||
registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0);
|
||||
registerConfigParam(CONF_MIXER_TYPE, 0, 0);
|
||||
registerConfigParam(CONF_MIXER_DEVICE, 0, 0);
|
||||
registerConfigParam(CONF_MIXER_CONTROL, 0, 0);
|
||||
registerConfigParam(CONF_REPLAYGAIN, 0, 0);
|
||||
registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0);
|
||||
registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0);
|
||||
registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0);
|
||||
registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0);
|
||||
registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0);
|
||||
registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0);
|
||||
registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0);
|
||||
registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0);
|
||||
registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0);
|
||||
registerConfigParam(CONF_CONN_TIMEOUT, 0, 0);
|
||||
registerConfigParam(CONF_MAX_CONN, 0, 0);
|
||||
registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0);
|
||||
registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0);
|
||||
registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0);
|
||||
registerConfigParam(CONF_FS_CHARSET, 0, 0);
|
||||
registerConfigParam(CONF_ID3V1_ENCODING, 0, 0);
|
||||
registerConfigParam(CONF_METADATA_TO_USE, 0, 0);
|
||||
registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0);
|
||||
registerConfigParam(CONF_DECODER, true, true);
|
||||
registerConfigParam(CONF_INPUT, true, true);
|
||||
registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0);
|
||||
}
|
||||
|
||||
void
|
||||
static void
|
||||
config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data)
|
||||
{
|
||||
struct config_param *param = data;
|
||||
|
||||
if (!param->used)
|
||||
/* this whole config_param was not queried at all -
|
||||
the feature might be disabled at compile time?
|
||||
Silently ignore it here. */
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < param->num_block_params; i++) {
|
||||
struct block_param *bp = ¶m->block_params[i];
|
||||
|
||||
if (!bp->used)
|
||||
g_warning("option '%s' on line %i was not recognized",
|
||||
bp->name, bp->line);
|
||||
}
|
||||
}
|
||||
|
||||
void config_global_check(void)
|
||||
{
|
||||
for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
|
||||
struct config_entry *entry = &config_entries[i];
|
||||
|
||||
g_slist_foreach(entry->params, config_param_check, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
config_add_block_param(struct config_param * param, const char *name,
|
||||
const char *value, int line)
|
||||
const char *value, int line, GError **error_r)
|
||||
{
|
||||
struct block_param *bp;
|
||||
|
||||
bp = config_get_block_param(param, name);
|
||||
if (bp != NULL) {
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"\"%s\" first defined on line %i, and "
|
||||
"redefined on line %i\n", name,
|
||||
bp->line, line);
|
||||
return false;
|
||||
}
|
||||
|
||||
param->num_block_params++;
|
||||
|
||||
param->block_params = g_realloc(param->block_params,
|
||||
@@ -235,67 +243,97 @@ config_add_block_param(struct config_param * param, const char *name,
|
||||
bp->name = g_strdup(name);
|
||||
bp->value = g_strdup(value);
|
||||
bp->line = line;
|
||||
bp->used = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static struct config_param *
|
||||
config_read_block(FILE *fp, int *count, char *string)
|
||||
config_read_block(FILE *fp, int *count, char *string, GError **error_r)
|
||||
{
|
||||
struct config_param *ret = config_new_param(NULL, *count);
|
||||
GError *error = NULL;
|
||||
bool success;
|
||||
|
||||
int i;
|
||||
int numberOfArgs;
|
||||
int argsMinusComment;
|
||||
while (true) {
|
||||
char *line;
|
||||
const char *name, *value;
|
||||
|
||||
while (fgets(string, MAX_STRING_SIZE, fp)) {
|
||||
char *array[CONF_LINE_TOKEN_MAX] = { NULL };
|
||||
line = fgets(string, MAX_STRING_SIZE, fp);
|
||||
if (line == NULL) {
|
||||
config_param_free(ret);
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"Expected '}' before end-of-file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
(*count)++;
|
||||
|
||||
numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
|
||||
|
||||
for (i = 0; i < numberOfArgs; i++) {
|
||||
if (array[i][0] == CONF_COMMENT)
|
||||
break;
|
||||
}
|
||||
|
||||
argsMinusComment = i;
|
||||
|
||||
if (0 == argsMinusComment) {
|
||||
line = g_strchug(line);
|
||||
if (*line == 0 || *line == CONF_COMMENT)
|
||||
continue;
|
||||
|
||||
if (*line == '}') {
|
||||
/* end of this block; return from the function
|
||||
(and from this "while" loop) */
|
||||
|
||||
line = g_strchug(line + 1);
|
||||
if (*line != 0 && *line != CONF_COMMENT) {
|
||||
config_param_free(ret);
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"line %i: Unknown tokens after '}'",
|
||||
*count);
|
||||
return false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (1 == argsMinusComment &&
|
||||
0 == strcmp(array[0], CONF_BLOCK_END)) {
|
||||
break;
|
||||
/* parse name and value */
|
||||
|
||||
name = tokenizer_next_word(&line, &error);
|
||||
if (name == NULL) {
|
||||
assert(*line != 0);
|
||||
config_param_free(ret);
|
||||
g_propagate_prefixed_error(error_r, error,
|
||||
"line %i: ", *count);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (2 != argsMinusComment) {
|
||||
g_error("improperly formatted config file at line %i:"
|
||||
" %s\n", *count, string);
|
||||
value = tokenizer_next_string(&line, &error);
|
||||
if (value == NULL) {
|
||||
config_param_free(ret);
|
||||
if (*line == 0)
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"line %i: Value missing", *count);
|
||||
else
|
||||
g_propagate_prefixed_error(error_r, error,
|
||||
"line %i: ",
|
||||
*count);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) ||
|
||||
0 == strcmp(array[1], CONF_BLOCK_BEGIN) ||
|
||||
0 == strcmp(array[0], CONF_BLOCK_END) ||
|
||||
0 == strcmp(array[1], CONF_BLOCK_END)) {
|
||||
g_error("improperly formatted config file at line %i: %s "
|
||||
"in block beginning at line %i\n",
|
||||
*count, string, ret->line);
|
||||
if (*line != 0 && *line != CONF_COMMENT) {
|
||||
config_param_free(ret);
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"line %i: Unknown tokens after value",
|
||||
*count);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
config_add_block_param(ret, array[0], array[1], *count);
|
||||
success = config_add_block_param(ret, name, value, *count,
|
||||
error_r);
|
||||
if (!success) {
|
||||
config_param_free(ret);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void config_read_file(const char *file)
|
||||
bool
|
||||
config_read_file(const char *file, GError **error_r)
|
||||
{
|
||||
FILE *fp;
|
||||
char string[MAX_STRING_SIZE + 1];
|
||||
int i;
|
||||
int numberOfArgs;
|
||||
int argsMinusComment;
|
||||
int count = 0;
|
||||
struct config_entry *entry;
|
||||
struct config_param *param;
|
||||
@@ -303,67 +341,110 @@ void config_read_file(const char *file)
|
||||
g_debug("loading file %s", file);
|
||||
|
||||
if (!(fp = fopen(file, "r"))) {
|
||||
g_error("problems opening file %s for reading: %s\n",
|
||||
file, strerror(errno));
|
||||
g_set_error(error_r, config_quark(), errno,
|
||||
"Failed to open %s: %s",
|
||||
file, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
while (fgets(string, MAX_STRING_SIZE, fp)) {
|
||||
char *array[CONF_LINE_TOKEN_MAX] = { NULL };
|
||||
char *line;
|
||||
const char *name, *value;
|
||||
GError *error = NULL;
|
||||
|
||||
count++;
|
||||
|
||||
numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX);
|
||||
|
||||
for (i = 0; i < numberOfArgs; i++) {
|
||||
if (array[i][0] == CONF_COMMENT)
|
||||
break;
|
||||
}
|
||||
|
||||
argsMinusComment = i;
|
||||
|
||||
if (0 == argsMinusComment) {
|
||||
line = g_strchug(string);
|
||||
if (*line == 0 || *line == CONF_COMMENT)
|
||||
continue;
|
||||
|
||||
/* the first token in each line is the name, followed
|
||||
by either the value or '{' */
|
||||
|
||||
name = tokenizer_next_word(&line, &error);
|
||||
if (name == NULL) {
|
||||
assert(*line != 0);
|
||||
g_propagate_prefixed_error(error_r, error,
|
||||
"line %i: ", count);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (2 != argsMinusComment) {
|
||||
g_error("improperly formatted config file at line %i:"
|
||||
" %s\n", count, string);
|
||||
/* get the definition of that option, and check the
|
||||
"repeatable" flag */
|
||||
|
||||
entry = config_entry_get(name);
|
||||
if (entry == NULL) {
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"unrecognized parameter in config file at "
|
||||
"line %i: %s\n", count, name);
|
||||
return false;
|
||||
}
|
||||
|
||||
entry = config_entry_get(array[0]);
|
||||
if (entry == NULL)
|
||||
g_error("unrecognized parameter in config file at "
|
||||
"line %i: %s\n", count, string);
|
||||
|
||||
if (!(entry->mask & CONF_REPEATABLE_MASK) &&
|
||||
entry->params != NULL) {
|
||||
if (entry->params != NULL && !entry->repeatable) {
|
||||
param = entry->params->data;
|
||||
g_error("config parameter \"%s\" is first defined on "
|
||||
"line %i and redefined on line %i\n",
|
||||
array[0], param->line, count);
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"config parameter \"%s\" is first defined "
|
||||
"on line %i and redefined on line %i\n",
|
||||
name, param->line, count);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry->mask & CONF_BLOCK_MASK) {
|
||||
if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) {
|
||||
g_error("improperly formatted config file at "
|
||||
"line %i: %s\n", count, string);
|
||||
/* now parse the block or the value */
|
||||
|
||||
if (entry->block) {
|
||||
/* it's a block, call config_read_block() */
|
||||
|
||||
if (*line != '{') {
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"line %i: '{' expected", count);
|
||||
return false;
|
||||
}
|
||||
param = config_read_block(fp, &count, string);
|
||||
} else
|
||||
param = config_new_param(array[1], count);
|
||||
|
||||
line = g_strchug(line + 1);
|
||||
if (*line != 0 && *line != CONF_COMMENT) {
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"line %i: Unknown tokens after '{'",
|
||||
count);
|
||||
return false;
|
||||
}
|
||||
|
||||
param = config_read_block(fp, &count, string, error_r);
|
||||
if (param == NULL)
|
||||
return false;
|
||||
} else {
|
||||
/* a string value */
|
||||
|
||||
value = tokenizer_next_string(&line, &error);
|
||||
if (value == NULL) {
|
||||
if (*line == 0)
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"line %i: Value missing",
|
||||
count);
|
||||
else {
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"line %i: %s", count,
|
||||
error->message);
|
||||
g_error_free(error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*line != 0 && *line != CONF_COMMENT) {
|
||||
g_set_error(error_r, config_quark(), 0,
|
||||
"line %i: Unknown tokens after value",
|
||||
count);
|
||||
return false;
|
||||
}
|
||||
|
||||
param = config_new_param(value, count);
|
||||
}
|
||||
|
||||
entry->params = g_slist_append(entry->params, param);
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
void
|
||||
config_add_param(const char *name, struct config_param *param)
|
||||
{
|
||||
struct config_entry *entry = config_entry_get(name);
|
||||
assert(entry != NULL);
|
||||
|
||||
entry->params = g_slist_append(entry->params, param);
|
||||
return true;
|
||||
}
|
||||
|
||||
struct config_param *
|
||||
@@ -391,7 +472,7 @@ config_get_next_param(const char *name, const struct config_param * last)
|
||||
return NULL;
|
||||
|
||||
param = node->data;
|
||||
|
||||
param->used = true;
|
||||
return param;
|
||||
}
|
||||
|
||||
@@ -424,6 +505,23 @@ config_get_path(const char *name)
|
||||
return param->value = path;
|
||||
}
|
||||
|
||||
unsigned
|
||||
config_get_unsigned(const char *name, unsigned default_value)
|
||||
{
|
||||
const struct config_param *param = config_get_param(name);
|
||||
long value;
|
||||
char *endptr;
|
||||
|
||||
if (param == NULL)
|
||||
return default_value;
|
||||
|
||||
value = strtol(param->value, &endptr, 0);
|
||||
if (*endptr != 0 || value < 0)
|
||||
g_error("Not a valid non-negative number in line %i", param->line);
|
||||
|
||||
return (unsigned)value;
|
||||
}
|
||||
|
||||
unsigned
|
||||
config_get_positive(const char *name, unsigned default_value)
|
||||
{
|
||||
@@ -447,43 +545,35 @@ config_get_positive(const char *name, unsigned default_value)
|
||||
struct block_param *
|
||||
config_get_block_param(const struct config_param * param, const char *name)
|
||||
{
|
||||
struct block_param *ret = NULL;
|
||||
|
||||
if (param == NULL)
|
||||
return NULL;
|
||||
|
||||
for (unsigned i = 0; i < param->num_block_params; i++) {
|
||||
if (0 == strcmp(name, param->block_params[i].name)) {
|
||||
if (ret) {
|
||||
g_warning("\"%s\" first defined on line %i, and "
|
||||
"redefined on line %i\n", name,
|
||||
ret->line, param->block_params[i].line);
|
||||
}
|
||||
ret = param->block_params + i;
|
||||
struct block_param *bp = ¶m->block_params[i];
|
||||
bp->used = true;
|
||||
return bp;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool config_get_bool(const char *name, bool default_value)
|
||||
{
|
||||
const struct config_param *param = config_get_param(name);
|
||||
int value;
|
||||
bool success, value;
|
||||
|
||||
if (param == NULL)
|
||||
return default_value;
|
||||
|
||||
value = get_bool(param->value);
|
||||
if (value == CONF_BOOL_INVALID)
|
||||
success = get_bool(param->value, &value);
|
||||
if (!success)
|
||||
g_error("%s is not a boolean value (yes, true, 1) or "
|
||||
"(no, false, 0) on line %i\n",
|
||||
name, param->line);
|
||||
|
||||
if (value == CONF_BOOL_UNSET)
|
||||
return default_value;
|
||||
|
||||
return !!value;
|
||||
return value;
|
||||
}
|
||||
|
||||
const char *
|
||||
@@ -524,19 +614,16 @@ config_get_block_bool(const struct config_param *param, const char *name,
|
||||
bool default_value)
|
||||
{
|
||||
struct block_param *bp = config_get_block_param(param, name);
|
||||
int value;
|
||||
bool success, value;
|
||||
|
||||
if (bp == NULL)
|
||||
return default_value;
|
||||
|
||||
value = get_bool(bp->value);
|
||||
if (value == CONF_BOOL_INVALID)
|
||||
success = get_bool(bp->value, &value);
|
||||
if (!success)
|
||||
g_error("%s is not a boolean value (yes, true, 1) or "
|
||||
"(no, false, 0) on line %i\n",
|
||||
name, bp->line);
|
||||
|
||||
if (value == CONF_BOOL_UNSET)
|
||||
return default_value;
|
||||
|
||||
return !!value;
|
||||
return value;
|
||||
}
|
||||
|
||||
83
src/conf.h
83
src/conf.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -30,10 +30,10 @@
|
||||
#define CONF_DB_FILE "db_file"
|
||||
#define CONF_STICKER_FILE "sticker_file"
|
||||
#define CONF_LOG_FILE "log_file"
|
||||
#define CONF_ERROR_FILE "error_file"
|
||||
#define CONF_PID_FILE "pid_file"
|
||||
#define CONF_STATE_FILE "state_file"
|
||||
#define CONF_USER "user"
|
||||
#define CONF_GROUP "group"
|
||||
#define CONF_BIND_TO_ADDRESS "bind_to_address"
|
||||
#define CONF_PORT "port"
|
||||
#define CONF_LOG_LEVEL "log_level"
|
||||
@@ -42,12 +42,13 @@
|
||||
#define CONF_PASSWORD "password"
|
||||
#define CONF_DEFAULT_PERMS "default_permissions"
|
||||
#define CONF_AUDIO_OUTPUT "audio_output"
|
||||
#define CONF_AUDIO_FILTER "filter"
|
||||
#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format"
|
||||
#define CONF_MIXER_TYPE "mixer_type"
|
||||
#define CONF_MIXER_DEVICE "mixer_device"
|
||||
#define CONF_MIXER_CONTROL "mixer_control"
|
||||
#define CONF_REPLAYGAIN "replaygain"
|
||||
#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp"
|
||||
#define CONF_REPLAYGAIN_MISSING_PREAMP "replaygain_missing_preamp"
|
||||
#define CONF_REPLAYGAIN_LIMIT "replaygain_limit"
|
||||
#define CONF_VOLUME_NORMALIZATION "volume_normalization"
|
||||
#define CONF_SAMPLERATE_CONVERTER "samplerate_converter"
|
||||
#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size"
|
||||
@@ -68,17 +69,25 @@
|
||||
#define CONF_DECODER "decoder"
|
||||
#define CONF_INPUT "input"
|
||||
#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback"
|
||||
|
||||
#define CONF_BOOL_UNSET -1
|
||||
#define CONF_BOOL_INVALID -2
|
||||
#define CONF_PLAYLIST_PLUGIN "playlist_plugin"
|
||||
#define CONF_AUTO_UPDATE "auto_update"
|
||||
#define CONF_AUTO_UPDATE_DEPTH "auto_update_depth"
|
||||
|
||||
#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16)
|
||||
#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS false
|
||||
|
||||
#define MAX_FILTER_CHAIN_LENGTH 255
|
||||
|
||||
struct block_param {
|
||||
char *name;
|
||||
char *value;
|
||||
int line;
|
||||
|
||||
/**
|
||||
* This flag is false when nobody has queried the value of
|
||||
* this option yet.
|
||||
*/
|
||||
bool used;
|
||||
};
|
||||
|
||||
struct config_param {
|
||||
@@ -87,31 +96,57 @@ struct config_param {
|
||||
|
||||
struct block_param *block_params;
|
||||
unsigned num_block_params;
|
||||
|
||||
/**
|
||||
* This flag is false when nobody has queried the value of
|
||||
* this option yet.
|
||||
*/
|
||||
bool used;
|
||||
};
|
||||
|
||||
/**
|
||||
* A GQuark for GError instances, resulting from malformed
|
||||
* configuration.
|
||||
*/
|
||||
static inline GQuark
|
||||
config_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("config");
|
||||
}
|
||||
|
||||
void config_global_init(void);
|
||||
void config_global_finish(void);
|
||||
|
||||
void config_read_file(const char *file);
|
||||
|
||||
/**
|
||||
* Adds a new configuration parameter. The name must be registered
|
||||
* with registerConfigParam().
|
||||
* Call this function after all configuration has been evaluated. It
|
||||
* checks for unused parameters, and logs warnings.
|
||||
*/
|
||||
void
|
||||
config_add_param(const char *name, struct config_param *param);
|
||||
void config_global_check(void);
|
||||
|
||||
bool
|
||||
config_read_file(const char *file, GError **error_r);
|
||||
|
||||
/* don't free the returned value
|
||||
set _last_ to NULL to get first entry */
|
||||
G_GNUC_PURE
|
||||
struct config_param *
|
||||
config_get_next_param(const char *name, const struct config_param *last);
|
||||
|
||||
G_GNUC_PURE
|
||||
static inline struct config_param *
|
||||
config_get_param(const char *name)
|
||||
{
|
||||
return config_get_next_param(name, NULL);
|
||||
}
|
||||
|
||||
/* Note on G_GNUC_PURE: Some of the functions declared pure are not
|
||||
really pure in strict sense. They have side effect such that they
|
||||
validate parameter's value and signal an error if it's invalid.
|
||||
However, if the argument was already validated or we don't care
|
||||
about the argument at all, this may be ignored so in the end, we
|
||||
should be fine with calling those functions pure. */
|
||||
|
||||
G_GNUC_PURE
|
||||
const char *
|
||||
config_get_string(const char *name, const char *default_value);
|
||||
|
||||
@@ -120,17 +155,31 @@ config_get_string(const char *name, const char *default_value);
|
||||
* absolute path. If there is a tilde prefix, it is expanded. Aborts
|
||||
* MPD if the path is not a valid absolute path.
|
||||
*/
|
||||
/* We lie here really. This function is not pure as it has side
|
||||
effects -- it parse the value and creates new string freeing
|
||||
previous one. However, because this works the very same way each
|
||||
time (ie. from the outside it appears as if function had no side
|
||||
effects) we should be in the clear declaring it pure. */
|
||||
G_GNUC_PURE
|
||||
const char *
|
||||
config_get_path(const char *name);
|
||||
|
||||
G_GNUC_PURE
|
||||
unsigned
|
||||
config_get_unsigned(const char *name, unsigned default_value);
|
||||
|
||||
G_GNUC_PURE
|
||||
unsigned
|
||||
config_get_positive(const char *name, unsigned default_value);
|
||||
|
||||
G_GNUC_PURE
|
||||
struct block_param *
|
||||
config_get_block_param(const struct config_param *param, const char *name);
|
||||
|
||||
G_GNUC_PURE
|
||||
bool config_get_bool(const char *name, bool default_value);
|
||||
|
||||
G_GNUC_PURE
|
||||
const char *
|
||||
config_get_block_string(const struct config_param *param, const char *name,
|
||||
const char *default_value);
|
||||
@@ -142,10 +191,12 @@ config_dup_block_string(const struct config_param *param, const char *name,
|
||||
return g_strdup(config_get_block_string(param, name, default_value));
|
||||
}
|
||||
|
||||
G_GNUC_PURE
|
||||
unsigned
|
||||
config_get_block_unsigned(const struct config_param *param, const char *name,
|
||||
unsigned default_value);
|
||||
|
||||
G_GNUC_PURE
|
||||
bool
|
||||
config_get_block_bool(const struct config_param *param, const char *name,
|
||||
bool default_value);
|
||||
@@ -153,8 +204,8 @@ config_get_block_bool(const struct config_param *param, const char *name,
|
||||
struct config_param *
|
||||
config_new_param(const char *value, int line);
|
||||
|
||||
void
|
||||
config_add_block_param(struct config_param *param, const char *name,
|
||||
const char *value, int line);
|
||||
bool
|
||||
config_add_block_param(struct config_param * param, const char *name,
|
||||
const char *value, int line, GError **error_r);
|
||||
|
||||
#endif
|
||||
|
||||
148
src/crossfade.c
148
src/crossfade.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "crossfade.h"
|
||||
#include "pcm_mix.h"
|
||||
#include "chunk.h"
|
||||
@@ -25,73 +26,112 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <glib.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "crossfade"
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
static char *
|
||||
strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr)
|
||||
{
|
||||
return strtok(str, delim);
|
||||
}
|
||||
#endif
|
||||
|
||||
static float mixramp_interpolate(char *ramp_list, float required_db)
|
||||
{
|
||||
float db, secs, last_db = nan(""), last_secs = 0;
|
||||
char *ramp_str, *save_str = NULL;
|
||||
|
||||
/* ramp_list is a string of pairs of dBs and seconds that describe the
|
||||
* volume profile. Delimiters are semi-colons between pairs and spaces
|
||||
* between the dB and seconds of a pair.
|
||||
* The dB values must be monotonically increasing for this to work. */
|
||||
|
||||
while (1) {
|
||||
/* Parse the dB tokens out of the input string. */
|
||||
ramp_str = strtok_r(ramp_list, " ", &save_str);
|
||||
|
||||
/* Tell strtok to continue next time round. */
|
||||
ramp_list = NULL;
|
||||
|
||||
/* Parse the dB value. */
|
||||
if (NULL == ramp_str) {
|
||||
return nan("");
|
||||
}
|
||||
db = (float)atof(ramp_str);
|
||||
|
||||
/* Parse the time. */
|
||||
ramp_str = strtok_r(NULL, ";", &save_str);
|
||||
if (NULL == ramp_str) {
|
||||
return nan("");
|
||||
}
|
||||
secs = (float)atof(ramp_str);
|
||||
|
||||
/* Check for exact match. */
|
||||
if (db == required_db) {
|
||||
return secs;
|
||||
}
|
||||
|
||||
/* Save if too quiet. */
|
||||
if (db < required_db) {
|
||||
last_db = db;
|
||||
last_secs = secs;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If required db < any stored value, use the least. */
|
||||
if (isnan(last_db)) {
|
||||
return secs;
|
||||
}
|
||||
|
||||
/* Finally, interpolate linearly. */
|
||||
secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
|
||||
return secs;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned cross_fade_calc(float duration, float total_time,
|
||||
float mixramp_db, float mixramp_delay,
|
||||
float replay_gain_db, float replay_gain_prev_db,
|
||||
char *mixramp_start, char *mixramp_prev_end,
|
||||
const struct audio_format *af,
|
||||
const struct audio_format *old_format,
|
||||
unsigned max_chunks)
|
||||
{
|
||||
unsigned int chunks;
|
||||
unsigned int chunks = 0;
|
||||
float chunks_f;
|
||||
float mixramp_overlap;
|
||||
|
||||
if (duration <= 0 || duration >= total_time ||
|
||||
if (duration < 0 || duration >= total_time ||
|
||||
/* we can't crossfade when the audio formats are different */
|
||||
!audio_format_equals(af, old_format))
|
||||
return 0;
|
||||
|
||||
assert(duration > 0);
|
||||
assert(af->bits > 0);
|
||||
assert(af->channels > 0);
|
||||
assert(af->sample_rate > 0);
|
||||
assert(duration >= 0);
|
||||
assert(audio_format_valid(af));
|
||||
|
||||
chunks = audio_format_time_to_size(af) / CHUNK_SIZE;
|
||||
chunks = (chunks * duration + 0.5);
|
||||
chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE;
|
||||
|
||||
if (chunks > max_chunks)
|
||||
if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) {
|
||||
chunks = (chunks_f * duration + 0.5);
|
||||
} else {
|
||||
/* Calculate mixramp overlap. */
|
||||
mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db)
|
||||
+ mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db);
|
||||
if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) {
|
||||
chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
|
||||
g_debug("will overlap %d chunks, %fs", chunks,
|
||||
mixramp_overlap - mixramp_delay);
|
||||
}
|
||||
}
|
||||
|
||||
if (chunks > max_chunks) {
|
||||
chunks = max_chunks;
|
||||
g_warning("audio_buffer_size too small for computed MixRamp overlap");
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
|
||||
const struct audio_format *format,
|
||||
unsigned int current_chunk, unsigned int num_chunks)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
assert(a != NULL);
|
||||
assert(b != NULL);
|
||||
assert(a->length == 0 || b->length == 0 ||
|
||||
audio_format_equals(&a->audio_format, &b->audio_format));
|
||||
assert(current_chunk <= num_chunks);
|
||||
|
||||
if (a->tag == NULL && b->tag != NULL)
|
||||
/* merge the tag into the destination chunk */
|
||||
a->tag = tag_dup(b->tag);
|
||||
|
||||
size = b->length > a->length
|
||||
? a->length
|
||||
: b->length;
|
||||
|
||||
pcm_mix(a->data,
|
||||
b->data,
|
||||
size,
|
||||
format,
|
||||
((float)current_chunk) / num_chunks);
|
||||
|
||||
if (b->length > a->length) {
|
||||
/* the second buffer is larger than the first one:
|
||||
there is unmixed rest at the end. Copy it over.
|
||||
The output buffer API guarantees that there is
|
||||
enough room in a->data. */
|
||||
|
||||
#ifndef NDEBUG
|
||||
if (a->length == 0)
|
||||
a->audio_format = b->audio_format;
|
||||
#endif
|
||||
|
||||
memcpy(a->data + a->length,
|
||||
b->data + a->length,
|
||||
b->length - a->length);
|
||||
a->length = b->length;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -28,6 +28,12 @@ struct music_chunk;
|
||||
*
|
||||
* @param duration the requested crossfade duration
|
||||
* @param total_time total_time the duration of the new song
|
||||
* @param mixramp_db the current mixramp_db setting
|
||||
* @param mixramp_delay the current mixramp_delay setting
|
||||
* @param replay_gain_db the ReplayGain adjustment used for this song
|
||||
* @param replay_gain_prev_db the ReplayGain adjustment used on the last song
|
||||
* @param mixramp_start the next songs mixramp_start tag
|
||||
* @param mixramp_prev_end the last songs mixramp_end setting
|
||||
* @param af the audio format of the new song
|
||||
* @param old_format the audio format of the current song
|
||||
* @param max_chunks the maximum number of chunks
|
||||
@@ -35,22 +41,11 @@ struct music_chunk;
|
||||
* should be disabled for this song change
|
||||
*/
|
||||
unsigned cross_fade_calc(float duration, float total_time,
|
||||
float mixramp_db, float mixramp_delay,
|
||||
float replay_gain_db, float replay_gain_prev_db,
|
||||
char *mixramp_start, char *mixramp_prev_end,
|
||||
const struct audio_format *af,
|
||||
const struct audio_format *old_format,
|
||||
unsigned max_chunks);
|
||||
|
||||
/**
|
||||
* Applies cross fading to two chunks, i.e. mixes these chunks.
|
||||
* Internally, this calls pcm_mix().
|
||||
*
|
||||
* @param a the chunk in the current song (and the destination chunk)
|
||||
* @param b the according chunk in the new song
|
||||
* @param format the audio format of both chunks (must be the same)
|
||||
* @param current_chunk the relative index of the current chunk
|
||||
* @param num_chunks the number of chunks used for cross fading
|
||||
*/
|
||||
void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
|
||||
const struct audio_format *format,
|
||||
unsigned int current_chunk, unsigned int num_chunks);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,76 +1,78 @@
|
||||
#include "config.h"
|
||||
#include "cue_tag.h"
|
||||
#include "tag.h"
|
||||
|
||||
static struct tag*
|
||||
cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem)
|
||||
#include <libcue/libcue.h>
|
||||
#include <assert.h>
|
||||
|
||||
static struct tag *
|
||||
cue_tag_cd(struct Cdtext *cdtext, struct Rem *rem)
|
||||
{
|
||||
char* tmp = NULL;
|
||||
struct tag* tag = NULL;
|
||||
struct tag *tag;
|
||||
char *tmp;
|
||||
|
||||
//if (cdtext == NULL)
|
||||
//return NULL;
|
||||
assert(cdtext != NULL);
|
||||
|
||||
tag = tag_new();
|
||||
|
||||
tag_begin_add(tag);
|
||||
|
||||
{ /* TAG_ITEM_ALBUM_ARTIST */
|
||||
/* TAG_ALBUM_ARTIST */
|
||||
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ALBUM_ARTIST, tmp);
|
||||
/* TAG_ITEM_ALBUM_ARTIST */ }
|
||||
tag_add_item(tag, TAG_ALBUM_ARTIST, tmp);
|
||||
|
||||
{ /* TAG_ITEM_ARTIST */
|
||||
/* TAG_ARTIST */
|
||||
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
|
||||
/* TAG_ITEM_ARTIST */ }
|
||||
tag_add_item(tag, TAG_ARTIST, tmp);
|
||||
|
||||
/* TAG_ITEM_PERFORMER */
|
||||
/* TAG_PERFORMER */
|
||||
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
|
||||
tag_add_item(tag, TAG_PERFORMER, tmp);
|
||||
|
||||
/* TAG_ITEM_COMPOSER */
|
||||
/* TAG_COMPOSER */
|
||||
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
|
||||
tag_add_item(tag, TAG_COMPOSER, tmp);
|
||||
|
||||
/* TAG_ITEM_ALBUM */
|
||||
/* TAG_ALBUM */
|
||||
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ALBUM, tmp);
|
||||
tag_add_item(tag, TAG_ALBUM, tmp);
|
||||
|
||||
/* TAG_ITEM_GENRE */
|
||||
/* TAG_GENRE */
|
||||
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_GENRE, tmp);
|
||||
tag_add_item(tag, TAG_GENRE, tmp);
|
||||
|
||||
/* TAG_ITEM_DATE */
|
||||
/* TAG_DATE */
|
||||
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_DATE, tmp);
|
||||
tag_add_item(tag, TAG_DATE, tmp);
|
||||
|
||||
/* TAG_ITEM_COMMENT */
|
||||
/* TAG_COMMENT */
|
||||
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
|
||||
tag_add_item(tag, TAG_COMMENT, tmp);
|
||||
|
||||
/* TAG_ITEM_DISC */
|
||||
/* TAG_DISC */
|
||||
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_DISC, tmp);
|
||||
tag_add_item(tag, TAG_DISC, tmp);
|
||||
|
||||
/* stream name, usually empty
|
||||
* tag_add_item(tag, TAG_ITEM_NAME,);
|
||||
* tag_add_item(tag, TAG_NAME,);
|
||||
*/
|
||||
|
||||
/* REM MUSICBRAINZ entry?
|
||||
@@ -82,175 +84,152 @@ cue_tag_cd(struct Cdtext* cdtext, struct Rem* rem)
|
||||
|
||||
tag_end_add(tag);
|
||||
|
||||
if (tag != NULL)
|
||||
{
|
||||
if (tag_is_empty(tag))
|
||||
{
|
||||
tag_free(tag);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
return tag;
|
||||
}
|
||||
else
|
||||
if (tag_is_empty(tag)) {
|
||||
tag_free(tag);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
static struct tag*
|
||||
cue_tag_track(struct Cdtext* cdtext, struct Rem* rem)
|
||||
static struct tag *
|
||||
cue_tag_track(struct Cdtext *cdtext, struct Rem *rem)
|
||||
{
|
||||
char* tmp = NULL;
|
||||
struct tag* tag = NULL;
|
||||
struct tag *tag;
|
||||
char *tmp;
|
||||
|
||||
//if (cdtext == NULL)
|
||||
//return NULL;
|
||||
assert(cdtext != NULL);
|
||||
|
||||
tag = tag_new();
|
||||
|
||||
tag_begin_add(tag);
|
||||
|
||||
{ /* TAG_ITEM_ARTIST */
|
||||
/* TAG_ARTIST */
|
||||
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_SONGWRITER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
|
||||
tag_add_item(tag, TAG_ARTIST, tmp);
|
||||
|
||||
else if ((tmp = cdtext_get(PTI_ARRANGER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, tmp);
|
||||
/* TAG_ITEM_ARTIST */ }
|
||||
tag_add_item(tag, TAG_ARTIST, tmp);
|
||||
|
||||
/* TAG_ITEM_TITLE */
|
||||
/* TAG_TITLE */
|
||||
if ((tmp = cdtext_get(PTI_TITLE, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_TITLE, tmp);
|
||||
tag_add_item(tag, TAG_TITLE, tmp);
|
||||
|
||||
/* TAG_ITEM_GENRE */
|
||||
/* TAG_GENRE */
|
||||
if ((tmp = cdtext_get(PTI_GENRE, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_GENRE, tmp);
|
||||
tag_add_item(tag, TAG_GENRE, tmp);
|
||||
|
||||
/* TAG_ITEM_DATE */
|
||||
/* TAG_DATE */
|
||||
if ((tmp = rem_get(REM_DATE, rem)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_DATE, tmp);
|
||||
tag_add_item(tag, TAG_DATE, tmp);
|
||||
|
||||
/* TAG_ITEM_COMPOSER */
|
||||
/* TAG_COMPOSER */
|
||||
if ((tmp = cdtext_get(PTI_COMPOSER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_COMPOSER, tmp);
|
||||
tag_add_item(tag, TAG_COMPOSER, tmp);
|
||||
|
||||
/* TAG_ITEM_PERFORMER */
|
||||
/* TAG_PERFORMER */
|
||||
if ((tmp = cdtext_get(PTI_PERFORMER, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_PERFORMER, tmp);
|
||||
tag_add_item(tag, TAG_PERFORMER, tmp);
|
||||
|
||||
/* TAG_ITEM_COMMENT */
|
||||
/* TAG_COMMENT */
|
||||
if ((tmp = cdtext_get(PTI_MESSAGE, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_COMMENT, tmp);
|
||||
tag_add_item(tag, TAG_COMMENT, tmp);
|
||||
|
||||
/* TAG_ITEM_DISC */
|
||||
/* TAG_DISC */
|
||||
if ((tmp = cdtext_get(PTI_DISC_ID, cdtext)) != NULL)
|
||||
tag_add_item(tag, TAG_ITEM_DISC, tmp);
|
||||
tag_add_item(tag, TAG_DISC, tmp);
|
||||
|
||||
tag_end_add(tag);
|
||||
|
||||
if (tag != NULL)
|
||||
{
|
||||
if (tag_is_empty(tag))
|
||||
{
|
||||
tag_free(tag);
|
||||
return NULL;
|
||||
}
|
||||
else
|
||||
return tag;
|
||||
}
|
||||
else
|
||||
if (tag_is_empty(tag)) {
|
||||
tag_free(tag);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
struct tag*
|
||||
cue_tag_file( FILE* fp,
|
||||
const unsigned int tnum)
|
||||
struct tag *
|
||||
cue_tag(struct Cd *cd, unsigned tnum)
|
||||
{
|
||||
struct tag* cd_tag = NULL;
|
||||
struct tag* track_tag = NULL;
|
||||
struct Cd* cd = NULL;
|
||||
struct tag *cd_tag, *track_tag, *tag;
|
||||
struct Track *track;
|
||||
|
||||
assert(cd != NULL);
|
||||
|
||||
track = cd_get_track(cd, tnum);
|
||||
if (track == NULL)
|
||||
return NULL;
|
||||
|
||||
/* tag from CDtext info */
|
||||
cd_tag = cue_tag_cd(cd_get_cdtext(cd), cd_get_rem(cd));
|
||||
|
||||
/* tag from TRACKtext info */
|
||||
track_tag = cue_tag_track(track_get_cdtext(track),
|
||||
track_get_rem(track));
|
||||
|
||||
tag = tag_merge_replace(cd_tag, track_tag);
|
||||
if (tag == NULL)
|
||||
return NULL;
|
||||
|
||||
tag->time = track_get_length(track)
|
||||
- track_get_index(track, 1)
|
||||
+ track_get_zero_pre(track);
|
||||
track = cd_get_track(cd, tnum + 1);
|
||||
if (track != NULL)
|
||||
tag->time += track_get_index(track, 1)
|
||||
- track_get_zero_pre(track);
|
||||
/* libcue returns the track duration in frames, and there are
|
||||
75 frames per second; this formula rounds down */
|
||||
tag->time = tag->time / 75;
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
struct tag *
|
||||
cue_tag_file(FILE *fp, unsigned tnum)
|
||||
{
|
||||
struct Cd *cd;
|
||||
struct tag *tag;
|
||||
|
||||
assert(fp != NULL);
|
||||
|
||||
if (tnum > 256)
|
||||
return NULL;
|
||||
|
||||
if (fp == NULL)
|
||||
return NULL;
|
||||
else
|
||||
cd = cue_parse_file(fp);
|
||||
|
||||
cd = cue_parse_file(fp);
|
||||
if (cd == NULL)
|
||||
return NULL;
|
||||
else
|
||||
{
|
||||
/* tag from CDtext info */
|
||||
cd_tag = cue_tag_cd( cd_get_cdtext(cd),
|
||||
cd_get_rem(cd));
|
||||
|
||||
/* tag from TRACKtext info */
|
||||
track_tag = cue_tag_track( track_get_cdtext( cd_get_track(cd, tnum)),
|
||||
track_get_rem( cd_get_track(cd, tnum)));
|
||||
tag = cue_tag(cd, tnum);
|
||||
cd_delete(cd);
|
||||
|
||||
cd_delete(cd);
|
||||
}
|
||||
|
||||
return tag_merge_replace(cd_tag, track_tag);
|
||||
return tag;
|
||||
}
|
||||
|
||||
struct tag*
|
||||
cue_tag_string( char* str,
|
||||
const unsigned int tnum)
|
||||
struct tag *
|
||||
cue_tag_string(const char *str, unsigned tnum)
|
||||
{
|
||||
struct tag* cd_tag = NULL;
|
||||
struct tag* track_tag = NULL;
|
||||
struct tag* merge_tag = NULL;
|
||||
struct Cd* cd = NULL;
|
||||
struct Cd *cd;
|
||||
struct tag *tag;
|
||||
|
||||
assert(str != NULL);
|
||||
|
||||
if (tnum > 256)
|
||||
return NULL;
|
||||
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
else
|
||||
cd = cue_parse_string(str);
|
||||
|
||||
cd = cue_parse_string(str);
|
||||
if (cd == NULL)
|
||||
return NULL;
|
||||
else
|
||||
{
|
||||
/* tag from CDtext info */
|
||||
cd_tag = cue_tag_cd( cd_get_cdtext(cd),
|
||||
cd_get_rem(cd));
|
||||
|
||||
/* tag from TRACKtext info */
|
||||
track_tag = cue_tag_track( track_get_cdtext( cd_get_track(cd, tnum)),
|
||||
track_get_rem( cd_get_track(cd, tnum)));
|
||||
tag = cue_tag(cd, tnum);
|
||||
cd_delete(cd);
|
||||
|
||||
cd_delete(cd);
|
||||
}
|
||||
|
||||
if ((cd_tag != NULL) && (track_tag != NULL))
|
||||
{
|
||||
merge_tag = tag_merge(cd_tag, track_tag);
|
||||
tag_free(cd_tag);
|
||||
tag_free(track_tag);
|
||||
return merge_tag;
|
||||
}
|
||||
|
||||
else if (cd_tag != NULL)
|
||||
{
|
||||
return cd_tag;
|
||||
}
|
||||
|
||||
else if (track_tag != NULL)
|
||||
{
|
||||
return track_tag;
|
||||
}
|
||||
|
||||
else
|
||||
return NULL;
|
||||
return tag;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
#ifndef MPD_CUE_TAG_H
|
||||
#define MPD_CUE_TAG_H
|
||||
|
||||
#include "config.h"
|
||||
#include "check.h"
|
||||
|
||||
#ifdef HAVE_CUE /* libcue */
|
||||
|
||||
#include <libcue/libcue.h>
|
||||
#include "../tag.h"
|
||||
#include <stdio.h>
|
||||
|
||||
struct tag*
|
||||
cue_tag_file( FILE*,
|
||||
const unsigned int);
|
||||
struct tag;
|
||||
struct Cd;
|
||||
|
||||
struct tag*
|
||||
cue_tag_string( char*,
|
||||
const unsigned int);
|
||||
struct tag *
|
||||
cue_tag(struct Cd *cd, unsigned tnum);
|
||||
|
||||
struct tag *
|
||||
cue_tag_file(FILE *file, unsigned tnum);
|
||||
|
||||
struct tag *
|
||||
cue_tag_string(const char *str, unsigned tnum);
|
||||
|
||||
#endif /* libcue */
|
||||
#endif
|
||||
|
||||
107
src/daemon.c
107
src/daemon.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "daemon.h"
|
||||
|
||||
#include <glib.h>
|
||||
@@ -45,20 +46,21 @@
|
||||
static char *user_name;
|
||||
|
||||
/** the Unix user id which MPD runs as */
|
||||
static uid_t user_uid;
|
||||
static uid_t user_uid = (uid_t)-1;
|
||||
|
||||
/** the Unix group id which MPD runs as */
|
||||
static gid_t user_gid;
|
||||
static gid_t user_gid = (pid_t)-1;
|
||||
|
||||
/** the absolute path of the pidfile */
|
||||
static char *pidfile;
|
||||
|
||||
#endif
|
||||
/* whether "group" conf. option was given */
|
||||
static bool had_group = false;
|
||||
|
||||
|
||||
void
|
||||
daemonize_kill(void)
|
||||
{
|
||||
#ifndef WIN32
|
||||
FILE *fp;
|
||||
int pid, ret;
|
||||
|
||||
@@ -82,41 +84,34 @@ daemonize_kill(void)
|
||||
pid, g_strerror(errno));
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
#else
|
||||
g_error("--kill is not available on WIN32");
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
daemonize_close_stdin(void)
|
||||
{
|
||||
int fd = open("/dev/null", O_RDONLY);
|
||||
|
||||
if (fd < 0)
|
||||
close(STDIN_FILENO);
|
||||
else if (fd != STDIN_FILENO) {
|
||||
dup2(fd, STDIN_FILENO);
|
||||
close(fd);
|
||||
}
|
||||
close(STDIN_FILENO);
|
||||
open("/dev/null", O_RDONLY);
|
||||
}
|
||||
|
||||
void
|
||||
daemonize_set_user(void)
|
||||
{
|
||||
#ifndef WIN32
|
||||
if (user_name == NULL)
|
||||
return;
|
||||
|
||||
/* get uid */
|
||||
if (setgid(user_gid) == -1) {
|
||||
g_error("cannot setgid for user \"%s\": %s",
|
||||
user_name, g_strerror(errno));
|
||||
/* set gid */
|
||||
if (user_gid != (gid_t)-1 && user_gid != getgid()) {
|
||||
if (setgid(user_gid) == -1) {
|
||||
g_error("cannot setgid to %d: %s",
|
||||
(int)user_gid, g_strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _BSD_SOURCE
|
||||
/* init suplementary groups
|
||||
* (must be done before we change our uid)
|
||||
*/
|
||||
if (initgroups(user_name, user_gid) == -1) {
|
||||
if (!had_group && initgroups(user_name, user_gid) == -1) {
|
||||
g_warning("cannot init supplementary groups "
|
||||
"of user \"%s\": %s",
|
||||
user_name, g_strerror(errno));
|
||||
@@ -124,32 +119,38 @@ daemonize_set_user(void)
|
||||
#endif
|
||||
|
||||
/* set uid */
|
||||
if (setuid(user_uid) == -1) {
|
||||
if (user_uid != (uid_t)-1 && user_uid != getuid() &&
|
||||
setuid(user_uid) == -1) {
|
||||
g_error("cannot change to uid of user \"%s\": %s",
|
||||
user_name, g_strerror(errno));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef G_OS_WIN32
|
||||
static void
|
||||
daemonize_detach(void)
|
||||
{
|
||||
pid_t pid;
|
||||
|
||||
/* flush all file handles before duplicating the buffers */
|
||||
|
||||
fflush(NULL);
|
||||
|
||||
#ifdef HAVE_DAEMON
|
||||
|
||||
if (daemon(0, 1))
|
||||
g_error("daemon() failed: %s", g_strerror(errno));
|
||||
|
||||
#elif defined(HAVE_FORK)
|
||||
|
||||
/* detach from parent process */
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0)
|
||||
switch (fork()) {
|
||||
case -1:
|
||||
g_error("fork() failed: %s", g_strerror(errno));
|
||||
|
||||
if (pid > 0)
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
/* exit the parent process */
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* release the current working directory */
|
||||
|
||||
@@ -160,14 +161,16 @@ daemonize_detach(void)
|
||||
|
||||
setsid();
|
||||
|
||||
#else
|
||||
g_error("no support for daemonizing");
|
||||
#endif
|
||||
|
||||
g_debug("daemonized!");
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
daemonize(bool detach)
|
||||
{
|
||||
#ifndef WIN32
|
||||
FILE *fp = NULL;
|
||||
|
||||
if (pidfile != NULL) {
|
||||
@@ -189,47 +192,45 @@ daemonize(bool detach)
|
||||
fprintf(fp, "%lu\n", (unsigned long)getpid());
|
||||
fclose(fp);
|
||||
}
|
||||
#else
|
||||
/* no daemonization on WIN32 */
|
||||
(void)detach;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
daemonize_init(const char *user, const char *_pidfile)
|
||||
daemonize_init(const char *user, const char *group, const char *_pidfile)
|
||||
{
|
||||
#ifndef WIN32
|
||||
if (user != NULL && strcmp(user, g_get_user_name()) != 0) {
|
||||
struct passwd *pwd;
|
||||
|
||||
user_name = g_strdup(user);
|
||||
|
||||
pwd = getpwnam(user_name);
|
||||
if (pwd == NULL)
|
||||
g_error("no such user \"%s\"", user_name);
|
||||
if (user) {
|
||||
struct passwd *pwd = getpwnam(user);
|
||||
if (!pwd)
|
||||
g_error("no such user \"%s\"", user);
|
||||
|
||||
user_uid = pwd->pw_uid;
|
||||
user_gid = pwd->pw_gid;
|
||||
|
||||
user_name = g_strdup(user);
|
||||
|
||||
/* this is needed by libs such as arts */
|
||||
g_setenv("HOME", pwd->pw_dir, true);
|
||||
}
|
||||
|
||||
if (group) {
|
||||
struct group *grp = grp = getgrnam(group);
|
||||
if (!grp)
|
||||
g_error("no such group \"%s\"", group);
|
||||
user_gid = grp->gr_gid;
|
||||
had_group = true;
|
||||
}
|
||||
|
||||
|
||||
pidfile = g_strdup(_pidfile);
|
||||
#else
|
||||
(void)user;
|
||||
(void)_pidfile;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
daemonize_finish(void)
|
||||
{
|
||||
#ifndef WIN32
|
||||
if (pidfile != NULL)
|
||||
unlink(pidfile);
|
||||
|
||||
g_free(user_name);
|
||||
g_free(pidfile);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
40
src/daemon.h
40
src/daemon.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -22,32 +22,68 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifndef WIN32
|
||||
void
|
||||
daemonize_init(const char *user, const char *pidfile);
|
||||
daemonize_init(const char *user, const char *group, const char *pidfile);
|
||||
#else
|
||||
static inline void
|
||||
daemonize_init(const char *user, const char *group, const char *pidfile)
|
||||
{ (void)user; (void)group; (void)pidfile; }
|
||||
#endif
|
||||
|
||||
#ifndef WIN32
|
||||
void
|
||||
daemonize_finish(void);
|
||||
#else
|
||||
static inline void
|
||||
daemonize_finish(void)
|
||||
{ /* nop */ }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Kill the MPD which is currently running, pid determined from the
|
||||
* pid file.
|
||||
*/
|
||||
#ifndef WIN32
|
||||
void
|
||||
daemonize_kill(void);
|
||||
#else
|
||||
#include <glib.h>
|
||||
static inline void
|
||||
daemonize_kill(void)
|
||||
{ g_error("--kill is not available on WIN32"); }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Close stdin (fd 0) and re-open it as /dev/null.
|
||||
*/
|
||||
#ifndef WIN32
|
||||
void
|
||||
daemonize_close_stdin(void);
|
||||
#else
|
||||
static inline void
|
||||
daemonize_close_stdin(void) {}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Change to the configured Unix user.
|
||||
*/
|
||||
#ifndef WIN32
|
||||
void
|
||||
daemonize_set_user(void);
|
||||
#else
|
||||
static inline void
|
||||
daemonize_set_user(void)
|
||||
{ /* nop */ }
|
||||
#endif
|
||||
|
||||
#ifndef WIN32
|
||||
void
|
||||
daemonize(bool detach);
|
||||
#else
|
||||
static inline void
|
||||
daemonize(bool detach)
|
||||
{ (void)detach; }
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
103
src/database.c
103
src/database.c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,13 +17,16 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "database.h"
|
||||
#include "directory.h"
|
||||
#include "directory_save.h"
|
||||
#include "song.h"
|
||||
#include "path.h"
|
||||
#include "stats.h"
|
||||
#include "config.h"
|
||||
#include "text_file.h"
|
||||
#include "tag.h"
|
||||
#include "tag_internal.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -40,8 +43,14 @@
|
||||
|
||||
#define DIRECTORY_INFO_BEGIN "info_begin"
|
||||
#define DIRECTORY_INFO_END "info_end"
|
||||
#define DB_FORMAT_PREFIX "format: "
|
||||
#define DIRECTORY_MPD_VERSION "mpd_version: "
|
||||
#define DIRECTORY_FS_CHARSET "fs_charset: "
|
||||
#define DB_TAG_PREFIX "tag: "
|
||||
|
||||
enum {
|
||||
DB_FORMAT = 1,
|
||||
};
|
||||
|
||||
static char *database_path;
|
||||
|
||||
@@ -230,20 +239,27 @@ db_save(void)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* block signals when writing the db so we don't get a corrupted db */
|
||||
fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN);
|
||||
fprintf(fp, DB_FORMAT_PREFIX "%u\n", DB_FORMAT);
|
||||
fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION);
|
||||
fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, path_get_fs_charset());
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
if (!ignore_tag_items[i])
|
||||
fprintf(fp, DB_TAG_PREFIX "%s\n", tag_item_names[i]);
|
||||
|
||||
fprintf(fp, "%s\n", DIRECTORY_INFO_END);
|
||||
|
||||
if (directory_save(fp, music_root) < 0) {
|
||||
directory_save(fp, music_root);
|
||||
|
||||
if (ferror(fp)) {
|
||||
g_warning("Failed to write to database file: %s",
|
||||
strerror(errno));
|
||||
while (fclose(fp) && errno == EINTR);
|
||||
fclose(fp);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (fclose(fp) && errno == EINTR);
|
||||
fclose(fp);
|
||||
|
||||
if (stat(database_path, &st) == 0)
|
||||
database_mtime = st.st_mtime;
|
||||
@@ -256,64 +272,64 @@ db_load(GError **error)
|
||||
{
|
||||
FILE *fp = NULL;
|
||||
struct stat st;
|
||||
char buffer[100];
|
||||
GString *buffer = g_string_sized_new(1024);
|
||||
char *line;
|
||||
int format = 0;
|
||||
bool found_charset = false, found_version = false;
|
||||
bool success;
|
||||
bool tags[TAG_NUM_OF_ITEM_TYPES];
|
||||
|
||||
assert(database_path != NULL);
|
||||
assert(music_root != NULL);
|
||||
|
||||
if (!music_root)
|
||||
music_root = directory_new("", NULL);
|
||||
while (!(fp = fopen(database_path, "r")) && errno == EINTR) ;
|
||||
fp = fopen(database_path, "r");
|
||||
if (fp == NULL) {
|
||||
g_set_error(error, db_quark(), errno,
|
||||
"Failed to open database file \"%s\": %s",
|
||||
database_path, strerror(errno));
|
||||
g_string_free(buffer, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* get initial info */
|
||||
if (!fgets(buffer, sizeof(buffer), fp)) {
|
||||
fclose(fp);
|
||||
g_set_error(error, db_quark(), 0, "Unexpected end of file");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_strchomp(buffer);
|
||||
|
||||
if (0 != strcmp(DIRECTORY_INFO_BEGIN, buffer)) {
|
||||
line = read_text_line(fp, buffer);
|
||||
if (line == NULL || strcmp(DIRECTORY_INFO_BEGIN, line) != 0) {
|
||||
fclose(fp);
|
||||
g_set_error(error, db_quark(), 0, "Database corrupted");
|
||||
g_string_free(buffer, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (fgets(buffer, sizeof(buffer), fp) &&
|
||||
!g_str_has_prefix(buffer, DIRECTORY_INFO_END)) {
|
||||
g_strchomp(buffer);
|
||||
memset(tags, false, sizeof(tags));
|
||||
|
||||
if (g_str_has_prefix(buffer, DIRECTORY_MPD_VERSION)) {
|
||||
while ((line = read_text_line(fp, buffer)) != NULL &&
|
||||
strcmp(line, DIRECTORY_INFO_END) != 0) {
|
||||
if (g_str_has_prefix(line, DB_FORMAT_PREFIX)) {
|
||||
format = atoi(line + sizeof(DB_FORMAT_PREFIX) - 1);
|
||||
} else if (g_str_has_prefix(line, DIRECTORY_MPD_VERSION)) {
|
||||
if (found_version) {
|
||||
fclose(fp);
|
||||
g_set_error(error, db_quark(), 0,
|
||||
"Duplicate version line");
|
||||
g_string_free(buffer, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
found_version = true;
|
||||
} else if (g_str_has_prefix(buffer, DIRECTORY_FS_CHARSET)) {
|
||||
} else if (g_str_has_prefix(line, DIRECTORY_FS_CHARSET)) {
|
||||
const char *new_charset, *old_charset;
|
||||
|
||||
if (found_charset) {
|
||||
fclose(fp);
|
||||
g_set_error(error, db_quark(), 0,
|
||||
"Duplicate charset line");
|
||||
g_string_free(buffer, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
found_charset = true;
|
||||
|
||||
new_charset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]);
|
||||
new_charset = line + sizeof(DIRECTORY_FS_CHARSET) - 1;
|
||||
old_charset = path_get_fs_charset();
|
||||
if (old_charset != NULL
|
||||
&& strcmp(new_charset, old_charset)) {
|
||||
@@ -323,20 +339,51 @@ db_load(GError **error)
|
||||
"\"%s\" instead of \"%s\"; "
|
||||
"discarding database file",
|
||||
new_charset, old_charset);
|
||||
g_string_free(buffer, true);
|
||||
return false;
|
||||
}
|
||||
} else if (g_str_has_prefix(line, DB_TAG_PREFIX)) {
|
||||
const char *name = line + sizeof(DB_TAG_PREFIX) - 1;
|
||||
enum tag_type tag = tag_name_parse(name);
|
||||
if (tag == TAG_NUM_OF_ITEM_TYPES) {
|
||||
g_set_error(error, db_quark(), 0,
|
||||
"Unrecognized tag '%s', "
|
||||
"discarding database file",
|
||||
name);
|
||||
return false;
|
||||
}
|
||||
|
||||
tags[tag] = true;
|
||||
} else {
|
||||
fclose(fp);
|
||||
g_set_error(error, db_quark(), 0,
|
||||
"Malformed line: %s", buffer);
|
||||
"Malformed line: %s", line);
|
||||
g_string_free(buffer, true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (format != DB_FORMAT) {
|
||||
g_set_error(error, db_quark(), 0,
|
||||
"Database format mismatch, "
|
||||
"discarding database file");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) {
|
||||
if (!ignore_tag_items[i] && !tags[i]) {
|
||||
g_set_error(error, db_quark(), 0,
|
||||
"Tag list mismatch, "
|
||||
"discarding database file");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
g_debug("reading DB");
|
||||
|
||||
success = directory_load(fp, music_root, error);
|
||||
while (fclose(fp) && errno == EINTR) ;
|
||||
success = directory_load(fp, music_root, buffer, error);
|
||||
g_string_free(buffer, true);
|
||||
fclose(fp);
|
||||
|
||||
if (!success)
|
||||
return false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,6 +17,7 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "dbUtils.h"
|
||||
#include "locate.h"
|
||||
#include "directory.h"
|
||||
@@ -59,7 +60,7 @@ static int
|
||||
printSongInDirectory(struct song *song, G_GNUC_UNUSED void *data)
|
||||
{
|
||||
struct client *client = data;
|
||||
song_print_url(client, song);
|
||||
song_print_uri(client, song);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -74,7 +75,7 @@ searchInDirectory(struct song *song, void *_data)
|
||||
struct search_data *data = _data;
|
||||
|
||||
if (locate_song_search(song, data->criteria))
|
||||
return song_print_info(data->client, song);
|
||||
song_print_info(data->client, song);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -104,7 +105,7 @@ findInDirectory(struct song *song, void *_data)
|
||||
struct search_data *data = _data;
|
||||
|
||||
if (locate_song_match(song, data->criteria))
|
||||
return song_print_info(data->client, song);
|
||||
song_print_info(data->client, song);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -134,8 +135,7 @@ searchStatsInDirectory(struct song *song, void *data)
|
||||
|
||||
if (locate_song_match(song, stats->criteria)) {
|
||||
stats->numberOfSongs++;
|
||||
if (song->tag->time > 0)
|
||||
stats->playTime += song->tag->time;
|
||||
stats->playTime += song_get_duration(song);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -168,7 +168,7 @@ int printAllIn(struct client *client, const char *name)
|
||||
static int
|
||||
directoryAddSongToPlaylist(struct song *song, G_GNUC_UNUSED void *data)
|
||||
{
|
||||
return addSongToPlaylist(&g_playlist, song, NULL);
|
||||
return playlist_append_song(&g_playlist, song, NULL);
|
||||
}
|
||||
|
||||
struct add_data {
|
||||
@@ -199,6 +199,28 @@ int addAllInToStoredPlaylist(const char *name, const char *utf8file)
|
||||
return db_walk(name, directoryAddSongToStoredPlaylist, NULL, &data);
|
||||
}
|
||||
|
||||
static int
|
||||
findAddInDirectory(struct song *song, void *_data)
|
||||
{
|
||||
struct search_data *data = _data;
|
||||
|
||||
if (locate_song_match(song, data->criteria))
|
||||
return directoryAddSongToPlaylist(song, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int findAddIn(struct client *client, const char *name,
|
||||
const struct locate_item_list *criteria)
|
||||
{
|
||||
struct search_data data;
|
||||
|
||||
data.client = client;
|
||||
data.criteria = criteria;
|
||||
|
||||
return db_walk(name, findAddInDirectory, NULL, &data);
|
||||
}
|
||||
|
||||
static int
|
||||
directoryPrintSongInfo(struct song *song, void *data)
|
||||
{
|
||||
@@ -237,7 +259,7 @@ visitTag(struct client *client, struct strset *set,
|
||||
bool found = false;
|
||||
|
||||
if (tagType == LOCATE_TAG_FILE_TYPE) {
|
||||
song_print_url(client, song);
|
||||
song_print_uri(client, song);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -39,6 +39,10 @@ int
|
||||
findSongsIn(struct client *client, const char *name,
|
||||
const struct locate_item_list *criteria);
|
||||
|
||||
int
|
||||
findAddIn(struct client *client, const char *name,
|
||||
const struct locate_item_list *criteria);
|
||||
|
||||
int
|
||||
searchStatsForSongsIn(struct client *client, const char *name,
|
||||
const struct locate_item_list *criteria);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -21,7 +21,11 @@
|
||||
* Common data structures and functions used by FLAC and OggFLAC
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "_flac_common.h"
|
||||
#include "flac_metadata.h"
|
||||
#include "flac_pcm.h"
|
||||
#include "audio_check.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -31,186 +35,104 @@ void
|
||||
flac_data_init(struct flac_data *data, struct decoder * decoder,
|
||||
struct input_stream *input_stream)
|
||||
{
|
||||
data->time = 0;
|
||||
pcm_buffer_init(&data->buffer);
|
||||
|
||||
data->unsupported = false;
|
||||
data->initialized = false;
|
||||
data->total_frames = 0;
|
||||
data->first_frame = 0;
|
||||
data->next_frame = 0;
|
||||
|
||||
data->position = 0;
|
||||
data->bit_rate = 0;
|
||||
data->decoder = decoder;
|
||||
data->input_stream = input_stream;
|
||||
data->replay_gain_info = NULL;
|
||||
data->tag = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_find_float_comment(const FLAC__StreamMetadata *block,
|
||||
const char *cmnt, float *fl, bool *found_r)
|
||||
{
|
||||
int offset;
|
||||
size_t pos;
|
||||
int len;
|
||||
unsigned char tmp, *p;
|
||||
|
||||
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
|
||||
cmnt);
|
||||
if (offset < 0)
|
||||
return;
|
||||
|
||||
pos = strlen(cmnt) + 1; /* 1 is for '=' */
|
||||
len = block->data.vorbis_comment.comments[offset].length - pos;
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
p = &block->data.vorbis_comment.comments[offset].entry[pos];
|
||||
tmp = p[len];
|
||||
p[len] = '\0';
|
||||
*fl = (float)atof((char *)p);
|
||||
p[len] = tmp;
|
||||
|
||||
*found_r = true;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_parse_replay_gain(const FLAC__StreamMetadata *block,
|
||||
struct flac_data *data)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
if (data->replay_gain_info)
|
||||
replay_gain_info_free(data->replay_gain_info);
|
||||
|
||||
data->replay_gain_info = replay_gain_info_new();
|
||||
|
||||
flac_find_float_comment(block, "replaygain_album_gain",
|
||||
&data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].gain,
|
||||
&found);
|
||||
flac_find_float_comment(block, "replaygain_album_peak",
|
||||
&data->replay_gain_info->tuples[REPLAY_GAIN_ALBUM].peak,
|
||||
&found);
|
||||
flac_find_float_comment(block, "replaygain_track_gain",
|
||||
&data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].gain,
|
||||
&found);
|
||||
flac_find_float_comment(block, "replaygain_track_peak",
|
||||
&data->replay_gain_info->tuples[REPLAY_GAIN_TRACK].peak,
|
||||
&found);
|
||||
|
||||
if (!found) {
|
||||
replay_gain_info_free(data->replay_gain_info);
|
||||
data->replay_gain_info = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified name matches the entry's name, and if yes,
|
||||
* returns the comment value (not null-temrinated).
|
||||
*/
|
||||
static const char *
|
||||
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
|
||||
const char *name, const char *char_tnum, size_t *length_r)
|
||||
{
|
||||
size_t name_length = strlen(name);
|
||||
size_t char_tnum_length = 0;
|
||||
const char *comment = (const char*)entry->entry;
|
||||
|
||||
if (entry->length <= name_length ||
|
||||
g_ascii_strncasecmp(comment, name, name_length) != 0)
|
||||
return NULL;
|
||||
|
||||
if (char_tnum != NULL) {
|
||||
char_tnum_length = strlen(char_tnum);
|
||||
if (entry->length > name_length + char_tnum_length + 2 &&
|
||||
comment[name_length] == '[' &&
|
||||
g_ascii_strncasecmp(comment + name_length + 1,
|
||||
char_tnum, char_tnum_length) == 0 &&
|
||||
comment[name_length + char_tnum_length + 1] == ']')
|
||||
name_length = name_length + char_tnum_length + 2;
|
||||
else if (entry->length > name_length + char_tnum_length &&
|
||||
g_ascii_strncasecmp(comment + name_length,
|
||||
char_tnum, char_tnum_length) == 0)
|
||||
name_length = name_length + char_tnum_length;
|
||||
}
|
||||
|
||||
if (comment[name_length] == '=') {
|
||||
*length_r = entry->length - name_length - 1;
|
||||
return comment + name_length + 1;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the comment's name equals the passed name, and if so, copy
|
||||
* the comment value into the tag.
|
||||
*/
|
||||
static bool
|
||||
flac_copy_comment(struct tag *tag,
|
||||
const FLAC__StreamMetadata_VorbisComment_Entry *entry,
|
||||
const char *name, enum tag_type tag_type,
|
||||
const char *char_tnum)
|
||||
{
|
||||
const char *value;
|
||||
size_t value_length;
|
||||
|
||||
value = flac_comment_value(entry, name, char_tnum, &value_length);
|
||||
if (value != NULL) {
|
||||
tag_add_item_n(tag, tag_type, value, value_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* tracknumber is used in VCs, MPD uses "track" ..., all the other
|
||||
* tag names match */
|
||||
static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
|
||||
static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
|
||||
|
||||
static void
|
||||
flac_parse_comment(struct tag *tag, const char *char_tnum,
|
||||
const FLAC__StreamMetadata_VorbisComment_Entry *entry)
|
||||
{
|
||||
assert(tag != NULL);
|
||||
|
||||
if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
|
||||
TAG_ITEM_TRACK, char_tnum) ||
|
||||
flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
|
||||
TAG_ITEM_DISC, char_tnum) ||
|
||||
flac_copy_comment(tag, entry, "album artist",
|
||||
TAG_ITEM_ALBUM_ARTIST, char_tnum))
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
if (flac_copy_comment(tag, entry,
|
||||
tag_item_names[i], i, char_tnum))
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
|
||||
const FLAC__StreamMetadata *block)
|
||||
flac_data_deinit(struct flac_data *data)
|
||||
{
|
||||
FLAC__StreamMetadata_VorbisComment_Entry *comments =
|
||||
block->data.vorbis_comment.comments;
|
||||
pcm_buffer_deinit(&data->buffer);
|
||||
|
||||
for (unsigned i = block->data.vorbis_comment.num_comments; i > 0; --i)
|
||||
flac_parse_comment(tag, char_tnum, comments++);
|
||||
if (data->tag != NULL)
|
||||
tag_free(data->tag);
|
||||
}
|
||||
|
||||
static enum sample_format
|
||||
flac_sample_format(unsigned bits_per_sample)
|
||||
{
|
||||
switch (bits_per_sample) {
|
||||
case 8:
|
||||
return SAMPLE_FORMAT_S8;
|
||||
|
||||
case 16:
|
||||
return SAMPLE_FORMAT_S16;
|
||||
|
||||
case 24:
|
||||
return SAMPLE_FORMAT_S24_P32;
|
||||
|
||||
case 32:
|
||||
return SAMPLE_FORMAT_S32;
|
||||
|
||||
default:
|
||||
return SAMPLE_FORMAT_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
flac_got_stream_info(struct flac_data *data,
|
||||
const FLAC__StreamMetadata_StreamInfo *stream_info)
|
||||
{
|
||||
if (data->initialized || data->unsupported)
|
||||
return;
|
||||
|
||||
GError *error = NULL;
|
||||
if (!audio_format_init_checked(&data->audio_format,
|
||||
stream_info->sample_rate,
|
||||
flac_sample_format(stream_info->bits_per_sample),
|
||||
stream_info->channels, &error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
data->unsupported = true;
|
||||
return;
|
||||
}
|
||||
|
||||
data->frame_size = audio_format_frame_size(&data->audio_format);
|
||||
|
||||
if (data->total_frames == 0)
|
||||
data->total_frames = stream_info->total_samples;
|
||||
|
||||
data->initialized = true;
|
||||
}
|
||||
|
||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||
struct flac_data *data)
|
||||
{
|
||||
const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info);
|
||||
if (data->unsupported)
|
||||
return;
|
||||
|
||||
struct replay_gain_info rgi;
|
||||
char *mixramp_start;
|
||||
char *mixramp_end;
|
||||
float replay_gain_db = 0;
|
||||
|
||||
switch (block->type) {
|
||||
case FLAC__METADATA_TYPE_STREAMINFO:
|
||||
data->audio_format.bits = (int8_t)si->bits_per_sample;
|
||||
data->audio_format.sample_rate = si->sample_rate;
|
||||
data->audio_format.channels = (int8_t)si->channels;
|
||||
data->total_time = ((float)si->total_samples) / (si->sample_rate);
|
||||
flac_got_stream_info(data, &block->data.stream_info);
|
||||
break;
|
||||
|
||||
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
||||
flac_parse_replay_gain(block, data);
|
||||
if (flac_parse_replay_gain(&rgi, block))
|
||||
replay_gain_db = decoder_replay_gain(data->decoder, &rgi);
|
||||
if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) {
|
||||
g_debug("setting mixramp_tags");
|
||||
decoder_mixramp(data->decoder, replay_gain_db,
|
||||
mixramp_start, mixramp_end);
|
||||
}
|
||||
|
||||
if (data->tag != NULL)
|
||||
flac_vorbis_comments_to_tag(data->tag, NULL, block);
|
||||
flac_vorbis_comments_to_tag(data->tag, NULL,
|
||||
&block->data.vorbis_comment);
|
||||
|
||||
default:
|
||||
break;
|
||||
@@ -239,187 +161,82 @@ void flac_error_common_cb(const char *plugin,
|
||||
}
|
||||
}
|
||||
|
||||
static void flac_convert_stereo16(int16_t *dest,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
for (; position < end; ++position) {
|
||||
*dest++ = buf[0][position];
|
||||
*dest++ = buf[1][position];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
flac_convert_16(int16_t *dest,
|
||||
unsigned int num_channels,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
unsigned int c_chan;
|
||||
|
||||
for (; position < end; ++position)
|
||||
for (c_chan = 0; c_chan < num_channels; c_chan++)
|
||||
*dest++ = buf[c_chan][position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this function also handles 24 bit files!
|
||||
* This function attempts to call decoder_initialized() in case there
|
||||
* was no STREAMINFO block. This is allowed for nonseekable streams,
|
||||
* where the server sends us only a part of the file, without
|
||||
* providing the STREAMINFO block from the beginning of the file
|
||||
* (e.g. when seeking with SqueezeBox Server).
|
||||
*/
|
||||
static void
|
||||
flac_convert_32(int32_t *dest,
|
||||
unsigned int num_channels,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
static bool
|
||||
flac_got_first_frame(struct flac_data *data, const FLAC__FrameHeader *header)
|
||||
{
|
||||
unsigned int c_chan;
|
||||
if (data->unsupported)
|
||||
return false;
|
||||
|
||||
for (; position < end; ++position)
|
||||
for (c_chan = 0; c_chan < num_channels; c_chan++)
|
||||
*dest++ = buf[c_chan][position];
|
||||
}
|
||||
|
||||
static void
|
||||
flac_convert_8(int8_t *dest,
|
||||
unsigned int num_channels,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
unsigned int c_chan;
|
||||
|
||||
for (; position < end; ++position)
|
||||
for (c_chan = 0; c_chan < num_channels; c_chan++)
|
||||
*dest++ = buf[c_chan][position];
|
||||
}
|
||||
|
||||
static void flac_convert(unsigned char *dest,
|
||||
unsigned int num_channels,
|
||||
unsigned int bytes_per_sample,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
switch (bytes_per_sample) {
|
||||
case 2:
|
||||
if (num_channels == 2)
|
||||
flac_convert_stereo16((int16_t*)dest, buf,
|
||||
position, end);
|
||||
else
|
||||
flac_convert_16((int16_t*)dest, num_channels, buf,
|
||||
position, end);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
flac_convert_32((int32_t*)dest, num_channels, buf,
|
||||
position, end);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
flac_convert_8((int8_t*)dest, num_channels, buf,
|
||||
position, end);
|
||||
break;
|
||||
GError *error = NULL;
|
||||
if (!audio_format_init_checked(&data->audio_format,
|
||||
header->sample_rate,
|
||||
flac_sample_format(header->bits_per_sample),
|
||||
header->channels, &error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
data->unsupported = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
data->frame_size = audio_format_frame_size(&data->audio_format);
|
||||
|
||||
decoder_initialized(data->decoder, &data->audio_format,
|
||||
data->input_stream->seekable,
|
||||
(float)data->total_frames /
|
||||
(float)data->audio_format.sample_rate);
|
||||
|
||||
data->initialized = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FLAC__StreamDecoderWriteStatus
|
||||
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
||||
const FLAC__int32 *const buf[])
|
||||
const FLAC__int32 *const buf[],
|
||||
FLAC__uint64 nbytes)
|
||||
{
|
||||
unsigned int c_samp;
|
||||
const unsigned int num_channels = frame->header.channels;
|
||||
const unsigned int bytes_per_sample =
|
||||
audio_format_sample_size(&data->audio_format);
|
||||
const unsigned int bytes_per_channel =
|
||||
bytes_per_sample * frame->header.channels;
|
||||
const unsigned int max_samples = FLAC_CHUNK_SIZE / bytes_per_channel;
|
||||
unsigned int num_samples;
|
||||
enum decoder_command cmd;
|
||||
void *buffer;
|
||||
unsigned bit_rate;
|
||||
|
||||
if (bytes_per_sample != 1 && bytes_per_sample != 2 &&
|
||||
bytes_per_sample != 4)
|
||||
/* exotic unsupported bit rate */
|
||||
if (!data->initialized && !flac_got_first_frame(data, &frame->header))
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
|
||||
for (c_samp = 0; c_samp < frame->header.blocksize;
|
||||
c_samp += num_samples) {
|
||||
num_samples = frame->header.blocksize - c_samp;
|
||||
if (num_samples > max_samples)
|
||||
num_samples = max_samples;
|
||||
size_t buffer_size = frame->header.blocksize * data->frame_size;
|
||||
buffer = pcm_buffer_get(&data->buffer, buffer_size);
|
||||
|
||||
flac_convert(data->chunk,
|
||||
num_channels, bytes_per_sample, buf,
|
||||
c_samp, c_samp + num_samples);
|
||||
flac_convert(buffer, frame->header.channels,
|
||||
data->audio_format.format, buf,
|
||||
0, frame->header.blocksize);
|
||||
|
||||
cmd = decoder_data(data->decoder, data->input_stream,
|
||||
data->chunk,
|
||||
num_samples * bytes_per_channel,
|
||||
data->time, data->bit_rate,
|
||||
data->replay_gain_info);
|
||||
switch (cmd) {
|
||||
case DECODE_COMMAND_NONE:
|
||||
case DECODE_COMMAND_START:
|
||||
break;
|
||||
if (nbytes > 0)
|
||||
bit_rate = nbytes * 8 * frame->header.sample_rate /
|
||||
(1000 * frame->header.blocksize);
|
||||
else
|
||||
bit_rate = 0;
|
||||
|
||||
case DECODE_COMMAND_STOP:
|
||||
return
|
||||
FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
cmd = decoder_data(data->decoder, data->input_stream,
|
||||
buffer, buffer_size,
|
||||
bit_rate);
|
||||
data->next_frame += frame->header.blocksize;
|
||||
switch (cmd) {
|
||||
case DECODE_COMMAND_NONE:
|
||||
case DECODE_COMMAND_START:
|
||||
break;
|
||||
|
||||
case DECODE_COMMAND_SEEK:
|
||||
return
|
||||
FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
case DECODE_COMMAND_STOP:
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
|
||||
|
||||
case DECODE_COMMAND_SEEK:
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
|
||||
char*
|
||||
flac_cue_track( const char* pathname,
|
||||
const unsigned int tnum)
|
||||
{
|
||||
FLAC__bool success;
|
||||
FLAC__StreamMetadata* cs;
|
||||
|
||||
success = FLAC__metadata_get_cuesheet(pathname, &cs);
|
||||
if (!success)
|
||||
return NULL;
|
||||
|
||||
assert(cs != NULL);
|
||||
|
||||
if (cs->data.cue_sheet.num_tracks <= 1)
|
||||
{
|
||||
FLAC__metadata_object_delete(cs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tnum > 0 && tnum < cs->data.cue_sheet.num_tracks)
|
||||
{
|
||||
char* track = g_strdup_printf("track_%03u.flac", tnum);
|
||||
|
||||
FLAC__metadata_object_delete(cs);
|
||||
|
||||
return track;
|
||||
}
|
||||
else
|
||||
{
|
||||
FLAC__metadata_object_delete(cs);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int
|
||||
flac_vtrack_tnum(const char* fname)
|
||||
{
|
||||
/* find last occurrence of '_' in fname
|
||||
* which is hopefully something like track_xxx.flac
|
||||
* another/better way would be to use tag struct
|
||||
*/
|
||||
char* ptr = strrchr(fname, '_');
|
||||
if (ptr == NULL)
|
||||
return 0;
|
||||
|
||||
// copy ascii tracknumber to int
|
||||
return (unsigned int)strtol(++ptr, NULL, 10);
|
||||
}
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -24,136 +24,62 @@
|
||||
#ifndef MPD_FLAC_COMMON_H
|
||||
#define MPD_FLAC_COMMON_H
|
||||
|
||||
#include "../decoder_api.h"
|
||||
#include "config.h"
|
||||
#include "decoder_api.h"
|
||||
#include "pcm_buffer.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <FLAC/stream_decoder.h>
|
||||
#include <FLAC/metadata.h>
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "flac"
|
||||
|
||||
#include <FLAC/export.h>
|
||||
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
||||
# include <FLAC/seekable_stream_decoder.h>
|
||||
# define flac_decoder FLAC__SeekableStreamDecoder
|
||||
# define flac_new() FLAC__seekable_stream_decoder_new()
|
||||
|
||||
# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0)
|
||||
|
||||
# define flac_get_decode_position(x,y) \
|
||||
FLAC__seekable_stream_decoder_get_decode_position(x,y)
|
||||
# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x)
|
||||
# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x)
|
||||
# define flac_process_metadata(x) \
|
||||
FLAC__seekable_stream_decoder_process_until_end_of_metadata(x)
|
||||
# define flac_seek_absolute(x,y) \
|
||||
FLAC__seekable_stream_decoder_seek_absolute(x,y)
|
||||
# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x)
|
||||
# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x)
|
||||
|
||||
# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
|
||||
|
||||
typedef unsigned flac_read_status_size_t;
|
||||
# define flac_read_status FLAC__SeekableStreamDecoderReadStatus
|
||||
# define flac_read_status_continue \
|
||||
FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
|
||||
# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
|
||||
# define flac_read_status_abort \
|
||||
FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
|
||||
|
||||
# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus
|
||||
# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
|
||||
# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
|
||||
|
||||
# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus
|
||||
# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
|
||||
# define flac_tell_status_error \
|
||||
FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
|
||||
# define flac_tell_status_unsupported \
|
||||
FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
|
||||
|
||||
# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus
|
||||
# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
|
||||
# define flac_length_status_error \
|
||||
FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
|
||||
# define flac_length_status_unsupported \
|
||||
FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
|
||||
|
||||
# ifdef HAVE_OGGFLAC
|
||||
# include <OggFLAC/seekable_stream_decoder.h>
|
||||
# endif
|
||||
#else /* FLAC_API_VERSION_CURRENT > 7 */
|
||||
|
||||
/*
|
||||
* OggFLAC support is handled by our flac_plugin already, and
|
||||
* thus we *can* always have it if libFLAC was compiled with it
|
||||
*/
|
||||
# include "_ogg_common.h"
|
||||
|
||||
# include <FLAC/stream_decoder.h>
|
||||
# define flac_decoder FLAC__StreamDecoder
|
||||
# define flac_new() FLAC__stream_decoder_new()
|
||||
|
||||
# define flac_init(a,b,c,d,e,f,g,h,i,j) \
|
||||
(FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
|
||||
== FLAC__STREAM_DECODER_INIT_STATUS_OK)
|
||||
# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \
|
||||
(FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \
|
||||
== FLAC__STREAM_DECODER_INIT_STATUS_OK)
|
||||
|
||||
# define flac_get_decode_position(x,y) \
|
||||
FLAC__stream_decoder_get_decode_position(x,y)
|
||||
# define flac_get_state(x) FLAC__stream_decoder_get_state(x)
|
||||
# define flac_process_single(x) FLAC__stream_decoder_process_single(x)
|
||||
# define flac_process_metadata(x) \
|
||||
FLAC__stream_decoder_process_until_end_of_metadata(x)
|
||||
# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y)
|
||||
# define flac_finish(x) FLAC__stream_decoder_finish(x)
|
||||
# define flac_delete(x) FLAC__stream_decoder_delete(x)
|
||||
|
||||
# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM
|
||||
|
||||
typedef size_t flac_read_status_size_t;
|
||||
# define flac_read_status FLAC__StreamDecoderReadStatus
|
||||
# define flac_read_status_continue \
|
||||
FLAC__STREAM_DECODER_READ_STATUS_CONTINUE
|
||||
# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM
|
||||
# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT
|
||||
|
||||
# define flac_seek_status FLAC__StreamDecoderSeekStatus
|
||||
# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK
|
||||
# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR
|
||||
# define flac_seek_status_unsupported \
|
||||
FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED
|
||||
|
||||
# define flac_tell_status FLAC__StreamDecoderTellStatus
|
||||
# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK
|
||||
# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR
|
||||
# define flac_tell_status_unsupported \
|
||||
FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED
|
||||
|
||||
# define flac_length_status FLAC__StreamDecoderLengthStatus
|
||||
# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK
|
||||
# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR
|
||||
# define flac_length_status_unsupported \
|
||||
FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
#include <FLAC/metadata.h>
|
||||
|
||||
#define FLAC_CHUNK_SIZE 4080
|
||||
|
||||
struct flac_data {
|
||||
unsigned char chunk[FLAC_CHUNK_SIZE];
|
||||
float time;
|
||||
unsigned int bit_rate;
|
||||
struct pcm_buffer buffer;
|
||||
|
||||
/**
|
||||
* The size of one frame in the output buffer.
|
||||
*/
|
||||
unsigned frame_size;
|
||||
|
||||
/**
|
||||
* Has decoder_initialized() been called yet?
|
||||
*/
|
||||
bool initialized;
|
||||
|
||||
/**
|
||||
* Does the FLAC file contain an unsupported audio format?
|
||||
*/
|
||||
bool unsupported;
|
||||
|
||||
/**
|
||||
* The validated audio format of the FLAC file. This
|
||||
* attribute is defined if "initialized" is true.
|
||||
*/
|
||||
struct audio_format audio_format;
|
||||
float total_time;
|
||||
|
||||
/**
|
||||
* The total number of frames in this song. The decoder
|
||||
* plugin may initialize this attribute to override the value
|
||||
* provided by libFLAC (e.g. for sub songs from a CUE sheet).
|
||||
*/
|
||||
FLAC__uint64 total_frames;
|
||||
|
||||
/**
|
||||
* The number of the first frame in this song. This is only
|
||||
* non-zero if playing sub songs from a CUE sheet.
|
||||
*/
|
||||
FLAC__uint64 first_frame;
|
||||
|
||||
/**
|
||||
* The number of the next frame which is going to be decoded.
|
||||
*/
|
||||
FLAC__uint64 next_frame;
|
||||
|
||||
FLAC__uint64 position;
|
||||
struct decoder *decoder;
|
||||
struct input_stream *input_stream;
|
||||
struct replay_gain_info *replay_gain_info;
|
||||
struct tag *tag;
|
||||
};
|
||||
|
||||
@@ -162,6 +88,9 @@ void
|
||||
flac_data_init(struct flac_data *data, struct decoder * decoder,
|
||||
struct input_stream *input_stream);
|
||||
|
||||
void
|
||||
flac_data_deinit(struct flac_data *data);
|
||||
|
||||
void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
|
||||
struct flac_data *data);
|
||||
|
||||
@@ -169,23 +98,9 @@ void flac_error_common_cb(const char *plugin,
|
||||
FLAC__StreamDecoderErrorStatus status,
|
||||
struct flac_data *data);
|
||||
|
||||
void
|
||||
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
|
||||
const FLAC__StreamMetadata *block);
|
||||
|
||||
FLAC__StreamDecoderWriteStatus
|
||||
flac_common_write(struct flac_data *data, const FLAC__Frame * frame,
|
||||
const FLAC__int32 *const buf[]);
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
|
||||
char*
|
||||
flac_cue_track( const char* pathname,
|
||||
const unsigned int tnum);
|
||||
|
||||
unsigned int
|
||||
flac_vtrack_tnum( const char*);
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
const FLAC__int32 *const buf[],
|
||||
FLAC__uint64 nbytes);
|
||||
|
||||
#endif /* _FLAC_COMMON_H */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -21,8 +21,8 @@
|
||||
* Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "_ogg_common.h"
|
||||
#include "../utils.h"
|
||||
|
||||
ogg_stream_type ogg_stream_type_detect(struct input_stream *inStream)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -24,7 +24,7 @@
|
||||
#ifndef MPD_OGG_COMMON_H
|
||||
#define MPD_OGG_COMMON_H
|
||||
|
||||
#include "../decoder_api.h"
|
||||
#include "decoder_api.h"
|
||||
|
||||
typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type;
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,7 +17,9 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "../decoder_api.h"
|
||||
#include "config.h"
|
||||
#include "decoder_api.h"
|
||||
#include "audio_check.h"
|
||||
|
||||
#include <audiofile.h>
|
||||
#include <af_vfs.h>
|
||||
@@ -45,10 +47,20 @@ static int audiofile_get_duration(const char *file)
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t nbytes)
|
||||
audiofile_file_read(AFvirtualfile *vfile, void *data, size_t length)
|
||||
{
|
||||
struct input_stream *is = (struct input_stream *) vfile->closure;
|
||||
return input_stream_read(is, data, nbytes);
|
||||
GError *error = NULL;
|
||||
size_t nbytes;
|
||||
|
||||
nbytes = input_stream_read(is, data, length, &error);
|
||||
if (nbytes == 0 && error != NULL) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return nbytes;
|
||||
}
|
||||
|
||||
static long
|
||||
@@ -78,7 +90,7 @@ audiofile_file_seek(AFvirtualfile *vfile, long offset, int is_relative)
|
||||
{
|
||||
struct input_stream *is = (struct input_stream *) vfile->closure;
|
||||
int whence = (is_relative ? SEEK_CUR : SEEK_SET);
|
||||
if (input_stream_seek(is, offset, whence)) {
|
||||
if (input_stream_seek(is, offset, whence, NULL)) {
|
||||
return is->offset;
|
||||
} else {
|
||||
return -1;
|
||||
@@ -99,17 +111,56 @@ setup_virtual_fops(struct input_stream *stream)
|
||||
return vf;
|
||||
}
|
||||
|
||||
static enum sample_format
|
||||
audiofile_bits_to_sample_format(int bits)
|
||||
{
|
||||
switch (bits) {
|
||||
case 8:
|
||||
return SAMPLE_FORMAT_S8;
|
||||
|
||||
case 16:
|
||||
return SAMPLE_FORMAT_S16;
|
||||
|
||||
case 24:
|
||||
return SAMPLE_FORMAT_S24_P32;
|
||||
|
||||
case 32:
|
||||
return SAMPLE_FORMAT_S32;
|
||||
}
|
||||
|
||||
return SAMPLE_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
static enum sample_format
|
||||
audiofile_setup_sample_format(AFfilehandle af_fp)
|
||||
{
|
||||
int fs, bits;
|
||||
|
||||
afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
|
||||
if (!audio_valid_sample_format(audiofile_bits_to_sample_format(bits))) {
|
||||
g_debug("input file has %d bit samples, converting to 16",
|
||||
bits);
|
||||
bits = 16;
|
||||
}
|
||||
|
||||
afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
|
||||
AF_SAMPFMT_TWOSCOMP, bits);
|
||||
afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
|
||||
|
||||
return audiofile_bits_to_sample_format(bits);
|
||||
}
|
||||
|
||||
static void
|
||||
audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
|
||||
{
|
||||
GError *error = NULL;
|
||||
AFvirtualfile *vf;
|
||||
int fs, frame_count;
|
||||
AFfilehandle af_fp;
|
||||
int bits;
|
||||
struct audio_format audio_format;
|
||||
float total_time;
|
||||
uint16_t bit_rate;
|
||||
int ret, current = 0;
|
||||
int ret;
|
||||
char chunk[CHUNK_SIZE];
|
||||
enum decoder_command cmd;
|
||||
|
||||
@@ -126,26 +177,13 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
|
||||
return;
|
||||
}
|
||||
|
||||
afGetSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
|
||||
if (!audio_valid_sample_format(bits)) {
|
||||
g_debug("input file has %d bit samples, converting to 16",
|
||||
bits);
|
||||
bits = 16;
|
||||
}
|
||||
|
||||
afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK,
|
||||
AF_SAMPFMT_TWOSCOMP, bits);
|
||||
afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits);
|
||||
audio_format.bits = (uint8_t)bits;
|
||||
audio_format.sample_rate =
|
||||
(unsigned int)afGetRate(af_fp, AF_DEFAULT_TRACK);
|
||||
audio_format.channels =
|
||||
(uint8_t)afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK);
|
||||
|
||||
if (!audio_format_valid(&audio_format)) {
|
||||
g_warning("Invalid audio format: %u:%u:%u\n",
|
||||
audio_format.sample_rate, audio_format.bits,
|
||||
audio_format.channels);
|
||||
if (!audio_format_init_checked(&audio_format,
|
||||
afGetRate(af_fp, AF_DEFAULT_TRACK),
|
||||
audiofile_setup_sample_format(af_fp),
|
||||
afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK),
|
||||
&error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
afCloseFile(af_fp);
|
||||
return;
|
||||
}
|
||||
@@ -166,17 +204,14 @@ audiofile_stream_decode(struct decoder *decoder, struct input_stream *is)
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
current += ret;
|
||||
cmd = decoder_data(decoder, NULL,
|
||||
chunk, ret * fs,
|
||||
(float)current /
|
||||
(float)audio_format.sample_rate,
|
||||
bit_rate, NULL);
|
||||
bit_rate);
|
||||
|
||||
if (cmd == DECODE_COMMAND_SEEK) {
|
||||
current = decoder_seek_where(decoder) *
|
||||
AFframecount frame = decoder_seek_where(decoder) *
|
||||
audio_format.sample_rate;
|
||||
afSeekFrame(af_fp, AF_DEFAULT_TRACK, current);
|
||||
afSeekFrame(af_fp, AF_DEFAULT_TRACK, frame);
|
||||
|
||||
decoder_command_finished(decoder);
|
||||
cmd = DECODE_COMMAND_NONE;
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,9 +17,10 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "../decoder_api.h"
|
||||
#include "decoder_buffer.h"
|
||||
#include "config.h"
|
||||
#include "decoder_api.h"
|
||||
#include "decoder_buffer.h"
|
||||
#include "audio_check.h"
|
||||
|
||||
#define AAC_MAX_CHANNELS 6
|
||||
|
||||
@@ -36,6 +37,15 @@ static const unsigned adts_sample_rates[] =
|
||||
16000, 12000, 11025, 8000, 7350, 0, 0, 0
|
||||
};
|
||||
|
||||
/**
|
||||
* The GLib quark used for errors reported by this plugin.
|
||||
*/
|
||||
static inline GQuark
|
||||
faad_decoder_quark(void)
|
||||
{
|
||||
return g_quark_from_static_string("faad");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the buffer head is an AAC frame, and return the frame
|
||||
* length. Returns 0 if it is not a frame.
|
||||
@@ -195,7 +205,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
|
||||
/* obtain the duration from the ADTS header */
|
||||
float song_length = adts_song_duration(buffer);
|
||||
|
||||
input_stream_seek(is, tagsize, SEEK_SET);
|
||||
input_stream_seek(is, tagsize, SEEK_SET, NULL);
|
||||
|
||||
data = decoder_buffer_read(buffer, &length);
|
||||
if (data != NULL)
|
||||
@@ -232,7 +242,7 @@ faad_song_duration(struct decoder_buffer *buffer, struct input_stream *is)
|
||||
*/
|
||||
static bool
|
||||
faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
|
||||
struct audio_format *audio_format)
|
||||
struct audio_format *audio_format, GError **error_r)
|
||||
{
|
||||
union {
|
||||
/* deconst hack for libfaad */
|
||||
@@ -247,32 +257,33 @@ faad_decoder_init(faacDecHandle decoder, struct decoder_buffer *buffer,
|
||||
/* neaacdec.h declares all arguments as "unsigned long", but
|
||||
internally expects uint32_t pointers. To avoid gcc
|
||||
warnings, use this workaround. */
|
||||
unsigned long *sample_rate_r = (unsigned long *)(void *)&sample_rate;
|
||||
unsigned long *sample_rate_p = (unsigned long *)(void *)&sample_rate;
|
||||
#else
|
||||
uint32_t *sample_rate_r = &sample_rate;
|
||||
uint32_t *sample_rate_p = &sample_rate;
|
||||
#endif
|
||||
|
||||
u.in = decoder_buffer_read(buffer, &length);
|
||||
if (u.in == NULL)
|
||||
if (u.in == NULL) {
|
||||
g_set_error(error_r, faad_decoder_quark(), 0,
|
||||
"Empty file");
|
||||
return false;
|
||||
}
|
||||
|
||||
nbytes = faacDecInit(decoder, u.out,
|
||||
#ifdef HAVE_FAAD_BUFLEN_FUNCS
|
||||
length,
|
||||
#endif
|
||||
sample_rate_r, &channels);
|
||||
if (nbytes < 0)
|
||||
sample_rate_p, &channels);
|
||||
if (nbytes < 0) {
|
||||
g_set_error(error_r, faad_decoder_quark(), 0,
|
||||
"Not an AAC stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
decoder_buffer_consume(buffer, nbytes);
|
||||
|
||||
*audio_format = (struct audio_format){
|
||||
.bits = 16,
|
||||
.channels = channels,
|
||||
.sample_rate = sample_rate,
|
||||
};
|
||||
|
||||
return true;
|
||||
return audio_format_init_checked(audio_format, sample_rate,
|
||||
SAMPLE_FORMAT_S16, channels, error_r);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -311,20 +322,16 @@ faad_decoder_decode(faacDecHandle decoder, struct decoder_buffer *buffer,
|
||||
* file is invalid.
|
||||
*/
|
||||
static float
|
||||
faad_get_file_time_float(const char *file)
|
||||
faad_get_file_time_float(struct input_stream *is)
|
||||
{
|
||||
struct decoder_buffer *buffer;
|
||||
float length;
|
||||
faacDecHandle decoder;
|
||||
faacDecConfigurationPtr config;
|
||||
struct input_stream is;
|
||||
|
||||
if (!input_stream_open(&is, file))
|
||||
return -1;
|
||||
|
||||
buffer = decoder_buffer_new(NULL, &is,
|
||||
buffer = decoder_buffer_new(NULL, is,
|
||||
FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS);
|
||||
length = faad_song_duration(buffer, &is);
|
||||
length = faad_song_duration(buffer, is);
|
||||
|
||||
if (length < 0) {
|
||||
bool ret;
|
||||
@@ -338,15 +345,14 @@ faad_get_file_time_float(const char *file)
|
||||
|
||||
decoder_buffer_fill(buffer);
|
||||
|
||||
ret = faad_decoder_init(decoder, buffer, &audio_format);
|
||||
if (ret && audio_format_valid(&audio_format))
|
||||
ret = faad_decoder_init(decoder, buffer, &audio_format, NULL);
|
||||
if (ret)
|
||||
length = 0;
|
||||
|
||||
faacDecClose(decoder);
|
||||
}
|
||||
|
||||
decoder_buffer_free(buffer);
|
||||
input_stream_close(&is);
|
||||
|
||||
return length;
|
||||
}
|
||||
@@ -357,12 +363,12 @@ faad_get_file_time_float(const char *file)
|
||||
* file is invalid.
|
||||
*/
|
||||
static int
|
||||
faad_get_file_time(const char *file)
|
||||
faad_get_file_time(struct input_stream *is)
|
||||
{
|
||||
int file_time = -1;
|
||||
float length;
|
||||
|
||||
if ((length = faad_get_file_time_float(file)) >= 0)
|
||||
if ((length = faad_get_file_time_float(is)) >= 0)
|
||||
file_time = length + 0.5;
|
||||
|
||||
return file_time;
|
||||
@@ -371,7 +377,7 @@ faad_get_file_time(const char *file)
|
||||
static void
|
||||
faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
|
||||
{
|
||||
float file_time;
|
||||
GError *error = NULL;
|
||||
float total_time = 0;
|
||||
faacDecHandle decoder;
|
||||
struct audio_format audio_format;
|
||||
@@ -408,15 +414,10 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
|
||||
|
||||
/* initialize it */
|
||||
|
||||
ret = faad_decoder_init(decoder, buffer, &audio_format);
|
||||
ret = faad_decoder_init(decoder, buffer, &audio_format, &error);
|
||||
if (!ret) {
|
||||
g_warning("Error not a AAC stream.\n");
|
||||
faacDecClose(decoder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!audio_format_valid(&audio_format)) {
|
||||
g_warning("invalid audio format\n");
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
faacDecClose(decoder);
|
||||
return;
|
||||
}
|
||||
@@ -427,8 +428,6 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
|
||||
|
||||
/* the decoder loop */
|
||||
|
||||
file_time = 0.0;
|
||||
|
||||
do {
|
||||
size_t frame_size;
|
||||
const void *decoded;
|
||||
@@ -474,16 +473,13 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
|
||||
bit_rate = frame_info.bytesconsumed * 8.0 *
|
||||
frame_info.channels * audio_format.sample_rate /
|
||||
frame_info.samples / 1000 + 0.5;
|
||||
file_time +=
|
||||
(float)(frame_info.samples) / frame_info.channels /
|
||||
audio_format.sample_rate;
|
||||
}
|
||||
|
||||
/* send PCM samples to MPD */
|
||||
|
||||
cmd = decoder_data(mpd_decoder, is, decoded,
|
||||
(size_t)frame_info.samples * 2, file_time,
|
||||
bit_rate, NULL);
|
||||
(size_t)frame_info.samples * 2,
|
||||
bit_rate);
|
||||
} while (cmd != DECODE_COMMAND_STOP);
|
||||
|
||||
/* cleanup */
|
||||
@@ -492,15 +488,13 @@ faad_stream_decode(struct decoder *mpd_decoder, struct input_stream *is)
|
||||
}
|
||||
|
||||
static struct tag *
|
||||
faad_tag_dup(const char *file)
|
||||
faad_stream_tag(struct input_stream *is)
|
||||
{
|
||||
int file_time = faad_get_file_time(file);
|
||||
int file_time = faad_get_file_time(is);
|
||||
struct tag *tag;
|
||||
|
||||
if (file_time < 0) {
|
||||
g_debug("Failed to get total song time from: %s", file);
|
||||
if (file_time < 0)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tag = tag_new();
|
||||
tag->time = file_time;
|
||||
@@ -515,7 +509,7 @@ static const char *const faad_mime_types[] = {
|
||||
const struct decoder_plugin faad_decoder_plugin = {
|
||||
.name = "faad",
|
||||
.stream_decode = faad_stream_decode,
|
||||
.tag_dup = faad_tag_dup,
|
||||
.stream_tag = faad_stream_tag,
|
||||
.suffixes = faad_suffixes,
|
||||
.mime_types = faad_mime_types,
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,8 +17,9 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "../decoder_api.h"
|
||||
#include "config.h"
|
||||
#include "decoder_api.h"
|
||||
#include "audio_check.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
@@ -39,93 +40,114 @@
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libavutil/log.h>
|
||||
#endif
|
||||
|
||||
#undef G_LOG_DOMAIN
|
||||
#define G_LOG_DOMAIN "ffmpeg"
|
||||
|
||||
struct ffmpeg_context {
|
||||
int audio_stream;
|
||||
AVFormatContext *format_context;
|
||||
AVCodecContext *codec_context;
|
||||
struct decoder *decoder;
|
||||
struct input_stream *input;
|
||||
struct tag *tag;
|
||||
};
|
||||
#ifndef OLD_FFMPEG_INCLUDES
|
||||
|
||||
struct ffmpeg_stream {
|
||||
/** hack - see url_to_struct() */
|
||||
char url[64];
|
||||
|
||||
struct decoder *decoder;
|
||||
struct input_stream *input;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a faked mpd:// URL to a ffmpeg_stream structure. This is a
|
||||
* hack because ffmpeg does not provide a nice API for passing a
|
||||
* user-defined pointer to mpdurl_open().
|
||||
*/
|
||||
static struct ffmpeg_stream *url_to_struct(const char *url)
|
||||
static GLogLevelFlags
|
||||
level_ffmpeg_to_glib(int level)
|
||||
{
|
||||
union {
|
||||
const char *in;
|
||||
struct ffmpeg_stream *out;
|
||||
} u = { .in = url };
|
||||
return u.out;
|
||||
if (level <= AV_LOG_FATAL)
|
||||
return G_LOG_LEVEL_CRITICAL;
|
||||
|
||||
if (level <= AV_LOG_ERROR)
|
||||
return G_LOG_LEVEL_WARNING;
|
||||
|
||||
if (level <= AV_LOG_INFO)
|
||||
return G_LOG_LEVEL_MESSAGE;
|
||||
|
||||
return G_LOG_LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
static int mpd_ffmpeg_open(URLContext *h, const char *filename,
|
||||
G_GNUC_UNUSED int flags)
|
||||
static void
|
||||
mpd_ffmpeg_log_callback(G_GNUC_UNUSED void *ptr, int level,
|
||||
const char *fmt, va_list vl)
|
||||
{
|
||||
struct ffmpeg_stream *stream = url_to_struct(filename);
|
||||
h->priv_data = stream;
|
||||
h->is_streamed = stream->input->seekable ? 0 : 1;
|
||||
return 0;
|
||||
const AVClass * cls = NULL;
|
||||
|
||||
if (ptr != NULL)
|
||||
cls = *(const AVClass *const*)ptr;
|
||||
|
||||
if (cls != NULL) {
|
||||
char *domain = g_strconcat(G_LOG_DOMAIN, "/", cls->item_name(ptr), NULL);
|
||||
g_logv(domain, level_ffmpeg_to_glib(level), fmt, vl);
|
||||
g_free(domain);
|
||||
}
|
||||
}
|
||||
|
||||
static int mpd_ffmpeg_read(URLContext *h, unsigned char *buf, int size)
|
||||
#endif /* !OLD_FFMPEG_INCLUDES */
|
||||
|
||||
struct mpd_ffmpeg_stream {
|
||||
struct decoder *decoder;
|
||||
struct input_stream *input;
|
||||
|
||||
ByteIOContext *io;
|
||||
unsigned char buffer[8192];
|
||||
};
|
||||
|
||||
static int
|
||||
mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size)
|
||||
{
|
||||
struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data;
|
||||
struct mpd_ffmpeg_stream *stream = opaque;
|
||||
|
||||
return decoder_read(stream->decoder, stream->input,
|
||||
(void *)buf, size);
|
||||
}
|
||||
|
||||
static int64_t mpd_ffmpeg_seek(URLContext *h, int64_t pos, int whence)
|
||||
static int64_t
|
||||
mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence)
|
||||
{
|
||||
struct ffmpeg_stream *stream = (struct ffmpeg_stream *) h->priv_data;
|
||||
struct mpd_ffmpeg_stream *stream = opaque;
|
||||
bool ret;
|
||||
|
||||
if (whence == AVSEEK_SIZE)
|
||||
return stream->input->size;
|
||||
|
||||
ret = input_stream_seek(stream->input, pos, whence);
|
||||
ret = input_stream_seek(stream->input, pos, whence, NULL);
|
||||
if (!ret)
|
||||
return -1;
|
||||
|
||||
return stream->input->offset;
|
||||
}
|
||||
|
||||
static int mpd_ffmpeg_close(URLContext *h)
|
||||
static struct mpd_ffmpeg_stream *
|
||||
mpd_ffmpeg_stream_open(struct decoder *decoder, struct input_stream *input)
|
||||
{
|
||||
h->priv_data = NULL;
|
||||
return 0;
|
||||
struct mpd_ffmpeg_stream *stream = g_new(struct mpd_ffmpeg_stream, 1);
|
||||
stream->decoder = decoder;
|
||||
stream->input = input;
|
||||
stream->io = av_alloc_put_byte(stream->buffer, sizeof(stream->buffer),
|
||||
false, stream,
|
||||
mpd_ffmpeg_stream_read, NULL,
|
||||
input->seekable
|
||||
? mpd_ffmpeg_stream_seek : NULL);
|
||||
if (stream->io == NULL) {
|
||||
g_free(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
static URLProtocol mpd_ffmpeg_fileops = {
|
||||
.name = "mpd",
|
||||
.url_open = mpd_ffmpeg_open,
|
||||
.url_read = mpd_ffmpeg_read,
|
||||
.url_seek = mpd_ffmpeg_seek,
|
||||
.url_close = mpd_ffmpeg_close,
|
||||
};
|
||||
static void
|
||||
mpd_ffmpeg_stream_close(struct mpd_ffmpeg_stream *stream)
|
||||
{
|
||||
av_free(stream->io);
|
||||
g_free(stream);
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_init(G_GNUC_UNUSED const struct config_param *param)
|
||||
{
|
||||
#ifndef OLD_FFMPEG_INCLUDES
|
||||
av_log_set_callback(mpd_ffmpeg_log_callback);
|
||||
#endif
|
||||
|
||||
av_register_all();
|
||||
register_protocol(&mpd_ffmpeg_fileops);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -140,98 +162,6 @@ ffmpeg_find_audio_stream(const AVFormatContext *format_context)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append the suffix of the original URI to the virtual stream URI.
|
||||
* Without this, libavformat cannot detect some of the codecs
|
||||
* (e.g. "shorten").
|
||||
*/
|
||||
static void
|
||||
append_uri_suffix(struct ffmpeg_stream *stream, const char *uri)
|
||||
{
|
||||
assert(stream != NULL);
|
||||
assert(uri != NULL);
|
||||
|
||||
char *base = g_path_get_basename(uri);
|
||||
|
||||
const char *suffix = strrchr(base, '.');
|
||||
if (suffix != NULL && suffix[1] != 0)
|
||||
g_strlcat(stream->url, suffix, sizeof(stream->url));
|
||||
|
||||
g_free(base);
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_helper(const char *uri, struct input_stream *input,
|
||||
bool (*callback)(struct ffmpeg_context *ctx),
|
||||
struct ffmpeg_context *ctx)
|
||||
{
|
||||
AVFormatContext *format_context;
|
||||
AVCodecContext *codec_context;
|
||||
AVCodec *codec;
|
||||
int audio_stream;
|
||||
struct ffmpeg_stream stream = {
|
||||
.url = "mpd://X", /* only the mpd:// prefix matters */
|
||||
};
|
||||
bool ret;
|
||||
|
||||
if (uri != NULL)
|
||||
append_uri_suffix(&stream, uri);
|
||||
|
||||
stream.input = input;
|
||||
if (ctx && ctx->decoder) {
|
||||
stream.decoder = ctx->decoder; //are we in decoding loop ?
|
||||
} else {
|
||||
stream.decoder = NULL;
|
||||
}
|
||||
|
||||
//ffmpeg works with ours "fileops" helper
|
||||
if (av_open_input_file(&format_context, stream.url, NULL, 0, NULL) != 0) {
|
||||
g_warning("Open failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (av_find_stream_info(format_context)<0) {
|
||||
g_warning("Couldn't find stream info\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
audio_stream = ffmpeg_find_audio_stream(format_context);
|
||||
if (audio_stream == -1) {
|
||||
g_warning("No audio stream inside\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
codec_context = format_context->streams[audio_stream]->codec;
|
||||
if (codec_context->codec_name[0] != 0)
|
||||
g_debug("codec '%s'", codec_context->codec_name);
|
||||
|
||||
codec = avcodec_find_decoder(codec_context->codec_id);
|
||||
|
||||
if (!codec) {
|
||||
g_warning("Unsupported audio codec\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (avcodec_open(codec_context, codec)<0) {
|
||||
g_warning("Could not open codec\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
ctx->audio_stream = audio_stream;
|
||||
ctx->format_context = format_context;
|
||||
ctx->codec_context = codec_context;
|
||||
|
||||
ret = callback(ctx);
|
||||
} else
|
||||
ret = true;
|
||||
|
||||
avcodec_close(codec_context);
|
||||
av_close_input_file(format_context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* On some platforms, libavcodec wants the output buffer aligned to 16
|
||||
* bytes (because it uses SSE/Altivec internally). This function
|
||||
@@ -254,7 +184,6 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
const AVRational *time_base)
|
||||
{
|
||||
enum decoder_command cmd = DECODE_COMMAND_NONE;
|
||||
int position;
|
||||
uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2 + 16];
|
||||
int16_t *aligned_buffer;
|
||||
size_t buffer_size;
|
||||
@@ -262,6 +191,11 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
uint8_t *packet_data;
|
||||
int packet_size;
|
||||
|
||||
if (packet->pts != (int64_t)AV_NOPTS_VALUE)
|
||||
decoder_timestamp(decoder,
|
||||
av_rescale_q(packet->pts, *time_base,
|
||||
(AVRational){1, 1}));
|
||||
|
||||
packet_data = packet->data;
|
||||
packet_size = packet->size;
|
||||
|
||||
@@ -286,65 +220,163 @@ ffmpeg_send_packet(struct decoder *decoder, struct input_stream *is,
|
||||
if (audio_size <= 0)
|
||||
continue;
|
||||
|
||||
position = packet->pts != (int64_t)AV_NOPTS_VALUE
|
||||
? av_rescale_q(packet->pts, *time_base,
|
||||
(AVRational){1, 1})
|
||||
: 0;
|
||||
|
||||
cmd = decoder_data(decoder, is,
|
||||
aligned_buffer, audio_size,
|
||||
position,
|
||||
codec_context->bit_rate / 1000, NULL);
|
||||
codec_context->bit_rate / 1000);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
|
||||
static bool
|
||||
ffmpeg_decode_internal(struct ffmpeg_context *ctx)
|
||||
static enum sample_format
|
||||
ffmpeg_sample_format(G_GNUC_UNUSED const AVCodecContext *codec_context)
|
||||
{
|
||||
struct decoder *decoder = ctx->decoder;
|
||||
AVCodecContext *codec_context = ctx->codec_context;
|
||||
AVFormatContext *format_context = ctx->format_context;
|
||||
AVPacket packet;
|
||||
struct audio_format audio_format;
|
||||
enum decoder_command cmd;
|
||||
int total_time;
|
||||
|
||||
total_time = 0;
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= ((51<<16)+(41<<8)+0)
|
||||
audio_format.bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
|
||||
int bits = (uint8_t) av_get_bits_per_sample_format(codec_context->sample_fmt);
|
||||
|
||||
/* XXX implement & test other sample formats */
|
||||
|
||||
switch (bits) {
|
||||
case 16:
|
||||
return SAMPLE_FORMAT_S16;
|
||||
}
|
||||
|
||||
return SAMPLE_FORMAT_UNDEFINED;
|
||||
#else
|
||||
/* XXX fixme 16-bit for older ffmpeg (13 Aug 2007) */
|
||||
audio_format.bits = (uint8_t) 16;
|
||||
return SAMPLE_FORMAT_S16;
|
||||
#endif
|
||||
audio_format.sample_rate = (unsigned int)codec_context->sample_rate;
|
||||
audio_format.channels = codec_context->channels;
|
||||
}
|
||||
|
||||
if (!audio_format_valid(&audio_format)) {
|
||||
g_warning("Invalid audio format: %u:%u:%u\n",
|
||||
audio_format.sample_rate, audio_format.bits,
|
||||
audio_format.channels);
|
||||
return false;
|
||||
static AVInputFormat *
|
||||
ffmpeg_probe(struct decoder *decoder, struct input_stream *is)
|
||||
{
|
||||
enum {
|
||||
BUFFER_SIZE = 16384,
|
||||
PADDING = 16,
|
||||
};
|
||||
|
||||
unsigned char *buffer = g_malloc(BUFFER_SIZE);
|
||||
size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE);
|
||||
if (nbytes <= PADDING || !input_stream_seek(is, 0, SEEK_SET, NULL)) {
|
||||
g_free(buffer);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//there is some problem with this on some demux (mp3 at least)
|
||||
if (format_context->duration != (int64_t)AV_NOPTS_VALUE) {
|
||||
total_time = format_context->duration / AV_TIME_BASE;
|
||||
/* some ffmpeg parsers (e.g. ac3_parser.c) read a few bytes
|
||||
beyond the declared buffer limit, which makes valgrind
|
||||
angry; this workaround removes some padding from the buffer
|
||||
size */
|
||||
nbytes -= PADDING;
|
||||
|
||||
AVProbeData avpd = {
|
||||
.buf = buffer,
|
||||
.buf_size = nbytes,
|
||||
.filename = is->uri,
|
||||
};
|
||||
|
||||
AVInputFormat *format = av_probe_input_format(&avpd, true);
|
||||
g_free(buffer);
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
static void
|
||||
ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
{
|
||||
AVInputFormat *input_format = ffmpeg_probe(decoder, input);
|
||||
if (input_format == NULL)
|
||||
return;
|
||||
|
||||
g_debug("detected input format '%s' (%s)",
|
||||
input_format->name, input_format->long_name);
|
||||
|
||||
struct mpd_ffmpeg_stream *stream =
|
||||
mpd_ffmpeg_stream_open(decoder, input);
|
||||
if (stream == NULL) {
|
||||
g_warning("Failed to open stream");
|
||||
return;
|
||||
}
|
||||
|
||||
AVFormatContext *format_context;
|
||||
AVCodecContext *codec_context;
|
||||
AVCodec *codec;
|
||||
int audio_stream;
|
||||
|
||||
//ffmpeg works with ours "fileops" helper
|
||||
if (av_open_input_stream(&format_context, stream->io, input->uri,
|
||||
input_format, NULL) != 0) {
|
||||
g_warning("Open failed\n");
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (av_find_stream_info(format_context)<0) {
|
||||
g_warning("Couldn't find stream info\n");
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
audio_stream = ffmpeg_find_audio_stream(format_context);
|
||||
if (audio_stream == -1) {
|
||||
g_warning("No audio stream inside\n");
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
codec_context = format_context->streams[audio_stream]->codec;
|
||||
if (codec_context->codec_name[0] != 0)
|
||||
g_debug("codec '%s'", codec_context->codec_name);
|
||||
|
||||
codec = avcodec_find_decoder(codec_context->codec_id);
|
||||
|
||||
if (!codec) {
|
||||
g_warning("Unsupported audio codec\n");
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (avcodec_open(codec_context, codec)<0) {
|
||||
g_warning("Could not open codec\n");
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
GError *error = NULL;
|
||||
struct audio_format audio_format;
|
||||
if (!audio_format_init_checked(&audio_format,
|
||||
codec_context->sample_rate,
|
||||
ffmpeg_sample_format(codec_context),
|
||||
codec_context->channels, &error)) {
|
||||
g_warning("%s", error->message);
|
||||
g_error_free(error);
|
||||
avcodec_close(codec_context);
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return;
|
||||
}
|
||||
|
||||
int total_time = format_context->duration != (int64_t)AV_NOPTS_VALUE
|
||||
? format_context->duration / AV_TIME_BASE
|
||||
: 0;
|
||||
|
||||
decoder_initialized(decoder, &audio_format,
|
||||
ctx->input->seekable, total_time);
|
||||
input->seekable, total_time);
|
||||
|
||||
enum decoder_command cmd;
|
||||
do {
|
||||
AVPacket packet;
|
||||
if (av_read_frame(format_context, &packet) < 0)
|
||||
/* end of file */
|
||||
break;
|
||||
|
||||
if (packet.stream_index == ctx->audio_stream)
|
||||
cmd = ffmpeg_send_packet(decoder, ctx->input,
|
||||
if (packet.stream_index == audio_stream)
|
||||
cmd = ffmpeg_send_packet(decoder, input,
|
||||
&packet, codec_context,
|
||||
&format_context->streams[ctx->audio_stream]->time_base);
|
||||
&format_context->streams[audio_stream]->time_base);
|
||||
else
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
@@ -361,104 +393,121 @@ ffmpeg_decode_internal(struct ffmpeg_context *ctx)
|
||||
}
|
||||
} while (cmd != DECODE_COMMAND_STOP);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
ffmpeg_decode(struct decoder *decoder, struct input_stream *input)
|
||||
{
|
||||
struct ffmpeg_context ctx;
|
||||
|
||||
ctx.input = input;
|
||||
ctx.decoder = decoder;
|
||||
|
||||
ffmpeg_helper(decoder_get_uri(decoder), input,
|
||||
ffmpeg_decode_internal, &ctx);
|
||||
avcodec_close(codec_context);
|
||||
av_close_input_stream(format_context);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
}
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
|
||||
typedef struct ffmpeg_tag_map {
|
||||
enum tag_type type;
|
||||
const char *name;
|
||||
} ffmpeg_tag_map;
|
||||
|
||||
static const ffmpeg_tag_map ffmpeg_tag_maps[] = {
|
||||
{ TAG_TITLE, "title" },
|
||||
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(50<<8))
|
||||
{ TAG_ARTIST, "artist" },
|
||||
{ TAG_DATE, "date" },
|
||||
#else
|
||||
{ TAG_ARTIST, "author" },
|
||||
{ TAG_DATE, "year" },
|
||||
#endif
|
||||
{ TAG_ALBUM, "album" },
|
||||
{ TAG_COMMENT, "comment" },
|
||||
{ TAG_GENRE, "genre" },
|
||||
{ TAG_TRACK, "track" },
|
||||
{ TAG_ARTIST_SORT, "author-sort" },
|
||||
{ TAG_ALBUM_ARTIST, "album_artist" },
|
||||
{ TAG_ALBUM_ARTIST_SORT, "album_artist-sort" },
|
||||
{ TAG_COMPOSER, "composer" },
|
||||
{ TAG_PERFORMER, "performer" },
|
||||
{ TAG_DISC, "disc" },
|
||||
};
|
||||
|
||||
static bool
|
||||
ffmpeg_copy_metadata(struct tag *tag, AVMetadata *m,
|
||||
enum tag_type type, const char *name)
|
||||
const ffmpeg_tag_map tag_map)
|
||||
{
|
||||
AVMetadataTag *mt = av_metadata_get(m, name, NULL, 0);
|
||||
if (mt != NULL)
|
||||
tag_add_item(tag, type, mt->value);
|
||||
AVMetadataTag *mt = NULL;
|
||||
|
||||
while ((mt = av_metadata_get(m, tag_map.name, mt, 0)) != NULL)
|
||||
tag_add_item(tag, tag_map.type, mt->value);
|
||||
return mt != NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static bool ffmpeg_tag_internal(struct ffmpeg_context *ctx)
|
||||
//no tag reading in ffmpeg, check if playable
|
||||
static struct tag *
|
||||
ffmpeg_stream_tag(struct input_stream *is)
|
||||
{
|
||||
struct tag *tag = (struct tag *) ctx->tag;
|
||||
AVFormatContext *f = ctx->format_context;
|
||||
AVInputFormat *input_format = ffmpeg_probe(NULL, is);
|
||||
if (input_format == NULL)
|
||||
return NULL;
|
||||
|
||||
tag->time = 0;
|
||||
if (f->duration != (int64_t)AV_NOPTS_VALUE)
|
||||
tag->time = f->duration / AV_TIME_BASE;
|
||||
struct mpd_ffmpeg_stream *stream = mpd_ffmpeg_stream_open(NULL, is);
|
||||
if (stream == NULL)
|
||||
return NULL;
|
||||
|
||||
AVFormatContext *f;
|
||||
if (av_open_input_stream(&f, stream->io, is->uri,
|
||||
input_format, NULL) != 0) {
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (av_find_stream_info(f) < 0) {
|
||||
av_close_input_stream(f);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct tag *tag = tag_new();
|
||||
|
||||
tag->time = f->duration != (int64_t)AV_NOPTS_VALUE
|
||||
? f->duration / AV_TIME_BASE
|
||||
: 0;
|
||||
|
||||
#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0)
|
||||
av_metadata_conv(f, NULL, f->iformat->metadata_conv);
|
||||
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TITLE, "title");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ARTIST, "author");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_ALBUM, "album");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_COMMENT, "comment");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_GENRE, "genre");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_TRACK, "track");
|
||||
ffmpeg_copy_metadata(tag, f->metadata, TAG_ITEM_DATE, "year");
|
||||
for (unsigned i = 0; i < sizeof(ffmpeg_tag_maps)/sizeof(ffmpeg_tag_map); i++) {
|
||||
int idx = ffmpeg_find_audio_stream(f);
|
||||
ffmpeg_copy_metadata(tag, f->metadata, ffmpeg_tag_maps[i]);
|
||||
if (idx >= 0)
|
||||
ffmpeg_copy_metadata(tag, f->streams[idx]->metadata, ffmpeg_tag_maps[i]);
|
||||
}
|
||||
#else
|
||||
if (f->author[0])
|
||||
tag_add_item(tag, TAG_ITEM_ARTIST, f->author);
|
||||
tag_add_item(tag, TAG_ARTIST, f->author);
|
||||
if (f->title[0])
|
||||
tag_add_item(tag, TAG_ITEM_TITLE, f->title);
|
||||
tag_add_item(tag, TAG_TITLE, f->title);
|
||||
if (f->album[0])
|
||||
tag_add_item(tag, TAG_ITEM_ALBUM, f->album);
|
||||
tag_add_item(tag, TAG_ALBUM, f->album);
|
||||
|
||||
if (f->track > 0) {
|
||||
char buffer[16];
|
||||
snprintf(buffer, sizeof(buffer), "%d", f->track);
|
||||
tag_add_item(tag, TAG_ITEM_TRACK, buffer);
|
||||
tag_add_item(tag, TAG_TRACK, buffer);
|
||||
}
|
||||
|
||||
if (f->comment[0])
|
||||
tag_add_item(tag, TAG_ITEM_COMMENT, f->comment);
|
||||
tag_add_item(tag, TAG_COMMENT, f->comment);
|
||||
if (f->genre[0])
|
||||
tag_add_item(tag, TAG_ITEM_GENRE, f->genre);
|
||||
tag_add_item(tag, TAG_GENRE, f->genre);
|
||||
if (f->year > 0) {
|
||||
char buffer[16];
|
||||
snprintf(buffer, sizeof(buffer), "%d", f->year);
|
||||
tag_add_item(tag, TAG_ITEM_DATE, buffer);
|
||||
tag_add_item(tag, TAG_DATE, buffer);
|
||||
}
|
||||
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
//no tag reading in ffmpeg, check if playable
|
||||
static struct tag *ffmpeg_tag(const char *file)
|
||||
{
|
||||
struct input_stream input;
|
||||
struct ffmpeg_context ctx;
|
||||
bool ret;
|
||||
av_close_input_stream(f);
|
||||
mpd_ffmpeg_stream_close(stream);
|
||||
|
||||
if (!input_stream_open(&input, file)) {
|
||||
g_warning("failed to open %s\n", file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ctx.decoder = NULL;
|
||||
ctx.tag = tag_new();
|
||||
|
||||
ret = ffmpeg_helper(file, &input, ffmpeg_tag_internal, &ctx);
|
||||
if (!ret) {
|
||||
tag_free(ctx.tag);
|
||||
ctx.tag = NULL;
|
||||
}
|
||||
|
||||
input_stream_close(&input);
|
||||
|
||||
return ctx.tag;
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -562,6 +611,12 @@ static const char *const ffmpeg_mime_types[] = {
|
||||
"video/x-vid",
|
||||
"video/x-wmv",
|
||||
"video/x-xvid",
|
||||
|
||||
/* special value for the "ffmpeg" input plugin: all streams by
|
||||
the "ffmpeg" input plugin shall be decoded by this
|
||||
plugin */
|
||||
"audio/x-mpd-ffmpeg",
|
||||
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -569,7 +624,7 @@ const struct decoder_plugin ffmpeg_decoder_plugin = {
|
||||
.name = "ffmpeg",
|
||||
.init = ffmpeg_init,
|
||||
.stream_decode = ffmpeg_decode,
|
||||
.tag_dup = ffmpeg_tag,
|
||||
.stream_tag = ffmpeg_stream_tag,
|
||||
.suffixes = ffmpeg_suffixes,
|
||||
.mime_types = ffmpeg_mime_types
|
||||
};
|
||||
114
src/decoder/flac_compat.h
Normal file
114
src/decoder/flac_compat.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Common data structures and functions used by FLAC and OggFLAC
|
||||
*/
|
||||
|
||||
#ifndef MPD_FLAC_COMPAT_H
|
||||
#define MPD_FLAC_COMPAT_H
|
||||
|
||||
#include <FLAC/export.h>
|
||||
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
||||
# include <FLAC/seekable_stream_decoder.h>
|
||||
|
||||
/* starting with libFLAC 1.1.3, the SeekableStreamDecoder has been
|
||||
merged into the StreamDecoder. The following macros try to emulate
|
||||
the new API for libFLAC 1.1.2 by mapping MPD's StreamDecoder calls
|
||||
to the old SeekableStreamDecoder API. */
|
||||
|
||||
#define FLAC__StreamDecoder FLAC__SeekableStreamDecoder
|
||||
#define FLAC__stream_decoder_new FLAC__seekable_stream_decoder_new
|
||||
#define FLAC__stream_decoder_get_decode_position FLAC__seekable_stream_decoder_get_decode_position
|
||||
#define FLAC__stream_decoder_get_state FLAC__seekable_stream_decoder_get_state
|
||||
#define FLAC__stream_decoder_process_single FLAC__seekable_stream_decoder_process_single
|
||||
#define FLAC__stream_decoder_process_until_end_of_metadata FLAC__seekable_stream_decoder_process_until_end_of_metadata
|
||||
#define FLAC__stream_decoder_seek_absolute FLAC__seekable_stream_decoder_seek_absolute
|
||||
#define FLAC__stream_decoder_finish FLAC__seekable_stream_decoder_finish
|
||||
#define FLAC__stream_decoder_delete FLAC__seekable_stream_decoder_delete
|
||||
|
||||
#define FLAC__STREAM_DECODER_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM
|
||||
|
||||
typedef unsigned flac_read_status_size_t;
|
||||
|
||||
#define FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus
|
||||
#define FLAC__STREAM_DECODER_READ_STATUS_CONTINUE FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
|
||||
#define FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK
|
||||
#define FLAC__STREAM_DECODER_READ_STATUS_ABORT FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR
|
||||
|
||||
#define FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus
|
||||
#define FLAC__STREAM_DECODER_SEEK_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK
|
||||
#define FLAC__STREAM_DECODER_SEEK_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
|
||||
#define FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR
|
||||
|
||||
#define FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus
|
||||
#define FLAC__STREAM_DECODER_TELL_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK
|
||||
#define FLAC__STREAM_DECODER_TELL_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
|
||||
#define FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR
|
||||
|
||||
#define FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus
|
||||
#define FLAC__STREAM_DECODER_LENGTH_STATUS_OK FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK
|
||||
#define FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
|
||||
#define FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR
|
||||
|
||||
typedef enum {
|
||||
FLAC__STREAM_DECODER_INIT_STATUS_OK,
|
||||
FLAC__STREAM_DECODER_INIT_STATUS_ERROR,
|
||||
} FLAC__StreamDecoderInitStatus;
|
||||
|
||||
static inline FLAC__StreamDecoderInitStatus
|
||||
FLAC__stream_decoder_init_stream(FLAC__SeekableStreamDecoder *decoder,
|
||||
FLAC__SeekableStreamDecoderReadCallback read_cb,
|
||||
FLAC__SeekableStreamDecoderSeekCallback seek_cb,
|
||||
FLAC__SeekableStreamDecoderTellCallback tell_cb,
|
||||
FLAC__SeekableStreamDecoderLengthCallback length_cb,
|
||||
FLAC__SeekableStreamDecoderEofCallback eof_cb,
|
||||
FLAC__SeekableStreamDecoderWriteCallback write_cb,
|
||||
FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
|
||||
FLAC__SeekableStreamDecoderErrorCallback error_cb,
|
||||
void *data)
|
||||
{
|
||||
return FLAC__seekable_stream_decoder_set_read_callback(decoder, read_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_seek_callback(decoder, seek_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_tell_callback(decoder, tell_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_length_callback(decoder, length_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_eof_callback(decoder, eof_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_write_callback(decoder, write_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_metadata_callback(decoder, metadata_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
|
||||
FLAC__seekable_stream_decoder_set_error_callback(decoder, error_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_client_data(decoder, data) &&
|
||||
FLAC__seekable_stream_decoder_init(decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK
|
||||
? FLAC__STREAM_DECODER_INIT_STATUS_OK
|
||||
: FLAC__STREAM_DECODER_INIT_STATUS_ERROR;
|
||||
}
|
||||
|
||||
#else /* FLAC_API_VERSION_CURRENT > 7 */
|
||||
|
||||
# include <FLAC/stream_decoder.h>
|
||||
|
||||
# define flac_init(a,b,c,d,e,f,g,h,i,j) \
|
||||
(FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \
|
||||
== FLAC__STREAM_DECODER_INIT_STATUS_OK)
|
||||
|
||||
typedef size_t flac_read_status_size_t;
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
#endif /* _FLAC_COMMON_H */
|
||||
497
src/decoder/flac_decoder_plugin.c
Normal file
497
src/decoder/flac_decoder_plugin.c
Normal file
@@ -0,0 +1,497 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h" /* must be first for large file support */
|
||||
#include "_flac_common.h"
|
||||
#include "flac_compat.h"
|
||||
#include "flac_metadata.h"
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
#include "_ogg_common.h"
|
||||
#endif
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
/* this code was based on flac123, from flac-tools */
|
||||
|
||||
static FLAC__StreamDecoderReadStatus
|
||||
flac_read_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
|
||||
FLAC__byte buf[], flac_read_status_size_t *bytes,
|
||||
void *fdata)
|
||||
{
|
||||
struct flac_data *data = fdata;
|
||||
size_t r;
|
||||
|
||||
r = decoder_read(data->decoder, data->input_stream,
|
||||
(void *)buf, *bytes);
|
||||
*bytes = r;
|
||||
|
||||
if (r == 0) {
|
||||
if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
|
||||
input_stream_eof(data->input_stream))
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM;
|
||||
else
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_ABORT;
|
||||
}
|
||||
|
||||
return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderSeekStatus
|
||||
flac_seek_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
|
||||
FLAC__uint64 offset, void *fdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) fdata;
|
||||
|
||||
if (!data->input_stream->seekable)
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
|
||||
|
||||
if (!input_stream_seek(data->input_stream, offset, SEEK_SET, NULL))
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR;
|
||||
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderTellStatus
|
||||
flac_tell_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
|
||||
FLAC__uint64 * offset, void *fdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) fdata;
|
||||
|
||||
if (!data->input_stream->seekable)
|
||||
return FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED;
|
||||
|
||||
*offset = (long)(data->input_stream->offset);
|
||||
|
||||
return FLAC__STREAM_DECODER_TELL_STATUS_OK;
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderLengthStatus
|
||||
flac_length_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
|
||||
FLAC__uint64 * length, void *fdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) fdata;
|
||||
|
||||
if (data->input_stream->size < 0)
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED;
|
||||
|
||||
*length = (size_t) (data->input_stream->size);
|
||||
|
||||
return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
|
||||
}
|
||||
|
||||
static FLAC__bool
|
||||
flac_eof_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd, void *fdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) fdata;
|
||||
|
||||
return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
|
||||
decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
|
||||
input_stream_eof(data->input_stream);
|
||||
}
|
||||
|
||||
static void
|
||||
flac_error_cb(G_GNUC_UNUSED const FLAC__StreamDecoder *fd,
|
||||
FLAC__StreamDecoderErrorStatus status, void *fdata)
|
||||
{
|
||||
flac_error_common_cb("flac", status, (struct flac_data *) fdata);
|
||||
}
|
||||
|
||||
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
||||
static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
|
||||
{
|
||||
const char *str = ""; /* "" to silence compiler warning */
|
||||
switch (state) {
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_OK:
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_SEEKING:
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
|
||||
return;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
|
||||
str = "allocation error";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
|
||||
str = "read error";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
|
||||
str = "seek error";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
|
||||
str = "seekable stream error";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
|
||||
str = "decoder already initialized";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
|
||||
str = "invalid callback";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
|
||||
str = "decoder uninitialized";
|
||||
}
|
||||
|
||||
g_warning("%s\n", str);
|
||||
}
|
||||
#else /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
static void flacPrintErroredState(FLAC__StreamDecoderState state)
|
||||
{
|
||||
const char *str = ""; /* "" to silence compiler warning */
|
||||
switch (state) {
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
|
||||
case FLAC__STREAM_DECODER_READ_METADATA:
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
|
||||
case FLAC__STREAM_DECODER_READ_FRAME:
|
||||
case FLAC__STREAM_DECODER_END_OF_STREAM:
|
||||
return;
|
||||
case FLAC__STREAM_DECODER_OGG_ERROR:
|
||||
str = "error in the Ogg layer";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_SEEK_ERROR:
|
||||
str = "seek error";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_ABORTED:
|
||||
str = "decoder aborted by read";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
|
||||
str = "allocation error";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_UNINITIALIZED:
|
||||
str = "decoder uninitialized";
|
||||
}
|
||||
|
||||
g_warning("%s\n", str);
|
||||
}
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
static void flacMetadata(G_GNUC_UNUSED const FLAC__StreamDecoder * dec,
|
||||
const FLAC__StreamMetadata * block, void *vdata)
|
||||
{
|
||||
flac_metadata_common_cb(block, (struct flac_data *) vdata);
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderWriteStatus
|
||||
flac_write_cb(const FLAC__StreamDecoder *dec, const FLAC__Frame *frame,
|
||||
const FLAC__int32 *const buf[], void *vdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) vdata;
|
||||
FLAC__uint64 nbytes = 0;
|
||||
|
||||
if (FLAC__stream_decoder_get_decode_position(dec, &nbytes)) {
|
||||
if (data->position > 0 && nbytes > data->position) {
|
||||
nbytes -= data->position;
|
||||
data->position += nbytes;
|
||||
} else {
|
||||
data->position = nbytes;
|
||||
nbytes = 0;
|
||||
}
|
||||
} else
|
||||
nbytes = 0;
|
||||
|
||||
return flac_common_write(data, frame, buf, nbytes);
|
||||
}
|
||||
|
||||
static struct tag *
|
||||
flac_tag_dup(const char *file)
|
||||
{
|
||||
return flac_tag_load(file, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Some glue code around FLAC__stream_decoder_new().
|
||||
*/
|
||||
static FLAC__StreamDecoder *
|
||||
flac_decoder_new(void)
|
||||
{
|
||||
FLAC__StreamDecoder *sd = FLAC__stream_decoder_new();
|
||||
if (sd == NULL) {
|
||||
g_warning("FLAC__stream_decoder_new() failed");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
if(!FLAC__stream_decoder_set_metadata_respond(sd, FLAC__METADATA_TYPE_VORBIS_COMMENT))
|
||||
g_debug("FLAC__stream_decoder_set_metadata_respond() has failed");
|
||||
#endif
|
||||
|
||||
return sd;
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
|
||||
FLAC__uint64 duration)
|
||||
{
|
||||
data->total_frames = duration;
|
||||
|
||||
if (!FLAC__stream_decoder_process_until_end_of_metadata(sd)) {
|
||||
g_warning("problem reading metadata");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->initialized) {
|
||||
/* done */
|
||||
decoder_initialized(data->decoder, &data->audio_format,
|
||||
data->input_stream->seekable,
|
||||
(float)data->total_frames /
|
||||
(float)data->audio_format.sample_rate);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (data->input_stream->seekable)
|
||||
/* allow the workaround below only for nonseekable
|
||||
streams*/
|
||||
return false;
|
||||
|
||||
/* no stream_info packet found; try to initialize the decoder
|
||||
from the first frame header */
|
||||
FLAC__stream_decoder_process_single(sd);
|
||||
return data->initialized;
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decoder_loop(struct flac_data *data, FLAC__StreamDecoder *flac_dec,
|
||||
FLAC__uint64 t_start, FLAC__uint64 t_end)
|
||||
{
|
||||
struct decoder *decoder = data->decoder;
|
||||
enum decoder_command cmd;
|
||||
|
||||
data->first_frame = t_start;
|
||||
|
||||
while (true) {
|
||||
if (data->tag != NULL && !tag_is_empty(data->tag)) {
|
||||
cmd = decoder_tag(data->decoder, data->input_stream,
|
||||
data->tag);
|
||||
tag_free(data->tag);
|
||||
data->tag = tag_new();
|
||||
} else
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
if (cmd == DECODE_COMMAND_SEEK) {
|
||||
FLAC__uint64 seek_sample = t_start +
|
||||
decoder_seek_where(decoder) *
|
||||
data->audio_format.sample_rate;
|
||||
if (seek_sample >= t_start &&
|
||||
(t_end == 0 || seek_sample <= t_end) &&
|
||||
FLAC__stream_decoder_seek_absolute(flac_dec, seek_sample)) {
|
||||
data->next_frame = seek_sample;
|
||||
data->position = 0;
|
||||
decoder_command_finished(decoder);
|
||||
} else
|
||||
decoder_seek_error(decoder);
|
||||
} else if (cmd == DECODE_COMMAND_STOP ||
|
||||
FLAC__stream_decoder_get_state(flac_dec) == FLAC__STREAM_DECODER_END_OF_STREAM)
|
||||
break;
|
||||
|
||||
if (t_end != 0 && data->next_frame >= t_end)
|
||||
/* end of this sub track */
|
||||
break;
|
||||
|
||||
if (!FLAC__stream_decoder_process_single(flac_dec)) {
|
||||
cmd = decoder_get_command(decoder);
|
||||
if (cmd != DECODE_COMMAND_SEEK)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd != DECODE_COMMAND_STOP) {
|
||||
flacPrintErroredState(FLAC__stream_decoder_get_state(flac_dec));
|
||||
FLAC__stream_decoder_finish(flac_dec);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decode_internal(struct decoder * decoder,
|
||||
struct input_stream *input_stream,
|
||||
bool is_ogg)
|
||||
{
|
||||
FLAC__StreamDecoder *flac_dec;
|
||||
struct flac_data data;
|
||||
const char *err = NULL;
|
||||
|
||||
flac_dec = flac_decoder_new();
|
||||
if (flac_dec == NULL)
|
||||
return;
|
||||
|
||||
flac_data_init(&data, decoder, input_stream);
|
||||
data.tag = tag_new();
|
||||
|
||||
if (is_ogg) {
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
FLAC__StreamDecoderInitStatus status =
|
||||
FLAC__stream_decoder_init_ogg_stream(flac_dec,
|
||||
flac_read_cb,
|
||||
flac_seek_cb,
|
||||
flac_tell_cb,
|
||||
flac_length_cb,
|
||||
flac_eof_cb,
|
||||
flac_write_cb,
|
||||
flacMetadata,
|
||||
flac_error_cb,
|
||||
(void *)&data);
|
||||
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
err = "doing Ogg init()";
|
||||
goto fail;
|
||||
}
|
||||
#else
|
||||
goto fail;
|
||||
#endif
|
||||
} else {
|
||||
FLAC__StreamDecoderInitStatus status =
|
||||
FLAC__stream_decoder_init_stream(flac_dec,
|
||||
flac_read_cb,
|
||||
flac_seek_cb,
|
||||
flac_tell_cb,
|
||||
flac_length_cb,
|
||||
flac_eof_cb,
|
||||
flac_write_cb,
|
||||
flacMetadata,
|
||||
flac_error_cb,
|
||||
(void *)&data);
|
||||
if (status != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
|
||||
err = "doing init()";
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flac_decoder_initialize(&data, flac_dec, 0)) {
|
||||
flac_data_deinit(&data);
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
return;
|
||||
}
|
||||
|
||||
flac_decoder_loop(&data, flac_dec, 0, 0);
|
||||
|
||||
fail:
|
||||
flac_data_deinit(&data);
|
||||
FLAC__stream_decoder_delete(flac_dec);
|
||||
|
||||
if (err)
|
||||
g_warning("%s\n", err);
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decode(struct decoder * decoder, struct input_stream *input_stream)
|
||||
{
|
||||
flac_decode_internal(decoder, input_stream, false);
|
||||
}
|
||||
|
||||
#ifndef HAVE_OGGFLAC
|
||||
|
||||
static bool
|
||||
oggflac_init(G_GNUC_UNUSED const struct config_param *param)
|
||||
{
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
return !!FLAC_API_SUPPORTS_OGG_FLAC;
|
||||
#else
|
||||
/* disable oggflac when libflac is too old */
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
|
||||
static struct tag *
|
||||
oggflac_tag_dup(const char *file)
|
||||
{
|
||||
struct tag *ret = NULL;
|
||||
FLAC__Metadata_Iterator *it;
|
||||
FLAC__StreamMetadata *block;
|
||||
FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
|
||||
|
||||
if (!(FLAC__metadata_chain_read_ogg(chain, file)))
|
||||
goto out;
|
||||
it = FLAC__metadata_iterator_new();
|
||||
FLAC__metadata_iterator_init(it, chain);
|
||||
|
||||
ret = tag_new();
|
||||
do {
|
||||
if (!(block = FLAC__metadata_iterator_get_block(it)))
|
||||
break;
|
||||
|
||||
flac_tag_apply_metadata(ret, NULL, block);
|
||||
} while (FLAC__metadata_iterator_next(it));
|
||||
FLAC__metadata_iterator_delete(it);
|
||||
|
||||
if (!tag_is_defined(ret)) {
|
||||
tag_free(ret);
|
||||
ret = NULL;
|
||||
}
|
||||
|
||||
out:
|
||||
FLAC__metadata_chain_delete(chain);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
|
||||
{
|
||||
if (ogg_stream_type_detect(input_stream) != FLAC)
|
||||
return;
|
||||
|
||||
/* rewind the stream, because ogg_stream_type_detect() has
|
||||
moved it */
|
||||
input_stream_seek(input_stream, 0, SEEK_SET, NULL);
|
||||
|
||||
flac_decode_internal(decoder, input_stream, true);
|
||||
}
|
||||
|
||||
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
|
||||
static const char *const oggflac_mime_types[] = {
|
||||
"application/ogg",
|
||||
"application/x-ogg",
|
||||
"audio/ogg",
|
||||
"audio/x-flac+ogg",
|
||||
"audio/x-ogg",
|
||||
NULL
|
||||
};
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
const struct decoder_plugin oggflac_decoder_plugin = {
|
||||
.name = "oggflac",
|
||||
.init = oggflac_init,
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
.stream_decode = oggflac_decode,
|
||||
.tag_dup = oggflac_tag_dup,
|
||||
.suffixes = oggflac_suffixes,
|
||||
.mime_types = oggflac_mime_types
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* HAVE_OGGFLAC */
|
||||
|
||||
static const char *const flac_suffixes[] = { "flac", NULL };
|
||||
static const char *const flac_mime_types[] = {
|
||||
"application/flac",
|
||||
"application/x-flac",
|
||||
"audio/flac",
|
||||
"audio/x-flac",
|
||||
NULL
|
||||
};
|
||||
|
||||
const struct decoder_plugin flac_decoder_plugin = {
|
||||
.name = "flac",
|
||||
.stream_decode = flac_decode,
|
||||
.tag_dup = flac_tag_dup,
|
||||
.suffixes = flac_suffixes,
|
||||
.mime_types = flac_mime_types,
|
||||
};
|
||||
289
src/decoder/flac_metadata.c
Normal file
289
src/decoder/flac_metadata.c
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "flac_metadata.h"
|
||||
#include "replay_gain_info.h"
|
||||
#include "tag.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static bool
|
||||
flac_find_float_comment(const FLAC__StreamMetadata *block,
|
||||
const char *cmnt, float *fl)
|
||||
{
|
||||
int offset;
|
||||
size_t pos;
|
||||
int len;
|
||||
unsigned char tmp, *p;
|
||||
|
||||
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
|
||||
cmnt);
|
||||
if (offset < 0)
|
||||
return false;
|
||||
|
||||
pos = strlen(cmnt) + 1; /* 1 is for '=' */
|
||||
len = block->data.vorbis_comment.comments[offset].length - pos;
|
||||
if (len <= 0)
|
||||
return false;
|
||||
|
||||
p = &block->data.vorbis_comment.comments[offset].entry[pos];
|
||||
tmp = p[len];
|
||||
p[len] = '\0';
|
||||
*fl = (float)atof((char *)p);
|
||||
p[len] = tmp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
flac_parse_replay_gain(struct replay_gain_info *rgi,
|
||||
const FLAC__StreamMetadata *block)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
replay_gain_info_init(rgi);
|
||||
|
||||
if (flac_find_float_comment(block, "replaygain_album_gain",
|
||||
&rgi->tuples[REPLAY_GAIN_ALBUM].gain))
|
||||
found = true;
|
||||
if (flac_find_float_comment(block, "replaygain_album_peak",
|
||||
&rgi->tuples[REPLAY_GAIN_ALBUM].peak))
|
||||
found = true;
|
||||
if (flac_find_float_comment(block, "replaygain_track_gain",
|
||||
&rgi->tuples[REPLAY_GAIN_TRACK].gain))
|
||||
found = true;
|
||||
if (flac_find_float_comment(block, "replaygain_track_peak",
|
||||
&rgi->tuples[REPLAY_GAIN_TRACK].peak))
|
||||
found = true;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_find_string_comment(const FLAC__StreamMetadata *block,
|
||||
const char *cmnt, char **str)
|
||||
{
|
||||
int offset;
|
||||
size_t pos;
|
||||
int len;
|
||||
unsigned char tmp, *p;
|
||||
|
||||
*str = NULL;
|
||||
offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
|
||||
cmnt);
|
||||
if (offset < 0)
|
||||
return false;
|
||||
|
||||
pos = strlen(cmnt) + 1; /* 1 is for '=' */
|
||||
len = block->data.vorbis_comment.comments[offset].length - pos;
|
||||
if (len <= 0)
|
||||
return false;
|
||||
|
||||
p = &block->data.vorbis_comment.comments[offset].entry[pos];
|
||||
tmp = p[len];
|
||||
p[len] = '\0';
|
||||
*str = strdup((char *)p);
|
||||
p[len] = tmp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
|
||||
const FLAC__StreamMetadata *block)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
if (flac_find_string_comment(block, "mixramp_start", mixramp_start))
|
||||
found = true;
|
||||
if (flac_find_string_comment(block, "mixramp_end", mixramp_end))
|
||||
found = true;
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the specified name matches the entry's name, and if yes,
|
||||
* returns the comment value (not null-temrinated).
|
||||
*/
|
||||
static const char *
|
||||
flac_comment_value(const FLAC__StreamMetadata_VorbisComment_Entry *entry,
|
||||
const char *name, const char *char_tnum, size_t *length_r)
|
||||
{
|
||||
size_t name_length = strlen(name);
|
||||
size_t char_tnum_length = 0;
|
||||
const char *comment = (const char*)entry->entry;
|
||||
|
||||
if (entry->length <= name_length ||
|
||||
g_ascii_strncasecmp(comment, name, name_length) != 0)
|
||||
return NULL;
|
||||
|
||||
if (char_tnum != NULL) {
|
||||
char_tnum_length = strlen(char_tnum);
|
||||
if (entry->length > name_length + char_tnum_length + 2 &&
|
||||
comment[name_length] == '[' &&
|
||||
g_ascii_strncasecmp(comment + name_length + 1,
|
||||
char_tnum, char_tnum_length) == 0 &&
|
||||
comment[name_length + char_tnum_length + 1] == ']')
|
||||
name_length = name_length + char_tnum_length + 2;
|
||||
else if (entry->length > name_length + char_tnum_length &&
|
||||
g_ascii_strncasecmp(comment + name_length,
|
||||
char_tnum, char_tnum_length) == 0)
|
||||
name_length = name_length + char_tnum_length;
|
||||
}
|
||||
|
||||
if (comment[name_length] == '=') {
|
||||
*length_r = entry->length - name_length - 1;
|
||||
return comment + name_length + 1;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the comment's name equals the passed name, and if so, copy
|
||||
* the comment value into the tag.
|
||||
*/
|
||||
static bool
|
||||
flac_copy_comment(struct tag *tag,
|
||||
const FLAC__StreamMetadata_VorbisComment_Entry *entry,
|
||||
const char *name, enum tag_type tag_type,
|
||||
const char *char_tnum)
|
||||
{
|
||||
const char *value;
|
||||
size_t value_length;
|
||||
|
||||
value = flac_comment_value(entry, name, char_tnum, &value_length);
|
||||
if (value != NULL) {
|
||||
tag_add_item_n(tag, tag_type, value, value_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* tracknumber is used in VCs, MPD uses "track" ..., all the other
|
||||
* tag names match */
|
||||
static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber";
|
||||
static const char *VORBIS_COMMENT_DISC_KEY = "discnumber";
|
||||
|
||||
static void
|
||||
flac_parse_comment(struct tag *tag, const char *char_tnum,
|
||||
const FLAC__StreamMetadata_VorbisComment_Entry *entry)
|
||||
{
|
||||
assert(tag != NULL);
|
||||
|
||||
if (flac_copy_comment(tag, entry, VORBIS_COMMENT_TRACK_KEY,
|
||||
TAG_TRACK, char_tnum) ||
|
||||
flac_copy_comment(tag, entry, VORBIS_COMMENT_DISC_KEY,
|
||||
TAG_DISC, char_tnum) ||
|
||||
flac_copy_comment(tag, entry, "album artist",
|
||||
TAG_ALBUM_ARTIST, char_tnum))
|
||||
return;
|
||||
|
||||
for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i)
|
||||
if (flac_copy_comment(tag, entry,
|
||||
tag_item_names[i], i, char_tnum))
|
||||
return;
|
||||
}
|
||||
|
||||
void
|
||||
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
|
||||
const FLAC__StreamMetadata_VorbisComment *comment)
|
||||
{
|
||||
for (unsigned i = 0; i < comment->num_comments; ++i)
|
||||
flac_parse_comment(tag, char_tnum, &comment->comments[i]);
|
||||
}
|
||||
|
||||
void
|
||||
flac_tag_apply_metadata(struct tag *tag, const char *track,
|
||||
const FLAC__StreamMetadata *block)
|
||||
{
|
||||
switch (block->type) {
|
||||
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
|
||||
flac_vorbis_comments_to_tag(tag, track,
|
||||
&block->data.vorbis_comment);
|
||||
break;
|
||||
|
||||
case FLAC__METADATA_TYPE_STREAMINFO:
|
||||
tag->time = flac_duration(&block->data.stream_info);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct tag *
|
||||
flac_tag_load(const char *file, const char *char_tnum)
|
||||
{
|
||||
struct tag *tag;
|
||||
FLAC__Metadata_SimpleIterator *it;
|
||||
FLAC__StreamMetadata *block = NULL;
|
||||
|
||||
it = FLAC__metadata_simple_iterator_new();
|
||||
if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
|
||||
const char *err;
|
||||
FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
|
||||
|
||||
s = FLAC__metadata_simple_iterator_status(it);
|
||||
|
||||
switch (s) { /* slightly more human-friendly messages: */
|
||||
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
|
||||
err = "illegal input";
|
||||
break;
|
||||
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
|
||||
err = "error opening file";
|
||||
break;
|
||||
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
|
||||
err = "not a FLAC file";
|
||||
break;
|
||||
default:
|
||||
err = FLAC__Metadata_SimpleIteratorStatusString[s];
|
||||
}
|
||||
g_debug("Reading '%s' metadata gave the following error: %s\n",
|
||||
file, err);
|
||||
FLAC__metadata_simple_iterator_delete(it);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tag = tag_new();
|
||||
do {
|
||||
block = FLAC__metadata_simple_iterator_get_block(it);
|
||||
if (!block)
|
||||
break;
|
||||
|
||||
flac_tag_apply_metadata(tag, char_tnum, block);
|
||||
FLAC__metadata_object_delete(block);
|
||||
} while (FLAC__metadata_simple_iterator_next(it));
|
||||
|
||||
FLAC__metadata_simple_iterator_delete(it);
|
||||
|
||||
if (!tag_is_defined(tag)) {
|
||||
tag_free(tag);
|
||||
tag = NULL;
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
55
src/decoder/flac_metadata.h
Normal file
55
src/decoder/flac_metadata.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef MPD_FLAC_METADATA_H
|
||||
#define MPD_FLAC_METADATA_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <FLAC/metadata.h>
|
||||
|
||||
struct tag;
|
||||
struct replay_gain_info;
|
||||
|
||||
static inline unsigned
|
||||
flac_duration(const FLAC__StreamMetadata_StreamInfo *stream_info)
|
||||
{
|
||||
return (stream_info->total_samples + stream_info->sample_rate - 1) /
|
||||
stream_info->sample_rate;
|
||||
}
|
||||
|
||||
bool
|
||||
flac_parse_replay_gain(struct replay_gain_info *rgi,
|
||||
const FLAC__StreamMetadata *block);
|
||||
|
||||
bool
|
||||
flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
|
||||
const FLAC__StreamMetadata *block);
|
||||
|
||||
void
|
||||
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
|
||||
const FLAC__StreamMetadata_VorbisComment *comment);
|
||||
|
||||
void
|
||||
flac_tag_apply_metadata(struct tag *tag, const char *track,
|
||||
const FLAC__StreamMetadata *block);
|
||||
|
||||
struct tag *
|
||||
flac_tag_load(const char *file, const char *char_tnum);
|
||||
|
||||
#endif
|
||||
109
src/decoder/flac_pcm.c
Normal file
109
src/decoder/flac_pcm.c
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include "flac_pcm.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
static void flac_convert_stereo16(int16_t *dest,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
for (; position < end; ++position) {
|
||||
*dest++ = buf[0][position];
|
||||
*dest++ = buf[1][position];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
flac_convert_16(int16_t *dest,
|
||||
unsigned int num_channels,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
unsigned int c_chan;
|
||||
|
||||
for (; position < end; ++position)
|
||||
for (c_chan = 0; c_chan < num_channels; c_chan++)
|
||||
*dest++ = buf[c_chan][position];
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this function also handles 24 bit files!
|
||||
*/
|
||||
static void
|
||||
flac_convert_32(int32_t *dest,
|
||||
unsigned int num_channels,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
unsigned int c_chan;
|
||||
|
||||
for (; position < end; ++position)
|
||||
for (c_chan = 0; c_chan < num_channels; c_chan++)
|
||||
*dest++ = buf[c_chan][position];
|
||||
}
|
||||
|
||||
static void
|
||||
flac_convert_8(int8_t *dest,
|
||||
unsigned int num_channels,
|
||||
const FLAC__int32 * const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
unsigned int c_chan;
|
||||
|
||||
for (; position < end; ++position)
|
||||
for (c_chan = 0; c_chan < num_channels; c_chan++)
|
||||
*dest++ = buf[c_chan][position];
|
||||
}
|
||||
|
||||
void
|
||||
flac_convert(void *dest,
|
||||
unsigned int num_channels, enum sample_format sample_format,
|
||||
const FLAC__int32 *const buf[],
|
||||
unsigned int position, unsigned int end)
|
||||
{
|
||||
switch (sample_format) {
|
||||
case SAMPLE_FORMAT_S16:
|
||||
if (num_channels == 2)
|
||||
flac_convert_stereo16((int16_t*)dest, buf,
|
||||
position, end);
|
||||
else
|
||||
flac_convert_16((int16_t*)dest, num_channels, buf,
|
||||
position, end);
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S24_P32:
|
||||
case SAMPLE_FORMAT_S32:
|
||||
flac_convert_32((int32_t*)dest, num_channels, buf,
|
||||
position, end);
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S8:
|
||||
flac_convert_8((int8_t*)dest, num_channels, buf,
|
||||
position, end);
|
||||
break;
|
||||
|
||||
case SAMPLE_FORMAT_S24:
|
||||
case SAMPLE_FORMAT_UNDEFINED:
|
||||
/* unreachable */
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* Copyright (C) 2003-2010 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
@@ -17,33 +17,17 @@
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "normalize.h"
|
||||
#include "compress.h"
|
||||
#include "conf.h"
|
||||
#ifndef MPD_FLAC_PCM_H
|
||||
#define MPD_FLAC_PCM_H
|
||||
|
||||
#include "audio_format.h"
|
||||
|
||||
#define DEFAULT_VOLUME_NORMALIZATION 0
|
||||
#include <FLAC/ordinals.h>
|
||||
|
||||
int normalizationEnabled;
|
||||
void
|
||||
flac_convert(void *dest,
|
||||
unsigned int num_channels, enum sample_format sample_format,
|
||||
const FLAC__int32 *const buf[],
|
||||
unsigned int position, unsigned int end);
|
||||
|
||||
void initNormalization(void)
|
||||
{
|
||||
normalizationEnabled = config_get_bool(CONF_VOLUME_NORMALIZATION,
|
||||
DEFAULT_VOLUME_NORMALIZATION);
|
||||
|
||||
if (normalizationEnabled)
|
||||
CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS);
|
||||
}
|
||||
|
||||
void finishNormalization(void)
|
||||
{
|
||||
if (normalizationEnabled) CompressFree();
|
||||
}
|
||||
|
||||
void normalizeData(char *buffer, int bufferSize,
|
||||
const struct audio_format *format)
|
||||
{
|
||||
if ((format->bits != 16) || (format->channels != 2)) return;
|
||||
|
||||
CompressDo(buffer, bufferSize);
|
||||
}
|
||||
#endif
|
||||
@@ -1,918 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2003-2009 The Music Player Daemon Project
|
||||
* http://www.musicpd.org
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "_flac_common.h"
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifdef HAVE_CUE /* libcue */
|
||||
#include "../cue/cue_tag.h"
|
||||
#endif
|
||||
|
||||
/* this code was based on flac123, from flac-tools */
|
||||
|
||||
static flac_read_status
|
||||
flac_read_cb(G_GNUC_UNUSED const flac_decoder *fd,
|
||||
FLAC__byte buf[], flac_read_status_size_t *bytes,
|
||||
void *fdata)
|
||||
{
|
||||
struct flac_data *data = fdata;
|
||||
size_t r;
|
||||
|
||||
r = decoder_read(data->decoder, data->input_stream,
|
||||
(void *)buf, *bytes);
|
||||
*bytes = r;
|
||||
|
||||
if (r == 0) {
|
||||
if (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE ||
|
||||
input_stream_eof(data->input_stream))
|
||||
return flac_read_status_eof;
|
||||
else
|
||||
return flac_read_status_abort;
|
||||
}
|
||||
|
||||
return flac_read_status_continue;
|
||||
}
|
||||
|
||||
static flac_seek_status
|
||||
flac_seek_cb(G_GNUC_UNUSED const flac_decoder *fd,
|
||||
FLAC__uint64 offset, void *fdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) fdata;
|
||||
|
||||
if (!input_stream_seek(data->input_stream, offset, SEEK_SET))
|
||||
return flac_seek_status_error;
|
||||
|
||||
return flac_seek_status_ok;
|
||||
}
|
||||
|
||||
static flac_tell_status
|
||||
flac_tell_cb(G_GNUC_UNUSED const flac_decoder *fd,
|
||||
FLAC__uint64 * offset, void *fdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) fdata;
|
||||
|
||||
*offset = (long)(data->input_stream->offset);
|
||||
|
||||
return flac_tell_status_ok;
|
||||
}
|
||||
|
||||
static flac_length_status
|
||||
flac_length_cb(G_GNUC_UNUSED const flac_decoder *fd,
|
||||
FLAC__uint64 * length, void *fdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) fdata;
|
||||
|
||||
if (data->input_stream->size < 0)
|
||||
return flac_length_status_unsupported;
|
||||
|
||||
*length = (size_t) (data->input_stream->size);
|
||||
|
||||
return flac_length_status_ok;
|
||||
}
|
||||
|
||||
static FLAC__bool
|
||||
flac_eof_cb(G_GNUC_UNUSED const flac_decoder *fd, void *fdata)
|
||||
{
|
||||
struct flac_data *data = (struct flac_data *) fdata;
|
||||
|
||||
return (decoder_get_command(data->decoder) != DECODE_COMMAND_NONE &&
|
||||
decoder_get_command(data->decoder) != DECODE_COMMAND_SEEK) ||
|
||||
input_stream_eof(data->input_stream);
|
||||
}
|
||||
|
||||
static void
|
||||
flac_error_cb(G_GNUC_UNUSED const flac_decoder *fd,
|
||||
FLAC__StreamDecoderErrorStatus status, void *fdata)
|
||||
{
|
||||
flac_error_common_cb("flac", status, (struct flac_data *) fdata);
|
||||
}
|
||||
|
||||
#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7
|
||||
static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
|
||||
{
|
||||
const char *str = ""; /* "" to silence compiler warning */
|
||||
switch (state) {
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_OK:
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_SEEKING:
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
|
||||
return;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
|
||||
str = "allocation error";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
|
||||
str = "read error";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
|
||||
str = "seek error";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
|
||||
str = "seekable stream error";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
|
||||
str = "decoder already initialized";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
|
||||
str = "invalid callback";
|
||||
break;
|
||||
case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
|
||||
str = "decoder uninitialized";
|
||||
}
|
||||
|
||||
g_warning("%s\n", str);
|
||||
}
|
||||
|
||||
static bool
|
||||
flac_init(FLAC__SeekableStreamDecoder *dec,
|
||||
FLAC__SeekableStreamDecoderReadCallback read_cb,
|
||||
FLAC__SeekableStreamDecoderSeekCallback seek_cb,
|
||||
FLAC__SeekableStreamDecoderTellCallback tell_cb,
|
||||
FLAC__SeekableStreamDecoderLengthCallback length_cb,
|
||||
FLAC__SeekableStreamDecoderEofCallback eof_cb,
|
||||
FLAC__SeekableStreamDecoderWriteCallback write_cb,
|
||||
FLAC__SeekableStreamDecoderMetadataCallback metadata_cb,
|
||||
FLAC__SeekableStreamDecoderErrorCallback error_cb,
|
||||
void *data)
|
||||
{
|
||||
return FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_metadata_callback(dec, metadata_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_metadata_respond(dec, FLAC__METADATA_TYPE_VORBIS_COMMENT) &&
|
||||
FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb) &&
|
||||
FLAC__seekable_stream_decoder_set_client_data(dec, data) &&
|
||||
FLAC__seekable_stream_decoder_init(dec) == FLAC__SEEKABLE_STREAM_DECODER_OK;
|
||||
}
|
||||
#else /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
static void flacPrintErroredState(FLAC__StreamDecoderState state)
|
||||
{
|
||||
const char *str = ""; /* "" to silence compiler warning */
|
||||
switch (state) {
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA:
|
||||
case FLAC__STREAM_DECODER_READ_METADATA:
|
||||
case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC:
|
||||
case FLAC__STREAM_DECODER_READ_FRAME:
|
||||
case FLAC__STREAM_DECODER_END_OF_STREAM:
|
||||
return;
|
||||
case FLAC__STREAM_DECODER_OGG_ERROR:
|
||||
str = "error in the Ogg layer";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_SEEK_ERROR:
|
||||
str = "seek error";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_ABORTED:
|
||||
str = "decoder aborted by read";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
|
||||
str = "allocation error";
|
||||
break;
|
||||
case FLAC__STREAM_DECODER_UNINITIALIZED:
|
||||
str = "decoder uninitialized";
|
||||
}
|
||||
|
||||
g_warning("%s\n", str);
|
||||
}
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
static void flacMetadata(G_GNUC_UNUSED const flac_decoder * dec,
|
||||
const FLAC__StreamMetadata * block, void *vdata)
|
||||
{
|
||||
flac_metadata_common_cb(block, (struct flac_data *) vdata);
|
||||
}
|
||||
|
||||
static FLAC__StreamDecoderWriteStatus
|
||||
flac_write_cb(const flac_decoder *dec, const FLAC__Frame *frame,
|
||||
const FLAC__int32 *const buf[], void *vdata)
|
||||
{
|
||||
FLAC__uint32 samples = frame->header.blocksize;
|
||||
struct flac_data *data = (struct flac_data *) vdata;
|
||||
float timeChange;
|
||||
FLAC__uint64 newPosition = 0;
|
||||
|
||||
timeChange = ((float)samples) / frame->header.sample_rate;
|
||||
data->time += timeChange;
|
||||
|
||||
flac_get_decode_position(dec, &newPosition);
|
||||
if (data->position && newPosition >= data->position) {
|
||||
assert(timeChange >= 0);
|
||||
|
||||
data->bit_rate =
|
||||
((newPosition - data->position) * 8.0 / timeChange)
|
||||
/ 1000 + 0.5;
|
||||
}
|
||||
data->position = newPosition;
|
||||
|
||||
return flac_common_write(data, frame, buf);
|
||||
}
|
||||
|
||||
static struct tag *
|
||||
flac_tag_load(const char *file, const char *char_tnum)
|
||||
{
|
||||
struct tag *tag;
|
||||
FLAC__Metadata_SimpleIterator *it;
|
||||
FLAC__StreamMetadata *block = NULL;
|
||||
|
||||
it = FLAC__metadata_simple_iterator_new();
|
||||
if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) {
|
||||
const char *err;
|
||||
FLAC_API FLAC__Metadata_SimpleIteratorStatus s;
|
||||
|
||||
s = FLAC__metadata_simple_iterator_status(it);
|
||||
|
||||
switch (s) { /* slightly more human-friendly messages: */
|
||||
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT:
|
||||
err = "illegal input";
|
||||
break;
|
||||
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE:
|
||||
err = "error opening file";
|
||||
break;
|
||||
case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE:
|
||||
err = "not a FLAC file";
|
||||
break;
|
||||
default:
|
||||
err = FLAC__Metadata_SimpleIteratorStatusString[s];
|
||||
}
|
||||
g_debug("Reading '%s' metadata gave the following error: %s\n",
|
||||
file, err);
|
||||
FLAC__metadata_simple_iterator_delete(it);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tag = tag_new();
|
||||
do {
|
||||
block = FLAC__metadata_simple_iterator_get_block(it);
|
||||
if (!block)
|
||||
break;
|
||||
if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
|
||||
flac_vorbis_comments_to_tag(tag, char_tnum, block);
|
||||
} else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
|
||||
tag->time = ((float)block->data.stream_info.total_samples) /
|
||||
block->data.stream_info.sample_rate + 0.5;
|
||||
}
|
||||
FLAC__metadata_object_delete(block);
|
||||
} while (FLAC__metadata_simple_iterator_next(it));
|
||||
|
||||
FLAC__metadata_simple_iterator_delete(it);
|
||||
|
||||
if (!tag_is_defined(tag)) {
|
||||
tag_free(tag);
|
||||
tag = NULL;
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
|
||||
static struct tag *
|
||||
flac_cue_tag_load(const char *file)
|
||||
{
|
||||
struct tag* tag = NULL;
|
||||
char* char_tnum = NULL;
|
||||
char* ptr = NULL;
|
||||
unsigned int tnum = 0;
|
||||
unsigned int sample_rate = 0;
|
||||
FLAC__uint64 track_time = 0;
|
||||
#ifdef HAVE_CUE /* libcue */
|
||||
FLAC__StreamMetadata* vc;
|
||||
#endif /* libcue */
|
||||
FLAC__StreamMetadata* si = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO);
|
||||
FLAC__StreamMetadata* cs;
|
||||
|
||||
tnum = flac_vtrack_tnum(file);
|
||||
char_tnum = g_strdup_printf("%u", tnum);
|
||||
|
||||
ptr = strrchr(file, '/');
|
||||
*ptr = '\0';
|
||||
|
||||
#ifdef HAVE_CUE /* libcue */
|
||||
if (FLAC__metadata_get_tags(file, &vc))
|
||||
{
|
||||
for (unsigned i = 0; i < vc->data.vorbis_comment.num_comments;
|
||||
i++)
|
||||
{
|
||||
if ((ptr = (char*)vc->data.vorbis_comment.comments[i].entry) != NULL)
|
||||
{
|
||||
if (g_ascii_strncasecmp(ptr, "cuesheet", 8) == 0)
|
||||
{
|
||||
while (*(++ptr) != '=');
|
||||
tag = cue_tag_string( ++ptr,
|
||||
tnum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FLAC__metadata_object_delete(vc);
|
||||
}
|
||||
#endif /* libcue */
|
||||
|
||||
if (tag == NULL)
|
||||
tag = flac_tag_load(file, char_tnum);
|
||||
|
||||
if (char_tnum != NULL)
|
||||
{
|
||||
tag_add_item( tag,
|
||||
TAG_ITEM_TRACK,
|
||||
char_tnum);
|
||||
g_free(char_tnum);
|
||||
}
|
||||
|
||||
if (FLAC__metadata_get_streaminfo(file, si))
|
||||
{
|
||||
sample_rate = si->data.stream_info.sample_rate;
|
||||
FLAC__metadata_object_delete(si);
|
||||
}
|
||||
|
||||
if (FLAC__metadata_get_cuesheet(file, &cs))
|
||||
{
|
||||
if (cs->data.cue_sheet.tracks != NULL
|
||||
&& (tnum <= cs->data.cue_sheet.num_tracks - 1))
|
||||
{
|
||||
track_time = cs->data.cue_sheet.tracks[tnum].offset
|
||||
- cs->data.cue_sheet.tracks[tnum - 1].offset;
|
||||
}
|
||||
FLAC__metadata_object_delete(cs);
|
||||
}
|
||||
|
||||
if (sample_rate != 0)
|
||||
{
|
||||
tag->time = (unsigned int)(track_time/sample_rate);
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
static struct tag *
|
||||
flac_tag_dup(const char *file)
|
||||
{
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
struct stat st;
|
||||
|
||||
if (stat(file, &st) < 0)
|
||||
return flac_cue_tag_load(file);
|
||||
else
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
return flac_tag_load(file, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decode_internal(struct decoder * decoder,
|
||||
struct input_stream *input_stream,
|
||||
bool is_ogg)
|
||||
{
|
||||
flac_decoder *flac_dec;
|
||||
struct flac_data data;
|
||||
enum decoder_command cmd;
|
||||
const char *err = NULL;
|
||||
|
||||
if (!(flac_dec = flac_new()))
|
||||
return;
|
||||
flac_data_init(&data, decoder, input_stream);
|
||||
data.tag = tag_new();
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
|
||||
{
|
||||
g_debug("Failed to set metadata respond\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (is_ogg) {
|
||||
if (!flac_ogg_init(flac_dec, flac_read_cb,
|
||||
flac_seek_cb, flac_tell_cb,
|
||||
flac_length_cb, flac_eof_cb,
|
||||
flac_write_cb, flacMetadata,
|
||||
flac_error_cb, (void *)&data)) {
|
||||
err = "doing Ogg init()";
|
||||
goto fail;
|
||||
}
|
||||
} else {
|
||||
if (!flac_init(flac_dec, flac_read_cb,
|
||||
flac_seek_cb, flac_tell_cb,
|
||||
flac_length_cb, flac_eof_cb,
|
||||
flac_write_cb, flacMetadata,
|
||||
flac_error_cb, (void *)&data)) {
|
||||
err = "doing init()";
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flac_process_metadata(flac_dec)) {
|
||||
err = "problem reading metadata";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!audio_format_valid(&data.audio_format)) {
|
||||
g_warning("Invalid audio format: %u:%u:%u\n",
|
||||
data.audio_format.sample_rate,
|
||||
data.audio_format.bits,
|
||||
data.audio_format.channels);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
decoder_initialized(decoder, &data.audio_format,
|
||||
input_stream->seekable, data.total_time);
|
||||
|
||||
while (true) {
|
||||
if (!tag_is_empty(data.tag)) {
|
||||
cmd = decoder_tag(decoder, input_stream, data.tag);
|
||||
tag_free(data.tag);
|
||||
data.tag = tag_new();
|
||||
} else
|
||||
cmd = decoder_get_command(decoder);
|
||||
|
||||
if (cmd == DECODE_COMMAND_SEEK) {
|
||||
FLAC__uint64 seek_sample = decoder_seek_where(decoder) *
|
||||
data.audio_format.sample_rate + 0.5;
|
||||
if (flac_seek_absolute(flac_dec, seek_sample)) {
|
||||
data.time = ((float)seek_sample) /
|
||||
data.audio_format.sample_rate;
|
||||
data.position = 0;
|
||||
decoder_command_finished(decoder);
|
||||
} else
|
||||
decoder_seek_error(decoder);
|
||||
} else if (cmd == DECODE_COMMAND_STOP ||
|
||||
flac_get_state(flac_dec) == flac_decoder_eof)
|
||||
break;
|
||||
|
||||
if (!flac_process_single(flac_dec)) {
|
||||
cmd = decoder_get_command(decoder);
|
||||
if (cmd != DECODE_COMMAND_SEEK)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (cmd != DECODE_COMMAND_STOP) {
|
||||
flacPrintErroredState(flac_get_state(flac_dec));
|
||||
flac_finish(flac_dec);
|
||||
}
|
||||
|
||||
fail:
|
||||
if (data.replay_gain_info)
|
||||
replay_gain_info_free(data.replay_gain_info);
|
||||
|
||||
tag_free(data.tag);
|
||||
|
||||
if (flac_dec)
|
||||
flac_delete(flac_dec);
|
||||
|
||||
if (err)
|
||||
g_warning("%s\n", err);
|
||||
}
|
||||
|
||||
static void
|
||||
flac_decode(struct decoder * decoder, struct input_stream *input_stream)
|
||||
{
|
||||
flac_decode_internal(decoder, input_stream, false);
|
||||
}
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
|
||||
/**
|
||||
* @brief Decode a flac file with embedded cue sheets
|
||||
* @param const char* fname filename on fs
|
||||
*/
|
||||
static void
|
||||
flac_container_decode(struct decoder* decoder,
|
||||
const char* fname,
|
||||
bool is_ogg)
|
||||
{
|
||||
unsigned int tnum = 0;
|
||||
FLAC__uint64 t_start = 0;
|
||||
FLAC__uint64 t_end = 0;
|
||||
FLAC__uint64 track_time = 0;
|
||||
FLAC__StreamMetadata* cs = NULL;
|
||||
|
||||
flac_decoder *flac_dec;
|
||||
struct flac_data data;
|
||||
const char *err = NULL;
|
||||
|
||||
char* pathname = g_strdup(fname);
|
||||
char* slash = strrchr(pathname, '/');
|
||||
*slash = '\0';
|
||||
|
||||
tnum = flac_vtrack_tnum(fname);
|
||||
|
||||
cs = FLAC__metadata_object_new(FLAC__METADATA_TYPE_CUESHEET);
|
||||
|
||||
FLAC__metadata_get_cuesheet(pathname, &cs);
|
||||
|
||||
if (cs != NULL)
|
||||
{
|
||||
if (cs->data.cue_sheet.tracks != NULL
|
||||
&& (tnum <= cs->data.cue_sheet.num_tracks - 1))
|
||||
{
|
||||
t_start = cs->data.cue_sheet.tracks[tnum - 1].offset;
|
||||
t_end = cs->data.cue_sheet.tracks[tnum].offset;
|
||||
track_time = cs->data.cue_sheet.tracks[tnum].offset
|
||||
- cs->data.cue_sheet.tracks[tnum - 1].offset;
|
||||
}
|
||||
|
||||
FLAC__metadata_object_delete(cs);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_free(pathname);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(flac_dec = flac_new()))
|
||||
{
|
||||
g_free(pathname);
|
||||
return;
|
||||
}
|
||||
|
||||
flac_data_init(&data, decoder, NULL);
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
|
||||
{
|
||||
g_debug("Failed to set metadata respond\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (is_ogg)
|
||||
{
|
||||
if (FLAC__stream_decoder_init_ogg_file( flac_dec,
|
||||
pathname,
|
||||
flac_write_cb,
|
||||
flacMetadata,
|
||||
flac_error_cb,
|
||||
(void*) &data )
|
||||
!= FLAC__STREAM_DECODER_INIT_STATUS_OK )
|
||||
{
|
||||
err = "doing Ogg init()";
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (FLAC__stream_decoder_init_file( flac_dec,
|
||||
pathname,
|
||||
flac_write_cb,
|
||||
flacMetadata,
|
||||
flac_error_cb,
|
||||
(void*) &data )
|
||||
!= FLAC__STREAM_DECODER_INIT_STATUS_OK )
|
||||
{
|
||||
err = "doing init()";
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flac_process_metadata(flac_dec))
|
||||
{
|
||||
err = "problem reading metadata";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!audio_format_valid(&data.audio_format))
|
||||
{
|
||||
g_warning("Invalid audio format: %u:%u:%u\n",
|
||||
data.audio_format.sample_rate,
|
||||
data.audio_format.bits,
|
||||
data.audio_format.channels);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
// set track time (order is important: after stream init)
|
||||
data.total_time = ((float)track_time / (float)data.audio_format.sample_rate);
|
||||
data.position = 0;
|
||||
|
||||
decoder_initialized(decoder, &data.audio_format,
|
||||
true, data.total_time);
|
||||
|
||||
// seek to song start (order is important: after decoder init)
|
||||
flac_seek_absolute(flac_dec, (FLAC__uint64)t_start);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!flac_process_single(flac_dec))
|
||||
break;
|
||||
|
||||
// we only need to break at the end of track if we are in "cue mode"
|
||||
if (data.time >= data.total_time)
|
||||
{
|
||||
flacPrintErroredState(flac_get_state(flac_dec));
|
||||
flac_finish(flac_dec);
|
||||
}
|
||||
|
||||
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK)
|
||||
{
|
||||
FLAC__uint64 seek_sample = t_start +
|
||||
(decoder_seek_where(decoder) * data.audio_format.sample_rate);
|
||||
|
||||
if (seek_sample >= t_start && seek_sample <= t_end &&
|
||||
flac_seek_absolute(flac_dec, (FLAC__uint64)seek_sample)) {
|
||||
data.time = (float)(seek_sample - t_start) /
|
||||
data.audio_format.sample_rate;
|
||||
data.position = 0;
|
||||
|
||||
decoder_command_finished(decoder);
|
||||
} else
|
||||
decoder_seek_error(decoder);
|
||||
}
|
||||
else if (flac_get_state(flac_dec) == flac_decoder_eof)
|
||||
break;
|
||||
}
|
||||
|
||||
if (decoder_get_command(decoder) != DECODE_COMMAND_STOP)
|
||||
{
|
||||
flacPrintErroredState(flac_get_state(flac_dec));
|
||||
flac_finish(flac_dec);
|
||||
}
|
||||
|
||||
fail:
|
||||
if (pathname)
|
||||
g_free(pathname);
|
||||
|
||||
if (data.replay_gain_info)
|
||||
replay_gain_info_free(data.replay_gain_info);
|
||||
|
||||
if (flac_dec)
|
||||
flac_delete(flac_dec);
|
||||
|
||||
if (err)
|
||||
g_warning("%s\n", err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Open a flac file for decoding
|
||||
* @param const char* fname filename on fs
|
||||
*/
|
||||
static void
|
||||
flac_filedecode_internal(struct decoder* decoder,
|
||||
const char* fname,
|
||||
bool is_ogg)
|
||||
{
|
||||
flac_decoder *flac_dec;
|
||||
struct flac_data data;
|
||||
const char *err = NULL;
|
||||
unsigned int flac_err_state = 0;
|
||||
|
||||
if (!(flac_dec = flac_new()))
|
||||
return;
|
||||
|
||||
flac_data_init(&data, decoder, NULL);
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
if(!FLAC__stream_decoder_set_metadata_respond(flac_dec, FLAC__METADATA_TYPE_VORBIS_COMMENT))
|
||||
{
|
||||
g_debug("Failed to set metadata respond\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
if (is_ogg)
|
||||
{
|
||||
if ( (flac_err_state = FLAC__stream_decoder_init_ogg_file( flac_dec,
|
||||
fname,
|
||||
flac_write_cb,
|
||||
flacMetadata,
|
||||
flac_error_cb,
|
||||
(void*) &data ))
|
||||
== FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE)
|
||||
{
|
||||
flac_container_decode(decoder, fname, is_ogg);
|
||||
}
|
||||
else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK)
|
||||
{
|
||||
err = "doing Ogg init()";
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( (flac_err_state = FLAC__stream_decoder_init_file( flac_dec,
|
||||
fname,
|
||||
flac_write_cb,
|
||||
flacMetadata,
|
||||
flac_error_cb,
|
||||
(void*) &data ))
|
||||
== FLAC__STREAM_DECODER_INIT_STATUS_ERROR_OPENING_FILE)
|
||||
{
|
||||
flac_container_decode(decoder, fname, is_ogg);
|
||||
}
|
||||
else if (flac_err_state != FLAC__STREAM_DECODER_INIT_STATUS_OK)
|
||||
{
|
||||
err = "doing init()";
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flac_process_metadata(flac_dec))
|
||||
{
|
||||
err = "problem reading metadata";
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (!audio_format_valid(&data.audio_format))
|
||||
{
|
||||
g_warning("Invalid audio format: %u:%u:%u\n",
|
||||
data.audio_format.sample_rate,
|
||||
data.audio_format.bits,
|
||||
data.audio_format.channels);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
decoder_initialized(decoder, &data.audio_format,
|
||||
true, data.total_time);
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (!flac_process_single(flac_dec))
|
||||
break;
|
||||
|
||||
if (decoder_get_command(decoder) == DECODE_COMMAND_SEEK)
|
||||
{
|
||||
FLAC__uint64 seek_sample = decoder_seek_where(decoder) *
|
||||
data.audio_format.sample_rate + 0.5;
|
||||
if (flac_seek_absolute(flac_dec, seek_sample))
|
||||
{
|
||||
data.time = ((float)seek_sample) /
|
||||
data.audio_format.sample_rate;
|
||||
data.position = 0;
|
||||
decoder_command_finished(decoder);
|
||||
}
|
||||
else
|
||||
decoder_seek_error(decoder);
|
||||
|
||||
}
|
||||
else if (flac_get_state(flac_dec) == flac_decoder_eof)
|
||||
break;
|
||||
}
|
||||
|
||||
if (decoder_get_command(decoder) != DECODE_COMMAND_STOP)
|
||||
{
|
||||
flacPrintErroredState(flac_get_state(flac_dec));
|
||||
flac_finish(flac_dec);
|
||||
}
|
||||
|
||||
fail:
|
||||
if (data.replay_gain_info)
|
||||
replay_gain_info_free(data.replay_gain_info);
|
||||
|
||||
if (flac_dec)
|
||||
flac_delete(flac_dec);
|
||||
|
||||
if (err)
|
||||
g_warning("%s\n", err);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief wrapper function for
|
||||
* flac_filedecode_internal method
|
||||
* for decoding without ogg
|
||||
*/
|
||||
static void
|
||||
flac_filedecode(struct decoder *decoder, const char *fname)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(fname, &st) < 0) {
|
||||
flac_container_decode(decoder, fname, false);
|
||||
} else
|
||||
flac_filedecode_internal(decoder, fname, false);
|
||||
}
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
#ifndef HAVE_OGGFLAC
|
||||
|
||||
static bool
|
||||
oggflac_init(G_GNUC_UNUSED const struct config_param *param)
|
||||
{
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
return !!FLAC_API_SUPPORTS_OGG_FLAC;
|
||||
#else
|
||||
/* disable oggflac when libflac is too old */
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
|
||||
static struct tag *
|
||||
oggflac_tag_dup(const char *file)
|
||||
{
|
||||
struct tag *ret = NULL;
|
||||
FLAC__Metadata_Iterator *it;
|
||||
FLAC__StreamMetadata *block;
|
||||
FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new();
|
||||
|
||||
if (!(FLAC__metadata_chain_read_ogg(chain, file)))
|
||||
goto out;
|
||||
it = FLAC__metadata_iterator_new();
|
||||
FLAC__metadata_iterator_init(it, chain);
|
||||
|
||||
ret = tag_new();
|
||||
do {
|
||||
if (!(block = FLAC__metadata_iterator_get_block(it)))
|
||||
break;
|
||||
if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) {
|
||||
flac_vorbis_comments_to_tag(ret, NULL, block);
|
||||
} else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) {
|
||||
ret->time = ((float)block->data.stream_info.
|
||||
total_samples) /
|
||||
block->data.stream_info.sample_rate + 0.5;
|
||||
}
|
||||
} while (FLAC__metadata_iterator_next(it));
|
||||
FLAC__metadata_iterator_delete(it);
|
||||
|
||||
if (!tag_is_defined(ret)) {
|
||||
tag_free(ret);
|
||||
ret = NULL;
|
||||
}
|
||||
|
||||
out:
|
||||
FLAC__metadata_chain_delete(chain);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
oggflac_decode(struct decoder *decoder, struct input_stream *input_stream)
|
||||
{
|
||||
if (ogg_stream_type_detect(input_stream) != FLAC)
|
||||
return;
|
||||
|
||||
/* rewind the stream, because ogg_stream_type_detect() has
|
||||
moved it */
|
||||
input_stream_seek(input_stream, 0, SEEK_SET);
|
||||
|
||||
flac_decode_internal(decoder, input_stream, true);
|
||||
}
|
||||
|
||||
static const char *const oggflac_suffixes[] = { "ogg", "oga", NULL };
|
||||
static const char *const oggflac_mime_types[] = {
|
||||
"application/ogg",
|
||||
"application/x-ogg",
|
||||
"audio/ogg",
|
||||
"audio/x-flac+ogg",
|
||||
"audio/x-ogg",
|
||||
NULL
|
||||
};
|
||||
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
|
||||
const struct decoder_plugin oggflac_decoder_plugin = {
|
||||
.name = "oggflac",
|
||||
.init = oggflac_init,
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
.stream_decode = oggflac_decode,
|
||||
.tag_dup = oggflac_tag_dup,
|
||||
.suffixes = oggflac_suffixes,
|
||||
.mime_types = oggflac_mime_types
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif /* HAVE_OGGFLAC */
|
||||
|
||||
static const char *const flac_suffixes[] = { "flac", NULL };
|
||||
static const char *const flac_mime_types[] = {
|
||||
"application/flac",
|
||||
"application/x-flac",
|
||||
"audio/flac",
|
||||
"audio/x-flac",
|
||||
NULL
|
||||
};
|
||||
|
||||
const struct decoder_plugin flac_decoder_plugin = {
|
||||
.name = "flac",
|
||||
.stream_decode = flac_decode,
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
.file_decode = flac_filedecode,
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
.tag_dup = flac_tag_dup,
|
||||
.suffixes = flac_suffixes,
|
||||
.mime_types = flac_mime_types,
|
||||
#if defined(FLAC_API_VERSION_CURRENT) && FLAC_API_VERSION_CURRENT > 7
|
||||
.container_scan = flac_cue_track,
|
||||
#endif /* FLAC_API_VERSION_CURRENT >= 7 */
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user