mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-06-08 17:29:22 +00:00
Compare commits
1048 commits
gstreamer-
...
main
Author | SHA1 | Date | |
---|---|---|---|
f68655b5e2 | |||
aaccc6e7f1 | |||
f30cb2b56c | |||
7cec628c43 | |||
0e85973e94 | |||
30252a1b2e | |||
1e964233c6 | |||
c9ac553cfe | |||
260b04a1cf | |||
ba70bb1154 | |||
85c38107cf | |||
8171a00943 | |||
ab2f5e3d8d | |||
2b68920f82 | |||
6597ec84eb | |||
6b628485c5 | |||
802ff6a67c | |||
8fc652f208 | |||
568e8533fa | |||
91bc39367b | |||
04e9e5284c | |||
1c54c77840 | |||
83f76280f5 | |||
712d4757c3 | |||
de726ca8d2 | |||
ebdcc403cf | |||
45800d7636 | |||
a7418fb483 | |||
df32e1ebfa | |||
525179f666 | |||
9485265769 | |||
1600d3b055 | |||
0121d78482 | |||
d480c6c2d3 | |||
7d5789032a | |||
06f40e72cb | |||
48e7a2ed06 | |||
66306e32f2 | |||
327f563e80 | |||
74ec83a0ff | |||
1865899621 | |||
2b4ec75bc5 | |||
e09ad990fa | |||
1e4a966c92 | |||
66c9840ad8 | |||
2c86f18a99 | |||
27ad26c258 | |||
984a9fe5ff | |||
b4fd6cf362 | |||
4f74cb7958 | |||
b6e24668a7 | |||
92a1e222f4 | |||
de71e9dadd | |||
be7da027f8 | |||
1e33926dc5 | |||
c2f67bd3c9 | |||
e7d0e0702a | |||
566e6443f4 | |||
4259d284bd | |||
58e91c154c | |||
28bd6f07a2 | |||
b1ad123595 | |||
c99cabfbc5 | |||
f5a7de9dc3 | |||
b12da2c543 | |||
02cd2c42fd | |||
dcc0b47349 | |||
0d33077df6 | |||
16608d2541 | |||
bab3498c6a | |||
72006215cb | |||
10e0294d5a | |||
61523baa7b | |||
6f871e6ce2 | |||
92c0cf1285 | |||
2585639054 | |||
0215339c5a | |||
539000574b | |||
bac5845be1 | |||
c282bc1bca | |||
9a7f37e2b7 | |||
71e9c2bb04 | |||
d9aa0731f4 | |||
49d3dd17a2 | |||
71cd80f204 | |||
5549dc7a15 | |||
613ed56675 | |||
a719cbfcc6 | |||
Rafael Caricio | 7d75e263f8 | ||
aabb011f5a | |||
e845e3575c | |||
f265c3197b | |||
e8e173d0d0 | |||
f842aff6df | |||
7e09481adc | |||
1b48fb7ae7 | |||
fe55acb4c9 | |||
fe3607bd14 | |||
5884c00bd0 | |||
13c3db7857 | |||
2b7488a4c8 | |||
b4576a0074 | |||
8861fc493b | |||
2bfb6ee016 | |||
edd7c258c8 | |||
3a3cec96ff | |||
80f8664564 | |||
be3ae583bc | |||
58106a42a9 | |||
096538989b | |||
150ad7a545 | |||
0d2f054c15 | |||
18cf5292b7 | |||
97d8a79d36 | |||
a306b1ce94 | |||
22c6a98914 | |||
8b64c734e7 | |||
befd8d4bd2 | |||
ce930eab5f | |||
75b25d011f | |||
953f6a3fd7 | |||
8e675de690 | |||
4326c3bfce | |||
47b788d44b | |||
16b0a4d762 | |||
b588ee59bc | |||
5466cafc24 | |||
812fe0a9bd | |||
c55c4ca42a | |||
83bd7be92a | |||
70397a9f05 | |||
a87eaa4b79 | |||
88cbc93338 | |||
927c3fcdb6 | |||
5803904deb | |||
c95e07a897 | |||
b42bd3d026 | |||
3dd800ac77 | |||
c92462b240 | |||
7573caa8e9 | |||
c12585377c | |||
17d7997137 | |||
66030f36ad | |||
b3d3895ae7 | |||
d6a855ff1b | |||
542030fd82 | |||
3fc38be5c4 | |||
168af88eda | |||
83d70d3471 | |||
596a9177ce | |||
2341ee6935 | |||
61c9cbdc8f | |||
00b56ca845 | |||
42158cbcb0 | |||
4dcc44687a | |||
fbce73f6fc | |||
f0c38621c1 | |||
a3e30b499f | |||
2ad452ee89 | |||
5d939498f1 | |||
f4b086738b | |||
6b30266145 | |||
c8180e714e | |||
0b356ee203 | |||
c2ebb3083a | |||
921938fd20 | |||
fab246f82e | |||
7757e06e36 | |||
70adedb142 | |||
7f6929b98d | |||
2575013faa | |||
d8fe1c64f1 | |||
fea85ff9c8 | |||
cc43935036 | |||
b5cbc47cf7 | |||
35b84d219f | |||
0aabbb3186 | |||
4dd6b102c4 | |||
0dd03da91f | |||
e1cd52178e | |||
55b4de779c | |||
9db4290d2d | |||
df30d2fbd3 | |||
b0cf7e5c81 | |||
756abbf807 | |||
5d7e068a8b | |||
a870d60621 | |||
9f27bde36a | |||
e868f81189 | |||
bac2e02160 | |||
ae7c68dbf8 | |||
0b11209674 | |||
f97150aa58 | |||
7e1ab086de | |||
be12c0a5f7 | |||
317f46ad97 | |||
c5e7e76e4d | |||
6556d31ab8 | |||
5476e3d759 | |||
cc7b7d508d | |||
2b9272c7eb | |||
cca3ebf520 | |||
428f670753 | |||
fadb7d0a26 | |||
2a88e29454 | |||
bfff0f7d87 | |||
96337d5234 | |||
eb49459937 | |||
ce3960f37f | |||
9f07ec35e6 | |||
f4366f8b2e | |||
523a46b4f5 | |||
6f8fc5f178 | |||
8f997ea4e3 | |||
992f8d9a5d | |||
9c6a39d692 | |||
b29a739fb2 | |||
1dea8f60a8 | |||
2629719b4e | |||
9e6e8c618e | |||
995f64513d | |||
5b01e43a12 | |||
03abb5c681 | |||
7a46377627 | |||
15e7a63e7b | |||
612f863ee9 | |||
237f22d131 | |||
2839e0078b | |||
0414f468c6 | |||
8b0731b5a2 | |||
7d0397e1ad | |||
f6476f1e8f | |||
cfebc32b82 | |||
721b7e9c8c | |||
1e88971ec8 | |||
8a6bcb712f | |||
002dc36ab9 | |||
9c590f4223 | |||
f0b408d823 | |||
17b2640237 | |||
fa006b9fc9 | |||
96037fbcc5 | |||
730b3459f1 | |||
60bb72ddc3 | |||
eabf31e6d0 | |||
550018c917 | |||
0829898d73 | |||
ec17c58dee | |||
563eff1193 | |||
594400a7f5 | |||
47ba068966 | |||
5df7c01cb5 | |||
f7ffa13543 | |||
23955d2dbb | |||
340d65d7a4 | |||
b9195ed309 | |||
fc1c017fc6 | |||
f563f8334b | |||
e09f9e9540 | |||
da21dc853d | |||
2228f882d8 | |||
8f3a6171ac | |||
2572afbf15 | |||
0faac3b875 | |||
cb0cc764ba | |||
45f55423fb | |||
8ef12a72e8 | |||
05884de50c | |||
67b7cf9764 | |||
9827106961 | |||
b2d5ee48cd | |||
7274c725a6 | |||
66ad059a47 | |||
c0d111e2c1 | |||
9116853e6d | |||
21aa61b69c | |||
119d905805 | |||
2f964f71bb | |||
d8a61edca0 | |||
3423d05f77 | |||
92891a61e8 | |||
76b9836e52 | |||
0fe4e0bf0b | |||
9971f71e94 | |||
803550111a | |||
09e9c047df | |||
cf5e7f6ed3 | |||
7a1cd675c2 | |||
e59f3bbe58 | |||
3e963e9239 | |||
42425abb69 | |||
437326ebfd | |||
44e49a06a0 | |||
975556c06b | |||
086ffd7aff | |||
612ef91af9 | |||
f8572c17dd | |||
d7c7784022 | |||
77cb344650 | |||
bb509bd537 | |||
d25a222bf9 | |||
0615a16124 | |||
91abc62ad0 | |||
d7d2d67558 | |||
1a55c70114 | |||
ffa830ae9b | |||
59ef053f50 | |||
df2f28bf31 | |||
311fda649f | |||
916a8b959e | |||
972b9e5474 | |||
7a72b2fc25 | |||
91bfd0f7c3 | |||
f54d714afd | |||
50e905fe4b | |||
f2a7a34abf | |||
a82068643d | |||
4ad101b53b | |||
08af298d11 | |||
451d928026 | |||
0e86dfa944 | |||
ad51c61ac8 | |||
4bb867bf52 | |||
54f24fe4b0 | |||
33a1d8de3d | |||
d5740ea844 | |||
5b0733d535 | |||
6b79ce605d | |||
8b5a398135 | |||
fcd57e9ac5 | |||
95c007953c | |||
764143d971 | |||
1af18f3028 | |||
80b58f3b45 | |||
773ebc7854 | |||
c616423edb | |||
9556abb162 | |||
c8bd1293b9 | |||
dfa95d8ed3 | |||
c85106e700 | |||
fecbe01e06 | |||
73a53e38c4 | |||
fd3675aac0 | |||
e70ef7f9e4 | |||
8b18ca15b5 | |||
06213714c5 | |||
fdd33fdeb0 | |||
cb78260d22 | |||
d36c91d10f | |||
2d85048925 | |||
63b568f4a0 | |||
d9397ef174 | |||
17f0b61576 | |||
1ef47cb48e | |||
79b8610fbe | |||
b128d127c2 | |||
6686f6415f | |||
df1f986239 | |||
06d96ec5a2 | |||
6d47045a60 | |||
410d104ad6 | |||
12dbf50ddc | |||
a54b2dd39e | |||
0a27b9e6d9 | |||
763739e3ae | |||
68b9dadf07 | |||
1faef49b51 | |||
9b1c9b1892 | |||
99b68d5b63 | |||
81dd45c814 | |||
caa1451fe8 | |||
147df5fd74 | |||
2f2bf6ca8f | |||
0bae18fe0d | |||
181bd13103 | |||
308a0c4532 | |||
6dfd1c1496 | |||
c7f961cc22 | |||
f13574d8ed | |||
cf1c7600a2 | |||
d644bcd79a | |||
5739a3d86f | |||
ded4261971 | |||
fb9b511e15 | |||
4333e90220 | |||
6c5a0c2795 | |||
c3ced8c7e6 | |||
9251b1ca26 | |||
47d540b7b8 | |||
1d9c89e3fe | |||
66c62d69b9 | |||
43ee6bfc1c | |||
ed3aa740be | |||
2d3d03b4d3 | |||
a0638ec983 | |||
3fcab67570 | |||
ceff8bd127 | |||
dee27e35b7 | |||
dd67dc87e3 | |||
097de9dbb7 | |||
8045e441a2 | |||
58723f2a8c | |||
8a04a38631 | |||
9250c592a7 | |||
636c76b03b | |||
39155ef81c | |||
2afffb39dd | |||
99d7cce0d6 | |||
eb137ec6dc | |||
6c5c09fae9 | |||
885928ea17 | |||
771741c10c | |||
63edc84103 | |||
8b37d8ec02 | |||
a8205d5b5d | |||
e5fd2c3568 | |||
5371eb52ad | |||
f7745a336f | |||
a33f29365a | |||
16b917abb1 | |||
436b6d8efb | |||
16c00ae3f5 | |||
855b03a9ea | |||
74c04d79c9 | |||
b771afe8be | |||
557b249e11 | |||
e3e58ac0be | |||
f1ba498b52 | |||
59beade079 | |||
f94ecfc7a6 | |||
13dae0f0d0 | |||
ee93448de7 | |||
6633cc4046 | |||
4ac7d0415b | |||
2f36bd5d77 | |||
7c48a299c3 | |||
17a2448237 | |||
1740a8e363 | |||
44f2195674 | |||
62791bfb47 | |||
d663f708ef | |||
6567041a3d | |||
0a45f776e0 | |||
01386b8451 | |||
d27a04e067 | |||
a49a5dcb11 | |||
bb26e04a55 | |||
51129febeb | |||
829469d0fe | |||
1f5e9a9335 | |||
b1894a0acb | |||
73ff822d24 | |||
a2d7f42138 | |||
895ee1d00b | |||
235e609eff | |||
84b0dd8980 | |||
c4a788b97b | |||
a946895fad | |||
2ce04c6a78 | |||
aacfe546d5 | |||
d468e1e4a6 | |||
50dd519c4f | |||
785c9557c8 | |||
c021e2b69f | |||
42008fb895 | |||
05aa9fa431 | |||
022afa6375 | |||
5b03f7d7b0 | |||
3fc6220009 | |||
245185d2f6 | |||
e4096b5157 | |||
a9719cada2 | |||
1c4833bc5d | |||
3343fd9813 | |||
41a6075fb5 | |||
5ac3162fca | |||
85c46ede5b | |||
68c2d27e8d | |||
4569b7eca6 | |||
d57b83fa08 | |||
747d9bfc6e | |||
a1ad3379ca | |||
450ffbe452 | |||
95581d7fbc | |||
436798b360 | |||
fe4273ca2a | |||
980dd74852 | |||
6159f299be | |||
4d9263f932 | |||
90e06dc37b | |||
ed4181617a | |||
22cc8c4986 | |||
1888a2eb82 | |||
52117e4b11 | |||
7835d78b3d | |||
5b563006f9 | |||
0fe69cea9f | |||
d8546dd140 | |||
8e4863e9cd | |||
a8d67cc607 | |||
c4d371d163 | |||
7f16ac3915 | |||
559313402e | |||
9595c6a1e5 | |||
8bbfb10cba | |||
a7fe24a294 | |||
4be24fdcaf | |||
95a7a3c0ec | |||
c237cf2c34 | |||
b12278e334 | |||
225482f7ed | |||
1de7754616 | |||
18967dadbf | |||
029fa9b8dc | |||
2381558169 | |||
9604dea90a | |||
e83238b681 | |||
ba9fa989ff | |||
67b9622bf5 | |||
f47c9ce9c6 | |||
146a96686b | |||
b0b63e58f8 | |||
8d433761d1 | |||
de6d2e7f40 | |||
905da44958 | |||
c1ac9396ee | |||
7f6421d977 | |||
a06793e25a | |||
7cf48db2fb | |||
4ff681e0bb | |||
7dea39736b | |||
1e0621797d | |||
f92dc28696 | |||
d3da30be6d | |||
100333c021 | |||
e905299eba | |||
6523a07a9f | |||
3c1f05cdc3 | |||
3000b08ec7 | |||
dd005a1e4c | |||
e5e3dc6e19 | |||
7af2ff0843 | |||
d688aeb184 | |||
473bc94278 | |||
23e1bfa720 | |||
3b41f206bc | |||
b3826c108d | |||
508026d09a | |||
5ee46a214c | |||
045b524bc6 | |||
cac791a6ca | |||
2591feb72e | |||
5b60ecbb18 | |||
d6616fed3f | |||
10902c0485 | |||
9680805bdb | |||
dbeb65da06 | |||
516c623df5 | |||
d4b3827efa | |||
0e26077722 | |||
d999c1d3ba | |||
31b1cb8ca6 | |||
5532ea5d2a | |||
3661b4f95b | |||
2dc4ce5da5 | |||
9707bb89e6 | |||
0331522128 | |||
ca51cf2509 | |||
89002b4562 | |||
bbd3d9ffe0 | |||
c2201480cf | |||
ee4aca3010 | |||
957a28f239 | |||
1bb06d775c | |||
b6bc7fb6d5 | |||
1dd13c4812 | |||
0fa2c861d6 | |||
2abc72b606 | |||
61e282af1a | |||
dd2d7d9215 | |||
2cc98bf410 | |||
64151790d0 | |||
58adebb325 | |||
983f990fe9 | |||
4918bf0ab2 | |||
08b6251a7a | |||
817b60a758 | |||
793ee66afa | |||
de6abf1439 | |||
9d7af671c5 | |||
1ed9992775 | |||
8417efc630 | |||
cc26f06289 | |||
1f0ce101eb | |||
4555b98aff | |||
c350f3c2af | |||
8366716456 | |||
effe1bacdf | |||
bbb88da9e0 | |||
f481bb74c4 | |||
042a297d1a | |||
e58abf0705 | |||
d3d78846dc | |||
dcb80ac105 | |||
2be14b95b3 | |||
1119ed6620 | |||
52ded6e8cc | |||
99fc5f16d2 | |||
f85106b86a | |||
84a33ca7b9 | |||
f00a169081 | |||
02c77d2e44 | |||
26fd68a37c | |||
21df8f8c08 | |||
63df653ad2 | |||
1200ae0ee6 | |||
64056c5527 | |||
063871a1eb | |||
4683291c1f | |||
6ad0db2cdb | |||
692d1bfb9e | |||
cdd084bbe8 | |||
8a7a1f519c | |||
81ae675f2d | |||
7f78a8428e | |||
7447d95f1b | |||
de0f7a08fe | |||
cd4b90fef4 | |||
271b583876 | |||
c65b3429ad | |||
b7e6e5cbc9 | |||
fda5aed89f | |||
ab1ec12698 | |||
059cdecf7d | |||
cf32d9d668 | |||
ce42723ad2 | |||
f4604e1c58 | |||
432de060ea | |||
97fa20237f | |||
780d9d5b78 | |||
6346d5608e | |||
2e83107c18 | |||
a5fcd66c95 | |||
80582923bb | |||
dfb261ac9a | |||
31b06e52ea | |||
4cc2498c24 | |||
a20855dfd9 | |||
a27be7d054 | |||
e62e9f5bd4 | |||
e3c46b40a0 | |||
44a395f134 | |||
e13124a426 | |||
ccf076ed1e | |||
9a59763df1 | |||
b68e2a1ed0 | |||
b05c21680d | |||
04e35e86d6 | |||
e73d7082a6 | |||
3406e604cd | |||
7ebf2d7a4f | |||
067d47f0ec | |||
7ba0073052 | |||
8e93d294e5 | |||
32d59c31d8 | |||
773fcd0780 | |||
cf21bfabf2 | |||
8b2b12e767 | |||
680d5221db | |||
092ae1fec8 | |||
dc5ddd3022 | |||
eca269cbf2 | |||
de1c8ece43 | |||
cb5b527d74 | |||
76c8279101 | |||
05ee55d617 | |||
3d01c9b363 | |||
8d6751c88d | |||
8e4fd2c167 | |||
41ba4b2bc3 | |||
5451035215 | |||
2bd2e501d9 | |||
cc3646640e | |||
760e97c7e7 | |||
721d17e181 | |||
dbdb9bc164 | |||
47159ad3c2 | |||
aabfb61834 | |||
f1fd8d84c3 | |||
367b98bfcb | |||
371ac83169 | |||
dba91bceca | |||
8f2273328b | |||
5dcdf645d6 | |||
011f0d5fee | |||
355f925954 | |||
f366c20869 | |||
408fd2030c | |||
4fcbb6ae61 | |||
3eca8c60e3 | |||
403004a85e | |||
a455819871 | |||
3368f55a88 | |||
58c8c0edc7 | |||
0b5cf4e5fd | |||
7c30430320 | |||
6e36e2ddfd | |||
c846147275 | |||
c141a82dfb | |||
e69b4b7f45 | |||
4f4e5f0d75 | |||
1c61e46f37 | |||
2ac560975c | |||
343b659755 | |||
b6e78b5f04 | |||
8236f3e5e7 | |||
538e2e0c9e | |||
65c6117962 | |||
884a8a8b23 | |||
0bc9718e3b | |||
762fb86ce7 | |||
9cb211470f | |||
e549f5c4a9 | |||
a8b46f1bf4 | |||
c0dc6eb35c | |||
9a5e5db271 | |||
f17622a1e1 | |||
8845f6a4c6 | |||
722dba1203 | |||
138c318be6 | |||
8576af247b | |||
4000d60305 | |||
c6e1efa0fe | |||
15e1844956 | |||
23c165dee1 | |||
315e53f064 | |||
6fe806c2b5 | |||
8cb328b6f2 | |||
002a70a2a4 | |||
7a1b2d97d4 | |||
539051c892 | |||
002e22510f | |||
93f61483b5 | |||
82ccba4267 | |||
f18f69809a | |||
9e3110988c | |||
ac52ea4d8e | |||
cbc7e16dc0 | |||
825fe9a4e2 | |||
a38e6ca99b | |||
2b32d00589 | |||
5a5ca76d9d | |||
162db2f3b9 | |||
d5d6a4daf9 | |||
3b3f0c1a29 | |||
299e25ab3c | |||
743e97738f | |||
9a55fda69c | |||
4eccd30ce2 | |||
42116b5bce | |||
9e00142b40 | |||
f88552ee7f | |||
f7c8940ff2 | |||
e757212741 | |||
c1bac30694 | |||
04bb7b4db0 | |||
a12a8c566d | |||
9a50f1f318 | |||
584392049c | |||
82f1789589 | |||
d3b7928b99 | |||
b025d068f2 | |||
fc5ed15af5 | |||
b9cd71d8eb | |||
2ea9f147ab | |||
f7c02cb3b0 | |||
3ef8a48ded | |||
93503c0ca9 | |||
cd177dee86 | |||
cd74d01324 | |||
fb528941ea | |||
e69f1e0b8c | |||
4a988aaeb8 | |||
f1a080c94e | |||
36ae29d746 | |||
00153754bb | |||
57f365979c | |||
ce3bb2f1d4 | |||
0ae637f531 | |||
4ec441560b | |||
a0f6e84ec2 | |||
ff2f7a8505 | |||
542c7e12b8 | |||
bca4af0c79 | |||
6bc72e513c | |||
90455a8111 | |||
ce1faa6020 | |||
f08b65ece1 | |||
8aa5125d5b | |||
93051dba0e | |||
143bf7608e | |||
53b6efaa6e | |||
9fc1404415 | |||
59222f7a35 | |||
6b15e772ac | |||
487d7fb26b | |||
77e99e92fb | |||
51d61af863 | |||
b0664b3c85 | |||
04e101c605 | |||
034c0f0fd8 | |||
39e0acb55a | |||
6420fe43da | |||
ac8afc4ac0 | |||
1e13dbb99c | |||
994c79569e | |||
4d9b6c5472 | |||
5965ff4364 | |||
f55c32ed37 | |||
953773a314 | |||
c1bfeb4c23 | |||
0af7151ae9 | |||
f03ee95bf0 | |||
c971c4d1d5 | |||
165b5f8c50 | |||
3c31c98d95 | |||
0f383a6545 | |||
44405e0cd7 | |||
0ed74d0aa4 | |||
3a408c0146 | |||
5c2de6aeb6 | |||
6f26e3bf79 | |||
5627bd8d7d | |||
4c3ae6f8ce | |||
cef6fef079 | |||
02ac4b3b04 | |||
042a5d0755 | |||
6006a0ba36 | |||
41aa1e51da | |||
5506f8001e | |||
194c4e9e9f | |||
49350f738f | |||
1756d7a516 | |||
ed4e9a50d5 | |||
d6cb9d72d8 | |||
27128a476c | |||
ecb26a0b16 | |||
560bdc4cb7 | |||
33696a8aed | |||
1998ecab45 | |||
a1cce9b796 | |||
2a3d962dc5 | |||
c7209dbd4f | |||
62bfc545d3 | |||
abe4efc4a2 | |||
3b4c48d9f5 | |||
ad3f1cf534 | |||
ee8249eec7 | |||
1ceaea844a | |||
77d68080e8 | |||
e0e63dd4da | |||
2c386fb792 | |||
037294b077 | |||
f62d07633d | |||
c5a625ae28 | |||
d110977580 | |||
4582ae91ab | |||
458b2386ed | |||
7cfd570c15 | |||
b6af64b970 | |||
d1196c3e28 | |||
03df4f253c | |||
b161f56a5c | |||
570eb7463a | |||
812df78b75 | |||
4464bf2eaa | |||
6132788b02 | |||
0c954135a3 | |||
f5e01b9196 | |||
1a8abde884 | |||
0201f5a456 | |||
b96d560f0a | |||
bc95a30db6 | |||
1a06f5e671 | |||
58c21d9868 | |||
e9bbf804ba | |||
be72fefb18 | |||
d44a1a4245 | |||
781fd1df9a | |||
27435ad82e | |||
d846f527af | |||
de23ea7f29 | |||
0fd63ece7d | |||
af9d9c0a5c | |||
f59d00b8e6 | |||
439ada614c | |||
f72540f5c2 | |||
9ec06199b1 | |||
4ee70913bd | |||
cf0ba40115 | |||
002e3fa171 | |||
f94e422b61 | |||
9ee7118bf6 | |||
155d621262 | |||
06123d74ba | |||
4fe0786bbd | |||
5f0ff8348f | |||
a3d405f670 | |||
851c82df85 | |||
72fa5fa922 | |||
ae9ac872c0 | |||
9fa838e366 | |||
877a9bd7f3 | |||
1ffeb4d44d | |||
2bc29c1fd3 | |||
ca17c9bc4f | |||
1026949b2b | |||
52764e140e | |||
30e501e7b0 | |||
0c8e69ed7c | |||
71c268da14 | |||
6607a63159 | |||
639997d67f | |||
0530b324c1 | |||
cb45ef2c03 | |||
4e444a066c | |||
a507f24694 | |||
985e3e85d6 | |||
620ba6e185 | |||
13b6f8fad4 | |||
e344585d99 | |||
9307acf7fa | |||
ed429d570e | |||
f8024f072f | |||
42385c81be | |||
473e7d951b | |||
3a8536b45e | |||
e5360ff431 | |||
3f904553ea | |||
289e8a08c3 | |||
65efdc8c81 | |||
54741b7cc4 | |||
a077ecba85 | |||
37b0dab0e8 | |||
d9de2d3d7b | |||
fb42cd8a0f | |||
1ffadbc270 | |||
08c716d110 | |||
99a1e30ab0 | |||
9b964db4c9 | |||
8452cd9efa | |||
9c31344bbc | |||
5dc52975ff | |||
40680a47ab | |||
d18761892e | |||
2eba3b321e | |||
0b1b8b91b9 | |||
db39370701 | |||
9fb058d5bc | |||
b5daa92c9d | |||
cc7419308b | |||
b992596236 | |||
b427cb6a3d | |||
6be5796888 | |||
3172bcd095 | |||
e6fa7c0b2b | |||
aa2abc50bf | |||
4604a6368c | |||
1c145e2ba9 | |||
f9e5a68156 | |||
25b0b15989 | |||
8c457cfa04 | |||
599d3a4d8a | |||
975f0141be | |||
51c34267a9 | |||
ea6c59e5e9 | |||
a84eeeb240 | |||
821c23e202 | |||
b75483e597 | |||
12c058bc49 | |||
76eeaffbb2 | |||
5bbe0eab25 | |||
73ce616bd9 | |||
72f616fa14 | |||
fceacf7081 | |||
0e2a00cbc8 | |||
1fcbc346bc | |||
45fa946e8b | |||
79972f8305 | |||
6b3bb2c747 | |||
e3f645af19 | |||
ae4b49c668 | |||
fd910cb828 | |||
c553ac7402 | |||
3003987c3a | |||
9903d536b5 | |||
aa7fd34097 | |||
02fc343642 | |||
456fb276d6 | |||
6e54d3cea9 | |||
1981ffbea0 | |||
6b11284e8a | |||
2642410702 | |||
3abd13e57b | |||
bf9f7a747e | |||
3094bd96df | |||
37cb636140 | |||
66e7b314f7 | |||
4d310434ab | |||
2b4fd40d62 | |||
97bb327b2a | |||
8d1c7cef3e | |||
cc8d84330c | |||
e1afa43aa3 | |||
29a490f6dc | |||
9b96cfc452 | |||
4616f0a4a4 | |||
f22be3a586 | |||
10d8cc21e6 | |||
360e4275ed | |||
c2f403f998 | |||
a5f3197651 | |||
f062b7cf0d | |||
e87251c7d9 | |||
d01779dc6c | |||
9aeaac5a96 | |||
7ac29827d2 | |||
6706f3a4b4 | |||
9504e4d540 | |||
54c84a7211 | |||
ba20fc735e | |||
a8250abbf1 | |||
31d4ba2eae | |||
976ae5707e | |||
6ceeadc0f0 | |||
7106b0484d | |||
938f4e4f1c | |||
627bf756b1 | |||
a56435801d | |||
27fac33c44 | |||
cab4cd3b8c | |||
ce166b4d8f | |||
d067fb2ec8 | |||
d46857d3b1 | |||
4c7dfceb9a | |||
8c8384c711 | |||
bf6bdab80c | |||
f2223cf2cb |
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,7 +1,7 @@
|
||||||
Cargo.lock
|
|
||||||
target
|
target
|
||||||
*~
|
*~
|
||||||
*.bk
|
*.bk
|
||||||
*.swp
|
*.swp
|
||||||
.vscode
|
.vscode
|
||||||
builddir
|
builddir
|
||||||
|
.meson-subproject-wrap-hash.txt
|
||||||
|
|
387
.gitlab-ci.yml
387
.gitlab-ci.yml
|
@ -1,4 +1,4 @@
|
||||||
.templates_sha: &templates_sha 567700e483aabed992d0a4fea84994a0472deff6
|
.templates_sha: &templates_sha fddab8aa63e89a8e65214f59860d9c0f030360c9
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- project: 'freedesktop/ci-templates'
|
- project: 'freedesktop/ci-templates'
|
||||||
|
@ -14,12 +14,14 @@ include:
|
||||||
file: '/.gitlab-image-tags.yml'
|
file: '/.gitlab-image-tags.yml'
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
FDO_UPSTREAM_REPO: gstreamer/gst-plugins-rs
|
FDO_UPSTREAM_REPO: gstreamer/gstreamer-rs
|
||||||
|
|
||||||
# We use GStreamer image to build the documentation as it is the simplest way
|
# We use GStreamer image to build the documentation as it is the simplest way
|
||||||
# to ensure that we are testing against the same thing as GStreamer itself.
|
# to ensure that we are testing against the same thing as GStreamer itself.
|
||||||
# The tag name is included above from the main repo.
|
# The tag name is included above from the main repo.
|
||||||
GSTREAMER_DOC_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer/amd64/fedora:$FEDORA_TAG-main"
|
GSTREAMER_DOC_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer/amd64/fedora:$FEDORA_TAG-main"
|
||||||
|
# Use the gstreamer image to trigger the cerbero job, same as the monorepo
|
||||||
|
CERBERO_TRIGGER_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer/amd64/fedora:$FEDORA_TAG-main"
|
||||||
WINDOWS_BASE: "registry.freedesktop.org/gstreamer/gstreamer-rs/windows"
|
WINDOWS_BASE: "registry.freedesktop.org/gstreamer/gstreamer-rs/windows"
|
||||||
WINDOWS_RUST_MINIMUM_IMAGE: "$WINDOWS_BASE:$GST_RS_IMG_TAG-main-$GST_RS_MSRV"
|
WINDOWS_RUST_MINIMUM_IMAGE: "$WINDOWS_BASE:$GST_RS_IMG_TAG-main-$GST_RS_MSRV"
|
||||||
WINDOWS_RUST_STABLE_IMAGE: "$WINDOWS_BASE:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
WINDOWS_RUST_STABLE_IMAGE: "$WINDOWS_BASE:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
||||||
|
@ -38,131 +40,54 @@ default:
|
||||||
interruptible: true
|
interruptible: true
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- "prep"
|
- "trigger"
|
||||||
- "lint"
|
- "lint"
|
||||||
- "test"
|
- "test"
|
||||||
- "extras"
|
- "extras"
|
||||||
|
- "integration"
|
||||||
|
|
||||||
.debian:11:
|
# This is an empty job that is used to trigger the pipeline.
|
||||||
|
trigger:
|
||||||
|
image: alpine:latest
|
||||||
|
stage: 'trigger'
|
||||||
variables:
|
variables:
|
||||||
FDO_DISTRIBUTION_VERSION: 'bullseye-slim'
|
GIT_STRATEGY: none
|
||||||
before_script:
|
tags: [ 'placeholder-job' ]
|
||||||
- source ./ci/env.sh
|
script:
|
||||||
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
- echo "Trigger job done, now running the pipeline."
|
||||||
# If cargo exists assume we probably will want to update
|
|
||||||
# the lockfile
|
|
||||||
- |
|
|
||||||
if command -v cargo; then
|
|
||||||
cargo generate-lockfile
|
|
||||||
cargo update
|
|
||||||
fi
|
|
||||||
|
|
||||||
.debian:11-stable:
|
|
||||||
extends: .debian:11
|
|
||||||
variables:
|
|
||||||
FDO_DISTRIBUTION_TAG: '$GST_RS_STABLE-${GST_RS_IMG_TAG}_2022-09-07.0'
|
|
||||||
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:$GST_RS_STABLE-$GST_RS_IMG_TAG"
|
|
||||||
|
|
||||||
.debian:11-msrv:
|
|
||||||
extends: .debian:11
|
|
||||||
variables:
|
|
||||||
FDO_DISTRIBUTION_TAG: '$GST_RS_MSRV-${GST_RS_IMG_TAG}_2022-09-07.0'
|
|
||||||
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:$GST_RS_MSRV-$GST_RS_IMG_TAG"
|
|
||||||
|
|
||||||
.debian:11-nightly:
|
|
||||||
extends: .debian:11
|
|
||||||
variables:
|
|
||||||
FDO_DISTRIBUTION_TAG: 'nightly-${GST_RS_IMG_TAG}_2022-09-07.0'
|
|
||||||
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:nightly-$GST_RS_IMG_TAG"
|
|
||||||
|
|
||||||
.build-debian-container:
|
|
||||||
extends:
|
|
||||||
- .fdo.container-build@debian
|
|
||||||
stage: prep
|
|
||||||
variables:
|
|
||||||
FDO_DISTRIBUTION_PACKAGES: "libcsound64-dev llvm clang nasm libsodium-dev libwebp-dev python3-pip"
|
|
||||||
FDO_DISTRIBUTION_EXEC: >-
|
|
||||||
bash ci/install-dav1d.sh &&
|
|
||||||
apt clean &&
|
|
||||||
bash ./ci/install-rust-ext.sh &&
|
|
||||||
pip install tomli
|
|
||||||
rules:
|
rules:
|
||||||
- if: '$UPDATE_IMG == null'
|
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||||
|
# If the MR is assigned to the Merge bot, trigger the pipeline automatically
|
||||||
|
- if: '$CI_MERGE_REQUEST_ASSIGNEES == "gstreamer-merge-bot"'
|
||||||
|
# Require explicit action to trigger tests post merge
|
||||||
|
- if: '$CI_PROJECT_NAMESPACE == "gstreamer" && $CI_COMMIT_BRANCH == "main"'
|
||||||
|
when: 'manual'
|
||||||
|
# When the assignee isn't the merge bot, require an explicit action to trigger the pipeline
|
||||||
|
# to avoid wasting CI resources
|
||||||
|
- if: '$CI_MERGE_REQUEST_ASSIGNEES != "gstreamer-merge-bot"'
|
||||||
|
when: 'manual'
|
||||||
|
allow_failure: false
|
||||||
|
|
||||||
build-stable:
|
.debian:12:
|
||||||
extends:
|
|
||||||
- .build-debian-container
|
|
||||||
- .debian:11-stable
|
|
||||||
|
|
||||||
build-msrv:
|
|
||||||
extends:
|
|
||||||
- .build-debian-container
|
|
||||||
- .debian:11-msrv
|
|
||||||
|
|
||||||
build-nightly:
|
|
||||||
extends:
|
|
||||||
- .build-debian-container
|
|
||||||
- .debian:11-nightly
|
|
||||||
|
|
||||||
# Those jobs are triggered by gstreamer-rs when updating its images
|
|
||||||
update-stable:
|
|
||||||
extends: build-stable
|
|
||||||
rules:
|
|
||||||
- if: '$UPDATE_IMG == "stable"'
|
|
||||||
variables:
|
|
||||||
FDO_FORCE_REBUILD: 1
|
|
||||||
|
|
||||||
update-msrv:
|
|
||||||
extends: build-msrv
|
|
||||||
rules:
|
|
||||||
- if: '$UPDATE_IMG == "msrv"'
|
|
||||||
variables:
|
|
||||||
FDO_FORCE_REBUILD: 1
|
|
||||||
|
|
||||||
update-nightly:
|
|
||||||
extends: build-nightly
|
|
||||||
rules:
|
|
||||||
- if: '$UPDATE_IMG == "nightly"'
|
|
||||||
variables:
|
|
||||||
FDO_FORCE_REBUILD: 1
|
|
||||||
|
|
||||||
.dist-debian-container:
|
|
||||||
extends:
|
|
||||||
- .fdo.distribution-image@debian
|
|
||||||
variables:
|
variables:
|
||||||
SODIUM_USE_PKG_CONFIG: "true"
|
SODIUM_USE_PKG_CONFIG: "true"
|
||||||
after_script:
|
after_script:
|
||||||
- rm -rf target
|
- rm -rf target
|
||||||
|
before_script:
|
||||||
|
- source ./ci/env.sh
|
||||||
|
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
||||||
|
|
||||||
.img-stable:
|
.debian:12-stable:
|
||||||
extends:
|
extends: .debian:12
|
||||||
- .dist-debian-container
|
image: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bookworm-slim:$GST_RS_STABLE-$GST_RS_IMG_TAG"
|
||||||
- .debian:11-stable
|
|
||||||
needs:
|
|
||||||
- job: 'build-stable'
|
|
||||||
optional: true
|
|
||||||
- job: 'update-stable'
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
.img-msrv:
|
.debian:12-msrv:
|
||||||
extends:
|
extends: .debian:12
|
||||||
- .dist-debian-container
|
image: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bookworm-slim:$GST_RS_MSRV-$GST_RS_IMG_TAG"
|
||||||
- .debian:11-msrv
|
|
||||||
needs:
|
|
||||||
- job: 'build-msrv'
|
|
||||||
optional: true
|
|
||||||
- job: 'update-msrv'
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
.img-nightly:
|
.debian:12-nightly:
|
||||||
extends:
|
extends: .debian:12
|
||||||
- .dist-debian-container
|
image: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bookworm-slim:nightly-$GST_RS_IMG_TAG"
|
||||||
- .debian:11-nightly
|
|
||||||
needs:
|
|
||||||
- job: 'build-nightly'
|
|
||||||
optional: true
|
|
||||||
- job: 'update-nightly'
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
.cargo test:
|
.cargo test:
|
||||||
stage: "test"
|
stage: "test"
|
||||||
|
@ -174,48 +99,44 @@ update-nightly:
|
||||||
- rustc --version
|
- rustc --version
|
||||||
|
|
||||||
- cargo build --locked --color=always --workspace --all-targets
|
- cargo build --locked --color=always --workspace --all-targets
|
||||||
- G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets
|
- RUST_BACKTRACE=1 G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets
|
||||||
- cargo build --locked --color=always --workspace --all-targets --all-features
|
- cargo build --locked --color=always --workspace --all-targets --all-features --exclude gst-plugin-gtk4
|
||||||
- G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets --all-features
|
- RUST_BACKTRACE=1 G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets --all-features --exclude gst-plugin-gtk4
|
||||||
- cargo build --locked --color=always --workspace --all-targets --no-default-features
|
- cargo build --locked --color=always --workspace --all-targets --no-default-features
|
||||||
- G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets --no-default-features
|
- RUST_BACKTRACE=1 G_DEBUG=fatal_warnings cargo test --locked --color=always --workspace --all-targets --no-default-features
|
||||||
|
|
||||||
test msrv:
|
test msrv:
|
||||||
extends:
|
extends:
|
||||||
- '.cargo test'
|
- '.cargo test'
|
||||||
- .img-msrv
|
- '.debian:12-msrv'
|
||||||
rules:
|
needs: [ "trigger" ]
|
||||||
- if: '$UPDATE_IMG == null || $UPDATE_IMG == "msrv"'
|
|
||||||
|
|
||||||
test stable:
|
test stable:
|
||||||
extends:
|
extends:
|
||||||
- '.cargo test'
|
- '.cargo test'
|
||||||
- .img-stable
|
- '.debian:12-stable'
|
||||||
rules:
|
needs: [ "trigger" ]
|
||||||
- if: '$UPDATE_IMG == null || $UPDATE_IMG == "stable"'
|
|
||||||
|
|
||||||
test nightly:
|
test nightly:
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
extends:
|
extends:
|
||||||
- '.cargo test'
|
- '.cargo test'
|
||||||
- .img-nightly
|
- '.debian:12-nightly'
|
||||||
rules:
|
needs: [ "trigger" ]
|
||||||
- if: '$UPDATE_IMG == null || $UPDATE_IMG == "nightly"'
|
|
||||||
|
|
||||||
.meson:
|
.meson:
|
||||||
extends: .img-stable
|
extends: .debian:12-stable
|
||||||
rules:
|
|
||||||
- if: '$UPDATE_IMG == null || $UPDATE_IMG == "stable"'
|
|
||||||
variables:
|
variables:
|
||||||
# csound-sys only looks at /usr/lib and /usr/local top levels
|
# csound-sys only looks at /usr/lib and /usr/local top levels
|
||||||
CSOUND_LIB_DIR: '/usr/lib/x86_64-linux-gnu/'
|
CSOUND_LIB_DIR: '/usr/lib/x86_64-linux-gnu/'
|
||||||
|
|
||||||
meson shared:
|
meson shared:
|
||||||
extends: .meson
|
extends: .meson
|
||||||
|
needs: [ "trigger" ]
|
||||||
variables:
|
variables:
|
||||||
CI_ARTIFACTS_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/"
|
CI_ARTIFACTS_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/"
|
||||||
script:
|
script:
|
||||||
- meson build --default-library=shared --prefix=$(pwd)/install
|
- meson build --default-library=shared --prefix=$(pwd)/install --fatal-meson-warnings
|
||||||
- ninja -C build install
|
- ninja -C build install
|
||||||
- ./ci/check-installed.py install
|
- ./ci/check-installed.py install
|
||||||
- ninja -C build docs/gst_plugins_cache.json
|
- ninja -C build docs/gst_plugins_cache.json
|
||||||
|
@ -229,8 +150,9 @@ meson shared:
|
||||||
|
|
||||||
meson static:
|
meson static:
|
||||||
extends: .meson
|
extends: .meson
|
||||||
|
needs: [ "trigger" ]
|
||||||
script:
|
script:
|
||||||
- meson build --default-library=static --prefix=$(pwd)/install -Dsodium=built-in
|
- meson build --default-library=static --prefix=$(pwd)/install -Dsodium-source=built-in
|
||||||
- ninja -C build install
|
- ninja -C build install
|
||||||
- ./ci/generate-static-test.py test-static-link-all
|
- ./ci/generate-static-test.py test-static-link-all
|
||||||
- cd test-static-link-all
|
- cd test-static-link-all
|
||||||
|
@ -246,10 +168,10 @@ meson static:
|
||||||
# Check that the gstreamer documentation keeps working
|
# Check that the gstreamer documentation keeps working
|
||||||
documentation:
|
documentation:
|
||||||
image: $GSTREAMER_DOC_IMAGE
|
image: $GSTREAMER_DOC_IMAGE
|
||||||
|
stage: 'integration'
|
||||||
variables:
|
variables:
|
||||||
MESON_ARGS: >
|
MESON_ARGS: >
|
||||||
-Ddoc=enabled
|
-Ddoc=enabled
|
||||||
-Domx=disabled
|
|
||||||
-Dpython=disabled
|
-Dpython=disabled
|
||||||
-Dlibav=disabled
|
-Dlibav=disabled
|
||||||
-Dlibnice=disabled
|
-Dlibnice=disabled
|
||||||
|
@ -258,7 +180,7 @@ documentation:
|
||||||
-Dsharp=disabled
|
-Dsharp=disabled
|
||||||
-Dgst-examples=disabled
|
-Dgst-examples=disabled
|
||||||
-Drs=enabled
|
-Drs=enabled
|
||||||
-Dgst-plugins-rs:sodium=system
|
-Dgst-plugins-rs:sodium-source=system
|
||||||
-Dgst-docs:fatal_warnings=true
|
-Dgst-docs:fatal_warnings=true
|
||||||
-Dorc=disabled
|
-Dorc=disabled
|
||||||
script:
|
script:
|
||||||
|
@ -279,54 +201,65 @@ documentation:
|
||||||
paths:
|
paths:
|
||||||
- documentation/
|
- documentation/
|
||||||
needs: []
|
needs: []
|
||||||
|
rules:
|
||||||
|
# Run job if the MR is assigned to the Merge bot or it a post-merge pipeline on main branch
|
||||||
|
- if: '$CI_MERGE_REQUEST_ASSIGNEES == "gstreamer-merge-bot"'
|
||||||
|
when: 'always'
|
||||||
|
- if: '$CI_PROJECT_NAMESPACE == "gstreamer" && $CI_COMMIT_BRANCH == "main"'
|
||||||
|
when: 'always'
|
||||||
|
# Require explicit action to trigger otherwise
|
||||||
|
- if: '$CI_PROJECT_NAMESPACE != "gstreamer" || $CI_COMMIT_BRANCH != "main"'
|
||||||
|
when: 'manual'
|
||||||
|
|
||||||
# build gst-plugins-rs as a gst-build subproject
|
# build gst-plugins-rs as a gst-build subproject
|
||||||
gst-build:
|
# Disabled because of https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/262
|
||||||
extends: .meson
|
#gst-build:
|
||||||
rules:
|
# extends: .meson
|
||||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
# rules:
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
# - if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
when: 'manual'
|
# - if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
allow_failure: true
|
# when: 'manual'
|
||||||
variables:
|
# allow_failure: true
|
||||||
MESON_ARGS: >
|
# variables:
|
||||||
-Domx=disabled
|
# MESON_ARGS: >
|
||||||
-Dpython=disabled
|
# -Domx=disabled
|
||||||
-Dlibav=disabled
|
# -Dpython=disabled
|
||||||
-Dlibnice=disabled
|
# -Dlibav=disabled
|
||||||
-Dugly=disabled
|
# -Dlibnice=disabled
|
||||||
-Dbad=disabled
|
# -Dugly=disabled
|
||||||
-Ddevtools=disabled
|
# -Dbad=disabled
|
||||||
-Dges=disabled
|
# -Ddevtools=disabled
|
||||||
-Drtsp_server=disabled
|
# -Dges=disabled
|
||||||
-Dvaapi=disabled
|
# -Drtsp_server=disabled
|
||||||
-Dsharp=disabled
|
# -Dvaapi=disabled
|
||||||
-Dgst-examples=disabled
|
# -Dsharp=disabled
|
||||||
-Drs=enabled
|
# -Dgst-examples=disabled
|
||||||
-Dgst-plugins-rs:sodium=system
|
# -Drs=enabled
|
||||||
script:
|
# -Dgst-plugins-rs:sodium-source=system
|
||||||
- P=$(pwd)
|
# script:
|
||||||
- cd ..
|
# - P=$(pwd)
|
||||||
- rm -rf gstreamer
|
# - cd ..
|
||||||
- git clone --depth 1 https://gitlab.freedesktop.org/gstreamer/gstreamer.git --branch main
|
# - rm -rf gstreamer
|
||||||
- cd gstreamer
|
# - git clone --depth 1 https://gitlab.freedesktop.org/gstreamer/gstreamer.git --branch main
|
||||||
- ln -s $P subprojects/gst-plugins-rs
|
# - cd gstreamer
|
||||||
- meson build $MESON_ARGS
|
# - ln -s $P subprojects/gst-plugins-rs
|
||||||
- ninja -C build
|
# - meson build $MESON_ARGS
|
||||||
# Check static Rust plugins can be linked into gst-full
|
# - ninja -C build
|
||||||
- meson build-gst-full --default-library=static $MESON_ARGS
|
# # Check static Rust plugins can be linked into gst-full
|
||||||
- ninja -C build-gst-full
|
# - meson build-gst-full --default-library=static $MESON_ARGS
|
||||||
- meson devenv -C build-gst-full ./gst-inspect-1.0 rsaudiofx
|
# - ninja -C build-gst-full
|
||||||
artifacts:
|
# - meson devenv -C build-gst-full ./gst-inspect-1.0 rsaudiofx
|
||||||
expire_in: '7 days'
|
# artifacts:
|
||||||
when: always
|
# expire_in: '7 days'
|
||||||
paths:
|
# when: always
|
||||||
- 'build/meson-logs/'
|
# paths:
|
||||||
- 'build-gst-full/meson-logs/'
|
# - 'build/meson-logs/'
|
||||||
|
# - 'build-gst-full/meson-logs/'
|
||||||
|
|
||||||
.msvc2019 build:
|
.msvc2019 build:
|
||||||
stage: 'test'
|
stage: 'test'
|
||||||
needs: []
|
needs:
|
||||||
|
- 'trigger'
|
||||||
tags:
|
tags:
|
||||||
- 'docker'
|
- 'docker'
|
||||||
- 'windows'
|
- 'windows'
|
||||||
|
@ -355,64 +288,97 @@ test windows stable:
|
||||||
image: "$WINDOWS_RUST_STABLE_IMAGE"
|
image: "$WINDOWS_RUST_STABLE_IMAGE"
|
||||||
|
|
||||||
rustfmt:
|
rustfmt:
|
||||||
extends: .img-stable
|
extends: '.debian:12-stable'
|
||||||
stage: "lint"
|
stage: "lint"
|
||||||
rules:
|
tags: [ 'placeholder-job' ]
|
||||||
- when: 'always'
|
needs: []
|
||||||
script:
|
script:
|
||||||
- cargo fmt --version
|
- cargo fmt --version
|
||||||
- cargo fmt -- --color=always --check
|
- cargo fmt -- --color=always --check
|
||||||
|
|
||||||
check commits:
|
typos:
|
||||||
extends: .img-stable
|
extends: '.debian:12-stable'
|
||||||
stage: "lint"
|
stage: "lint"
|
||||||
rules:
|
tags: [ 'placeholder-job' ]
|
||||||
- when: 'always'
|
needs: []
|
||||||
|
script:
|
||||||
|
- typos
|
||||||
|
|
||||||
|
gstwebrtc-api lint:
|
||||||
|
image: node:lts
|
||||||
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
|
needs: []
|
||||||
|
script:
|
||||||
|
- cd net/webrtc/gstwebrtc-api
|
||||||
|
- npm install
|
||||||
|
- npm run check
|
||||||
|
|
||||||
|
check commits:
|
||||||
|
extends: '.debian:12-stable'
|
||||||
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
|
needs: []
|
||||||
script:
|
script:
|
||||||
- ci-fairy check-commits --textwidth 0 --no-signed-off-by
|
- ci-fairy check-commits --textwidth 0 --no-signed-off-by
|
||||||
|
- ci/check-for-symlinks.sh
|
||||||
|
- ci/check-meson-version.sh
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
extends: .img-stable
|
extends: '.debian:12-stable'
|
||||||
|
needs:
|
||||||
|
- "trigger"
|
||||||
|
- "test stable"
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
variables:
|
variables:
|
||||||
# csound-sys only looks at /usr/lib and /usr/local top levels
|
# csound-sys only looks at /usr/lib and /usr/local top levels
|
||||||
CSOUND_LIB_DIR: '/usr/lib/x86_64-linux-gnu/'
|
CSOUND_LIB_DIR: '/usr/lib/x86_64-linux-gnu/'
|
||||||
rules:
|
|
||||||
- when: 'always'
|
|
||||||
script:
|
script:
|
||||||
- cargo clippy --locked --color=always --all --all-features --all-targets -- -D warnings
|
- cargo clippy --locked --color=always --all --all-targets -- -D warnings -A unknown-lints
|
||||||
|
- cargo clippy --locked --color=always --all --all-features --all-targets --exclude gst-plugin-gtk4 -- -D warnings -A unknown-lints
|
||||||
|
- cargo clippy --locked --color=always --all --all-targets --no-default-features -- -D warnings -A unknown-lints
|
||||||
|
|
||||||
deny:
|
deny:
|
||||||
extends: .img-stable
|
extends: .debian:12-stable
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
|
needs:
|
||||||
|
- "trigger"
|
||||||
|
- "test stable"
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
script:
|
script:
|
||||||
- cargo deny check
|
- cargo update --color=always
|
||||||
|
- cargo deny --color=always --workspace --all-features check all
|
||||||
|
|
||||||
outdated:
|
outdated:
|
||||||
extends: .img-stable
|
extends: '.debian:12-stable'
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
needs:
|
||||||
|
- "trigger"
|
||||||
|
- "test stable"
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
rules:
|
rules:
|
||||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
script:
|
script:
|
||||||
- cargo outdated --root-deps-only --exit-code 1 -v
|
- cargo update --color=always
|
||||||
|
# env_logger is ignored because it requires Rust >= 1.71
|
||||||
|
- cargo outdated --color=always --root-deps-only --ignore env_logger --exit-code 1 -v
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
extends:
|
extends:
|
||||||
- .img-stable
|
- '.debian:12-stable'
|
||||||
|
needs:
|
||||||
|
- "trigger"
|
||||||
|
- "test stable"
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
rules:
|
|
||||||
- when: 'always'
|
|
||||||
variables:
|
variables:
|
||||||
RUSTFLAGS: "-Cinstrument-coverage"
|
RUSTFLAGS: "-Cinstrument-coverage"
|
||||||
LLVM_PROFILE_FILE: "gst-plugins-rs-%p-%m.profraw"
|
LLVM_PROFILE_FILE: "gst-plugins-rs-%p-%m.profraw"
|
||||||
# csound-sys only looks at /usr/lib and /usr/local top levels
|
# csound-sys only looks at /usr/lib and /usr/local top levels
|
||||||
CSOUND_LIB_DIR: '/usr/lib/x86_64-linux-gnu/'
|
CSOUND_LIB_DIR: '/usr/lib/x86_64-linux-gnu/'
|
||||||
script:
|
script:
|
||||||
- cargo test --locked --color=always --all --all-features
|
- cargo test --locked --color=always --all --all-features --exclude gst-plugin-gtk4
|
||||||
# generate html report
|
# generate html report
|
||||||
- grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing --ignore "*target*" --ignore "*/build.rs" -o ./coverage/
|
- grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing --ignore "*target*" --ignore "*/build.rs" -o ./coverage/
|
||||||
# generate cobertura report for gitlab integration
|
# generate cobertura report for gitlab integration
|
||||||
|
@ -427,3 +393,34 @@ coverage:
|
||||||
coverage_report:
|
coverage_report:
|
||||||
coverage_format: cobertura
|
coverage_format: cobertura
|
||||||
path: coverage.xml
|
path: coverage.xml
|
||||||
|
|
||||||
|
cerbero trigger:
|
||||||
|
image: $CERBERO_TRIGGER_IMAGE
|
||||||
|
needs: [ "trigger" ]
|
||||||
|
variables:
|
||||||
|
# We will build this cerbero branch in the cerbero trigger CI
|
||||||
|
CERBERO_UPSTREAM_BRANCH: 'main'
|
||||||
|
script:
|
||||||
|
- ci/cerbero/trigger_cerbero_pipeline.py
|
||||||
|
rules:
|
||||||
|
# Never run post merge
|
||||||
|
- if: '$CI_PROJECT_NAMESPACE == "gstreamer"'
|
||||||
|
when: never
|
||||||
|
# Don't run if the only changes are files that cargo-c does not read
|
||||||
|
- if:
|
||||||
|
changes:
|
||||||
|
- "CHANGELOG.md"
|
||||||
|
- "README.md"
|
||||||
|
- "deny.toml"
|
||||||
|
- "rustfmt.toml"
|
||||||
|
- "typos.toml"
|
||||||
|
- "*.py"
|
||||||
|
- "*.sh"
|
||||||
|
- "Makefile"
|
||||||
|
- "meson.build"
|
||||||
|
- "meson_options.txt"
|
||||||
|
- "**/meson.build"
|
||||||
|
- "ci/*.sh"
|
||||||
|
- "ci/*.py"
|
||||||
|
when: never
|
||||||
|
- when: always
|
||||||
|
|
33
.gitlab/issue_templates/Bug.md
Normal file
33
.gitlab/issue_templates/Bug.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
### Describe your issue
|
||||||
|
<!-- a clear and concise summary of the bug. -->
|
||||||
|
<!-- For any GStreamer usage question, please contact the community using the #gstreamer channel on IRC https://www.oftc.net/ or the mailing list on https://gstreamer.freedesktop.org/lists/ -->
|
||||||
|
|
||||||
|
#### Expected Behavior
|
||||||
|
<!-- What did you expect to happen -->
|
||||||
|
|
||||||
|
#### Observed Behavior
|
||||||
|
<!-- What actually happened -->
|
||||||
|
|
||||||
|
#### Setup
|
||||||
|
- **Operating System:**
|
||||||
|
- **Device:** Computer / Tablet / Mobile / Virtual Machine <!-- Delete as appropriate !-->
|
||||||
|
- **gst-plugins-rs Version:**
|
||||||
|
- **GStreamer Version:**
|
||||||
|
- **Command line:**
|
||||||
|
|
||||||
|
### Steps to reproduce the bug
|
||||||
|
<!-- please fill in exact steps which reproduce the bug on your system, for example: -->
|
||||||
|
1. open terminal
|
||||||
|
2. type `command`
|
||||||
|
|
||||||
|
### How reproducible is the bug?
|
||||||
|
<!-- The reproducibility of the bug is Always/Intermittent/Only once after doing a very specific set of steps-->
|
||||||
|
|
||||||
|
### Screenshots if relevant
|
||||||
|
|
||||||
|
### Solutions you have tried
|
||||||
|
|
||||||
|
### Related non-duplicate issues
|
||||||
|
|
||||||
|
### Additional Information
|
||||||
|
<!-- Any other information such as logs. Make use of <details> for long output -->
|
457
CHANGELOG.md
Normal file
457
CHANGELOG.md
Normal file
|
@ -0,0 +1,457 @@
|
||||||
|
# Changelog
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html),
|
||||||
|
specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-version-field).
|
||||||
|
|
||||||
|
## [0.12.6] - 2024-05-23
|
||||||
|
### Fixed
|
||||||
|
- Various Rust 1.78 clippy warnings.
|
||||||
|
- gtk4paintablesink: Fix plugin description.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- fmp4mux / mp4mux: Add support for adding AV1 header OBUs into the MP4
|
||||||
|
headers.
|
||||||
|
- fmp4mux / mp4mux: Take track language from the tags if provided.
|
||||||
|
- gtk4paintablesink: Add GST_GTK4_WINDOW_FULLSCREEN environment variable to
|
||||||
|
create a fullscreen window for debugging purposes.
|
||||||
|
- gtk4paintablesink: Also create a window automatically when called from
|
||||||
|
gst-play-1.0.
|
||||||
|
- webrtc: Add support for insecure TLS connections.
|
||||||
|
- webrtcsink: Add VP9 parser after the encoder.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- webrtcsink: Improve error when no discovery pipeline runs.
|
||||||
|
- rtpgccbwe: Improve debug output in various places.
|
||||||
|
|
||||||
|
## [0.12.5] - 2024-04-29
|
||||||
|
### Fixed
|
||||||
|
- hrtfrender: Use a bitmask instead of an int in the caps for the channel-mask.
|
||||||
|
- rtpgccbwe: Don't log an error when pushing a buffer list fails while stopping.
|
||||||
|
- webrtcsink: Don't panic in bitrate handling with unsupported encoders.
|
||||||
|
- webrtcsink: Don't panic if unsupported input caps are used.
|
||||||
|
- webrtcsrc: Allow a `None` producer-id in `request-encoded-filter` signal.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- aws: New property to support path-style addressing.
|
||||||
|
- fmp4mux / mp4mux: Support FLAC instead (f)MP4.
|
||||||
|
- gtk4: Support directly importing dmabufs with GTK 4.14.
|
||||||
|
- gtk4: Add force-aspect-ratio property similar to other video sinks.
|
||||||
|
|
||||||
|
## [0.12.4] - 2024-04-08
|
||||||
|
### Fixed
|
||||||
|
- aws: Use fixed behaviour version to ensure that updates to the AWS SDK don't
|
||||||
|
change any defaults configurations in unexpected ways.
|
||||||
|
- onvifmetadataparse: Fix possible deadlock on shutdown.
|
||||||
|
- webrtcsink: Set `perfect-timestamp=true` on audio encoders to work around
|
||||||
|
bugs in Chrome's audio decoders.
|
||||||
|
- Various clippy warnings.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- reqwest: Update to reqwest 0.12.
|
||||||
|
- webrtchttp: Update to reqwest 0.12.
|
||||||
|
|
||||||
|
## [0.12.3] - 2024-03-21
|
||||||
|
### Fixed
|
||||||
|
- gtk4paintablesink: Fix scaling of texture position.
|
||||||
|
- janusvrwebrtcsink: Handle 64 bit numerical room ids.
|
||||||
|
- janusvrwebrtcsink: Don't include deprecated audio/video fields in publish
|
||||||
|
messages.
|
||||||
|
- janusvrwebrtcsink: Handle various other messages to avoid printing errors.
|
||||||
|
- livekitwebrtc: Fix shutdown behaviour.
|
||||||
|
- rtpgccbwe: Don't forward buffer lists with buffers from different SSRCs to
|
||||||
|
avoid breaking assumptions in rtpsession.
|
||||||
|
- sccparse: Ignore invalid timecodes during seeking.
|
||||||
|
- webrtcsink: Don't try parsing audio caps as video caps.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- webrtc: Allow resolution and framerate changes.
|
||||||
|
- webrtcsrc: Make produce-peer-id optional.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- livekitwebrtcsrc: Add new LiveKit source element.
|
||||||
|
- regex: Add support for configuring regex behaviour.
|
||||||
|
- spotifyaudiosrc: Document how to use with non-Facebook accounts.
|
||||||
|
- webrtcsrc: Add `do-retransmission` property.
|
||||||
|
|
||||||
|
## [0.12.2] - 2024-02-26
|
||||||
|
### Fixed
|
||||||
|
- rtpgccbwe: Don't reset PTS/DTS to `None` as otherwise `rtpsession` won't be
|
||||||
|
able to generate valid RTCP.
|
||||||
|
- webrtcsink: Fix usage with 1.22.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- janusvrwebrtcsink: Add `secret-key` property.
|
||||||
|
- janusvrwebrtcsink: Allow for string room ids and add `string-ids` property.
|
||||||
|
- textwrap: Don't split on all whitespaces, especially not on non-breaking
|
||||||
|
whitespace.
|
||||||
|
|
||||||
|
## [0.12.1] - 2024-02-13
|
||||||
|
### Added
|
||||||
|
- gtk4: Create a window for testing purposes when running in `gst-launch-1.0`
|
||||||
|
or if `GST_GTK4_WINDOW=1` is set.
|
||||||
|
- webrtcsink: Add `msid` property.
|
||||||
|
|
||||||
|
## [0.12.0] - 2024-02-08
|
||||||
|
### Changed
|
||||||
|
- ndi: `ndisrc` passes received data downstream without an additional copy, if
|
||||||
|
possible.
|
||||||
|
- webrtc: Cleanups to webrtcsrc/sink default signalling protocol, JavaScript
|
||||||
|
implementation and server implementation.
|
||||||
|
- webrtc: `whipwebrtcsink` is renamed to `whipclientsink` and deprecate old
|
||||||
|
`whipsink`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- gtk4: Fix Windows build when using EGL.
|
||||||
|
- gtk4: Fix ARGB pre-multiplication with GTK 4.14. This requires building with
|
||||||
|
the `gtk_v4_10` or even better `gtk_v4_14` feature.
|
||||||
|
- gtk4: Fix segfault if GTK3 is used in the same process.
|
||||||
|
- gtk4: Always draw background behind the video frame and not only when
|
||||||
|
borders have to be added to avoid glitches.
|
||||||
|
- livekitwebrtcsink: Add high-quality layer for video streams.
|
||||||
|
- webrtc: Fix potential hang and fd leak in signalling server.
|
||||||
|
- webrtc: Fix closing of WebSockets.
|
||||||
|
- webrtchttp: Allow setting `None` for audio/video caps for WHEP.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- New `awss3putobjectsink` that works similar to `awss3sink` but with a
|
||||||
|
different upload strategy.
|
||||||
|
- New `hlscmafsink` element for writing HLS streams with CMAF/ISOBMFF
|
||||||
|
fragments.
|
||||||
|
- New `inter` plugin with `intersink` / `intersrc` elements that allow to
|
||||||
|
connect different pipelines in the same process.
|
||||||
|
- New `janusvrwebrtcsink` element for the Janus VideoRoom API.
|
||||||
|
- New `rtspsrc2` element.
|
||||||
|
- New `whipserversrc` element.
|
||||||
|
- gtk4: New `background-color` property for setting the color of the
|
||||||
|
background of the frame and the borders, if any.
|
||||||
|
- gtk4: New `scale-filter` property for defining how to scale the frames.
|
||||||
|
- livesync: Add support for image formats.
|
||||||
|
- ndi: Closed Caption support in `ndisrc` / `ndisink`.
|
||||||
|
- textwrap: Add support for gaps.
|
||||||
|
- tracers: Optionally only show late buffers in `buffer-lateness` tracer.
|
||||||
|
- webrtc: Add support for custom headers.
|
||||||
|
- webrtcsink: New `payloader-setup` signal to configure payloader elements.
|
||||||
|
- webrtcsrc: Support for navigation events.
|
||||||
|
|
||||||
|
## [0.11.3] - 2023-12-18
|
||||||
|
### Fixed
|
||||||
|
- ndi: Mark a private type as such and remove a wrong `Clone` impl of internal types.
|
||||||
|
- uriplaylistbin: Fix a minor clippy warning.
|
||||||
|
- fallbacksrc: Fix error during badly timed timeout scheduling.
|
||||||
|
- webrtcsink: Fail gracefully if webrtcbin pads can't be requested instead of
|
||||||
|
panicking.
|
||||||
|
- threadshare: Fix deadlock in `ts-udpsrc` `notify::used-socket` signal
|
||||||
|
emission.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Update to AWS SDK 1.0.
|
||||||
|
- Update to windows-sys 0.52.
|
||||||
|
- Update to async-tungstenite 0.24.
|
||||||
|
- Update to bitstream-io 2.0.
|
||||||
|
- tttocea608: De-duplicate some functions.
|
||||||
|
- gtk4: Use async-channel instead of deprecated GLib main context channel.
|
||||||
|
|
||||||
|
## [0.11.2] - 2023-11-11
|
||||||
|
### Fixed
|
||||||
|
- filesink / s3sink: Set `sync=false` to allow processing faster than
|
||||||
|
real-time.
|
||||||
|
- hlssink3: Various minor bugfixes and cleanups.
|
||||||
|
- livesync: Various minor bugfixes and cleanups that should make the element
|
||||||
|
work more reliable.
|
||||||
|
- s3sink: Fix handling of non-ASCII characters in URIs and keys.
|
||||||
|
- sccparse: Parse SCC files that are incorrectly created by CCExtractor.
|
||||||
|
- ndisrc: Assume > 8 channels are unpositioned.
|
||||||
|
- rtpav1depay: Skip unexpected leading fragments instead of repeatedly warning
|
||||||
|
about the stream possibly being corrupted.
|
||||||
|
- rtpav1depay: Don't push stale temporal delimiters downstream but wait until
|
||||||
|
a complete OBU is collected.
|
||||||
|
- whipwebrtcsink: Use correct URL during redirects.
|
||||||
|
- webrtcsink: Make sure to not miss any ICE candidates.
|
||||||
|
- webrtcsink: Fix deadlock when calling `set-local-description`.
|
||||||
|
- webrtcsrc: Fix reference cycles that prevented the element from being freed.
|
||||||
|
- webrtcsrc: Define signaller property as `CONSTRUCT_ONLY` to make it actually
|
||||||
|
possible to set different signallers.
|
||||||
|
- webrtc: Update livekit signaller to livekit 0.2.
|
||||||
|
- meson: Various fixes to the meson-based build system.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- audiornnoise: Attach audio level meta to output buffers.
|
||||||
|
- hlssink3: Allow adding `EXT-X-PROGRAM-DATE-TIME` tag to the manifest.
|
||||||
|
- webrtcsrc: Add `turn-servers` property.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- aws/webrtc: Update to AWS SDK 0.57/0.35.
|
||||||
|
|
||||||
|
## [0.11.1] - 2023-10-04
|
||||||
|
### Fixed
|
||||||
|
- fallbackswitch: Fix various deadlocks.
|
||||||
|
- webrtcsink: Gracefully fail if adding the TWCC RTP header extension fails.
|
||||||
|
- webrtcsink: Fix codec selection discovery.
|
||||||
|
- webrtcsink: Add support for D3D11 memory and qsvh264enc.
|
||||||
|
- onvifmetadataparse: Skip metadata frames with unrepresentable UTC times.
|
||||||
|
- gtk4paintablesink: Pre-multiply alpha when creating GL textures with alpha.
|
||||||
|
- gtk4paintablesink: Only support RGBA/RGB in the GL code path.
|
||||||
|
- webrtchttp: Respect HTTP redirects.
|
||||||
|
- fmp4mux: Specify unit of fragment-duration property.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- threadshare: Port to polling 3.1.
|
||||||
|
|
||||||
|
## [0.11.0] - 2023-08-10
|
||||||
|
### Changed
|
||||||
|
- Updated MSRV to 1.70.
|
||||||
|
- Compatible with gtk-rs 0.18 and gstreamer-rs 0.21.
|
||||||
|
- awstranscriber: Move to HTTP2-based API via the aws-sdk-transcribestreaming
|
||||||
|
crate instead of our own implementation around the WebSocket API.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- webrtcsink: Add AWS KVS signaller and corresponding aws-kvs-webrtcsink
|
||||||
|
element.
|
||||||
|
- awstranscriber / transcriberbin: Add support for translations and outputting
|
||||||
|
transcriptions from a single audio stream in multiple languages at once.
|
||||||
|
- gstwebrtc-api: JavaScript API for interacting with the default signalling
|
||||||
|
protocol used by webrtcsink / webrtcsrc.
|
||||||
|
- cea608to708: New element for converting CEA608 to CEA708 closed captions.
|
||||||
|
- webrtcsink: Expose the signaller as property and allow implementing a
|
||||||
|
custom signaller by connecting signal handlers to the default signaller.
|
||||||
|
- webrtcsink: Add support for pre-encoded streams.
|
||||||
|
- togglerecord: Add support for non-live input streams.
|
||||||
|
- webrtcsink: New whipwebrtcsink that implements WHIP around webrtcsink.
|
||||||
|
The existing whipsink still exists but will sooner or later be deprecated.
|
||||||
|
- webrtcsink: Add LiveKit signaller and corresponding livekitwebrtcsink
|
||||||
|
element.
|
||||||
|
|
||||||
|
## [0.10.11] - 2023-07-20
|
||||||
|
### Fixed
|
||||||
|
- fallbackswitch: Fix pad health calculation and notifies.
|
||||||
|
- fallbackswitch: Change the threshold for trailing buffers.
|
||||||
|
- webrtcsink: Fix pipeline when input caps contain a max-framerate field.
|
||||||
|
- webrtcsink: Set VP8/VP9 payloader properties based on payloader element
|
||||||
|
factory name.
|
||||||
|
- webrtcsink: Set config-interval=-1 and aggregate-mode=zero-latency for
|
||||||
|
H264/5 payloaders.
|
||||||
|
- webrtcsink: Translate force-keyunit events to custom force-IDR API of NVIDIA
|
||||||
|
encoders.
|
||||||
|
- webrtcsink: Configure only 4 threads instead of 12 for x264enc for Chrome
|
||||||
|
compatibility.
|
||||||
|
- fmp4mux: Fix draining in chunk mode if keyframes are after the desired
|
||||||
|
fragment end.
|
||||||
|
|
||||||
|
## [0.10.10] - 2023-07-05
|
||||||
|
### Fixed
|
||||||
|
- livesync: Improve EOS handling to be in sync with `queue`'s behaviour.
|
||||||
|
- livesync: Wait for the end timestamp of the previous buffer before looking
|
||||||
|
at queue to actually make use of the available latency.
|
||||||
|
- webrtcsink: Avoid panic on unprepare from an async tokio context.
|
||||||
|
- webrtc/signalling: Fix race condition in message ordering.
|
||||||
|
- webrtcsink: Use the correct property types when configuring `nvvideoconvert`.
|
||||||
|
- videofx: Minimize dependencies of the image crate.
|
||||||
|
- togglerecord: Fix segment clipping to actually work as intended.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- gtk4paintablesink: Support for WGL/EGL on Windows.
|
||||||
|
- gtk4paintablesink: Add Python example application to the repository.
|
||||||
|
|
||||||
|
## [0.10.9] - 2023-06-19
|
||||||
|
### Fixed
|
||||||
|
- mp4mux/fmp4mux: Fix byte order in Opus extension box.
|
||||||
|
- webrtcsrc: Add twcc extension to the codec-preferences when present.
|
||||||
|
- webrtcsink: Don't try using cudaconvert if it is not present.
|
||||||
|
- mccparse: Don't offset the first timecode to a zero PTS.
|
||||||
|
- Correctly use MPL as license specifier instead of MPL-2 for plugins that
|
||||||
|
compile with GStreamer < 1.20.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- fallbackswitch: Add `stop-on-eos` property.
|
||||||
|
|
||||||
|
## [0.10.8] - 2023-06-07
|
||||||
|
### Fixed
|
||||||
|
- fmp4mux: Use updated start PTS when checking if a stream is filled instead
|
||||||
|
of a stale one.
|
||||||
|
- fmp4mux: Fix various issues with stream gaps, especially in the beginning.
|
||||||
|
- fmp4mux: Fix waiting in live pipelines.
|
||||||
|
- uriplaylistbin: Prevent deadlocks during property notifications.
|
||||||
|
- webrtcsink: Fix panics during `twcc-stats` callback and related issues.
|
||||||
|
- awstranscriber: Handle stream disconts correctly.
|
||||||
|
- roundedcorners: Fix caps negotiation to not use I420 if a border radius is
|
||||||
|
configured.
|
||||||
|
- whipsink: Use the correct pad template to request pads from the internal
|
||||||
|
webrtcbin.
|
||||||
|
- fallbacksrc: Don't apply fallback audio caps to the main stream.
|
||||||
|
- webrtcsrc: Fix caps handling during transceiver creation.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- rtpgccbwe: Improve packet handling.
|
||||||
|
|
||||||
|
## [0.10.7] - 2023-05-09
|
||||||
|
### Fixed
|
||||||
|
- ffv1dec: Drop rank until the implementation is feature-complete.
|
||||||
|
- spotifyaudiosrc: Check cached credentials before use and fix usage of
|
||||||
|
credentials cache.
|
||||||
|
- tttocea608: Specify raw CEA608 field.
|
||||||
|
- gtk4paintablesink: Fix compilation on non-Linux UNIX systems.
|
||||||
|
- webrtcsrc: Don't set stun-server to the empty string if none was set.
|
||||||
|
- webrtcsink: Abort statistics collection before stopping the signaller.
|
||||||
|
- rtpgccbwe: Don't process empty lists.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- ndi: Update to libloading 0.8.
|
||||||
|
- aws: Update to AWS SDK 0.55/0.27.
|
||||||
|
- webrtcsink: Order pads by serial number.
|
||||||
|
- Update to async-tungstenite 0.22.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- webrtcsink/webrtcsrc: Add `request-encoded-filter` signal to add support for
|
||||||
|
inserting custom filters between encoder/payloader or depayloader/decoder.
|
||||||
|
This allows interacting with the "insertable streams" API from Chrome.
|
||||||
|
|
||||||
|
## [0.10.6] - 2023-04-06
|
||||||
|
### Fixed
|
||||||
|
- webrtcsink: Fix max/min-bitrate property blurb/nick.
|
||||||
|
- uriplaylistbin: Add missing queues to example.
|
||||||
|
- tttocea608: Fix pushing of caps events that sometimes contained unfixed caps.
|
||||||
|
- tttocea608: Fix disappearing text after special character in non-popon mode.
|
||||||
|
- transcriberbin: Fix deadlock on construction.
|
||||||
|
- transcriberbin: Fix initial bin setup.
|
||||||
|
- fallbacksrc: Handle incompatible downstream caps without panicking.
|
||||||
|
- ndisrc: Fix copying of raw video frames with different NDI/GStreamer strides.
|
||||||
|
- livesync: Correctly assume zero upstream latency if latency query fails.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- webrtcsink: Add `ice-transport-policy` property that proxies the same
|
||||||
|
`webrtcbin` property.
|
||||||
|
|
||||||
|
## [0.10.5] - 2023-03-19
|
||||||
|
### Fixed
|
||||||
|
- gtk4: Fix build with OpenGL support on macOS.
|
||||||
|
- threadshare: Fix symbol conflicts when statically linking the plugin.
|
||||||
|
|
||||||
|
## [0.10.4] - 2023-03-14
|
||||||
|
### Fixed
|
||||||
|
- fmp4mux: Return a running time from `AggregatorImpl::next_time()` to fix
|
||||||
|
waiting in live pipelines.
|
||||||
|
- fmp4mux: Fix `hls_live` example to set properties on the right element.
|
||||||
|
- uriplaylistbin: Reset element when switching back to `NULL` state.
|
||||||
|
- livesync: Handle variable framerates correctly in fallback buffer duration
|
||||||
|
calculation.
|
||||||
|
- meson: Fix GStreamer version feature detection.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- webrtc: New `webrtc` element.
|
||||||
|
|
||||||
|
## [0.10.3] - 2023-03-02
|
||||||
|
### Added
|
||||||
|
- tracers: `queue_levels` tracer now also supports printing the `appsrc` levels.
|
||||||
|
- webrtc: `webrtcsink` can use `nvvidconv` if `nvvideoconvert` does not exist
|
||||||
|
on an NVIDIA platform.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- gtk4: Set the sync point on the video frame after mapping it as otherwise
|
||||||
|
the frame might not be ready yet for further usage.
|
||||||
|
- livesync: Correctly calculate the fallback buffer duration from the video
|
||||||
|
framerate.
|
||||||
|
- ndi: Handle caps changes correctly in `ndisinkcombiner`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- webrtc: Minor cleanup.
|
||||||
|
|
||||||
|
## [0.10.2] - 2023-02-23
|
||||||
|
### Fixed
|
||||||
|
- hlssink3: Allow signal handlers to return `None`
|
||||||
|
- gtk4: Make GL context sharing more reliable in pipelines with multiple
|
||||||
|
`gtk4paintablesinks`
|
||||||
|
- gtk4: Attach channel receiver to the main context from the correct thread to
|
||||||
|
make it possible to start the sink from a different thread than the main
|
||||||
|
thread without having retrieved the paintable from the main thread before.
|
||||||
|
- fmp4mux/mp4mux: Ignore caps changes if only the framerate changes.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- gtk4: Simplify and refactor GL context sharing. Apart from being more
|
||||||
|
reliable this reduces GL resource usage.
|
||||||
|
|
||||||
|
## [0.10.1] - 2023-02-13
|
||||||
|
### Fixed
|
||||||
|
- rtpav1pay: Fix calculation of Leb128 size size to work correctly with
|
||||||
|
streams from certain encoders.
|
||||||
|
|
||||||
|
## [0.10.0] - 2023-02-10
|
||||||
|
### Fixed
|
||||||
|
- audiornnoise: Use correct value range for the samples
|
||||||
|
- awss3sink: Treat stopping without EOS as an error for multipart upload
|
||||||
|
- awss3hlssink: Fix the name of the hlssink child element
|
||||||
|
- awss3hlssink: Fix deadlock on EOS
|
||||||
|
- dav1d: Various fixes to improve performance, to handle decoding errors more
|
||||||
|
gracefully and to make sure all frames are output in the end
|
||||||
|
- fmp4mux: Various fixes to fragment splitting behaviour, output formatting
|
||||||
|
and header generation
|
||||||
|
- gtk4: Various stability and rendering fixes
|
||||||
|
- meson: Various fixes and improvements to the meson-based build system
|
||||||
|
- ndi: provide non-Linux/macOS UNIX fallback for the soname
|
||||||
|
- ndisrc: Use default channel mask for audio output to allow >2 channels to
|
||||||
|
work better
|
||||||
|
- rav1e: Correctly enable threading support
|
||||||
|
- rtpav1: Various fixes to the payloader and depayloader to handle streams
|
||||||
|
more correctly and to handle errors more cleanly
|
||||||
|
- rtpav1depay: Set caps on the source pad
|
||||||
|
- spotify: fix "start a runtime from within a runtime" with static link
|
||||||
|
- textahead: fix previous buffers
|
||||||
|
- textwrap: Don't panic on empty buffers
|
||||||
|
- tttocea608: Don't fail if a GAP event contains no duration
|
||||||
|
- webrtchttp: whipsink: construct TURN URL correctly
|
||||||
|
- webrtcsink: fix panic on pre-bwe request error
|
||||||
|
- whipsink: Send ICE candidates together with the offer
|
||||||
|
- whipsink: Various cleanups and minor fixes
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- audiornnoise: Add voice detection threshold property
|
||||||
|
- awss3hlssink: Add `stats` property
|
||||||
|
- awss3sink: Add properties to set Content-Type and Content-Disposition
|
||||||
|
- fmp4mux: add 'offset-to-zero' property
|
||||||
|
- fmp4mux/mp4mux: add support for muxing Opus, VP8, VP9 and AV1 streams
|
||||||
|
- fmp4mux/mp4mux: Make media/track timescales configurable
|
||||||
|
- fmp4mux: Add support for CMAF-style chunking, e.g. low-latency / LL HLS and DASH
|
||||||
|
- gtk4: Support for rendering GL textures on X11/EGL, X11/GLX, Wayland and macOS
|
||||||
|
- hlssink3: Allow generating i-frame-only playlist
|
||||||
|
- livesync: New element that allows maintaining a contiguous live stream
|
||||||
|
without gaps from a potentially unstable source.
|
||||||
|
- mp4mux: New non-fragmented MP4 muxer element
|
||||||
|
- spotifyaudiosrc: Support configurable bitrate
|
||||||
|
- textahead: add settings to display previous buffers
|
||||||
|
- threadshare: Introduce new ts-audiotestsrc
|
||||||
|
- webrtcsink: Support nvv4l2vp9enc
|
||||||
|
- whepsource: Add a WebRTC WHEP source element
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- audiofx: Derive from AudioFilter where possible
|
||||||
|
- dav1ddec: Lower rank to primary to allow usage of hardware decoders with
|
||||||
|
higher ranks
|
||||||
|
- fmp4mux: Only push `fragment_offset` if `write-mfra` is true to reduce memory usage
|
||||||
|
- webrtcsink: Make the `turn-server` property a `turn-servers` list
|
||||||
|
- webrtcsink: Move from async-std to tokio
|
||||||
|
|
||||||
|
[Unreleased]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.6...HEAD
|
||||||
|
[0.12.6]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.5...0.12.6
|
||||||
|
[0.12.5]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.4...0.12.5
|
||||||
|
[0.12.4]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.3...0.12.4
|
||||||
|
[0.12.3]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.2...0.12.3
|
||||||
|
[0.12.2]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.1...0.12.2
|
||||||
|
[0.12.1]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.0...0.12.1
|
||||||
|
[0.12.0]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.3...0.12.0
|
||||||
|
[0.11.3]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.2...0.11.3
|
||||||
|
[0.11.2]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.1...0.11.2
|
||||||
|
[0.11.1]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.0...0.11.1
|
||||||
|
[0.11.0]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.11...0.11.0
|
||||||
|
[0.10.11]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.10...0.10.11
|
||||||
|
[0.10.10]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.9...0.10.10
|
||||||
|
[0.10.9]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.8...0.10.9
|
||||||
|
[0.10.8]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.7...0.10.8
|
||||||
|
[0.10.7]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.6...0.10.7
|
||||||
|
[0.10.6]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.5...0.10.6
|
||||||
|
[0.10.5]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.4...0.10.5
|
||||||
|
[0.10.4]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.3...0.10.4
|
||||||
|
[0.10.3]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.2...0.10.3
|
||||||
|
[0.10.2]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.1...0.10.2
|
||||||
|
[0.10.1]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.0...0.10.1
|
||||||
|
[0.10.0]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.9.0...0.10.0
|
7674
Cargo.lock
generated
Normal file
7674
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
51
Cargo.toml
51
Cargo.toml
|
@ -1,4 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"tutorial",
|
"tutorial",
|
||||||
|
@ -11,11 +12,15 @@ members = [
|
||||||
"audio/spotify",
|
"audio/spotify",
|
||||||
|
|
||||||
"generic/file",
|
"generic/file",
|
||||||
|
"generic/originalbuffer",
|
||||||
"generic/sodium",
|
"generic/sodium",
|
||||||
"generic/threadshare",
|
"generic/threadshare",
|
||||||
|
"generic/inter",
|
||||||
|
"generic/gopbuffer",
|
||||||
|
|
||||||
"mux/flavors",
|
"mux/flavors",
|
||||||
"mux/fmp4",
|
"mux/fmp4",
|
||||||
|
"mux/mp4",
|
||||||
|
|
||||||
"net/aws",
|
"net/aws",
|
||||||
"net/hlssink3",
|
"net/hlssink3",
|
||||||
|
@ -24,10 +29,12 @@ members = [
|
||||||
"net/raptorq",
|
"net/raptorq",
|
||||||
"net/reqwest",
|
"net/reqwest",
|
||||||
"net/rtp",
|
"net/rtp",
|
||||||
|
"net/rtsp",
|
||||||
"net/webrtchttp",
|
"net/webrtchttp",
|
||||||
"net/webrtc",
|
"net/webrtc",
|
||||||
"net/webrtc/protocol",
|
"net/webrtc/protocol",
|
||||||
"net/webrtc/signalling",
|
"net/webrtc/signalling",
|
||||||
|
"net/quinn",
|
||||||
|
|
||||||
"text/ahead",
|
"text/ahead",
|
||||||
"text/json",
|
"text/json",
|
||||||
|
@ -35,6 +42,7 @@ members = [
|
||||||
"text/wrap",
|
"text/wrap",
|
||||||
|
|
||||||
"utils/fallbackswitch",
|
"utils/fallbackswitch",
|
||||||
|
"utils/livesync",
|
||||||
"utils/togglerecord",
|
"utils/togglerecord",
|
||||||
"utils/tracers",
|
"utils/tracers",
|
||||||
"utils/uriplaylistbin",
|
"utils/uriplaylistbin",
|
||||||
|
@ -60,9 +68,13 @@ default-members = [
|
||||||
"audio/claxon",
|
"audio/claxon",
|
||||||
"audio/lewton",
|
"audio/lewton",
|
||||||
|
|
||||||
|
"generic/originalbuffer",
|
||||||
"generic/threadshare",
|
"generic/threadshare",
|
||||||
|
"generic/inter",
|
||||||
|
"generic/gopbuffer",
|
||||||
|
|
||||||
"mux/fmp4",
|
"mux/fmp4",
|
||||||
|
"mux/mp4",
|
||||||
|
|
||||||
"net/aws",
|
"net/aws",
|
||||||
"net/hlssink3",
|
"net/hlssink3",
|
||||||
|
@ -70,11 +82,13 @@ default-members = [
|
||||||
"net/raptorq",
|
"net/raptorq",
|
||||||
"net/reqwest",
|
"net/reqwest",
|
||||||
"net/rtp",
|
"net/rtp",
|
||||||
|
"net/rtsp",
|
||||||
"net/webrtchttp",
|
"net/webrtchttp",
|
||||||
"net/webrtc",
|
"net/webrtc",
|
||||||
"net/webrtc/protocol",
|
"net/webrtc/protocol",
|
||||||
"net/webrtc/signalling",
|
"net/webrtc/signalling",
|
||||||
"net/ndi",
|
"net/ndi",
|
||||||
|
"net/quinn",
|
||||||
|
|
||||||
"text/ahead",
|
"text/ahead",
|
||||||
"text/json",
|
"text/json",
|
||||||
|
@ -82,6 +96,7 @@ default-members = [
|
||||||
"text/wrap",
|
"text/wrap",
|
||||||
|
|
||||||
"utils/fallbackswitch",
|
"utils/fallbackswitch",
|
||||||
|
"utils/livesync",
|
||||||
"utils/togglerecord",
|
"utils/togglerecord",
|
||||||
"utils/tracers",
|
"utils/tracers",
|
||||||
"utils/uriplaylistbin",
|
"utils/uriplaylistbin",
|
||||||
|
@ -102,3 +117,39 @@ panic = 'unwind'
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "0.13.0-alpha.1"
|
||||||
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.70"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
once_cell = "1"
|
||||||
|
glib = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master", features=["use_glib"] }
|
||||||
|
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master"}
|
||||||
|
gdk-wayland = { package = "gdk4-wayland", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master"}
|
||||||
|
gdk-x11 = { package = "gdk4-x11", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master"}
|
||||||
|
gdk-win32 = { package = "gdk4-win32", git = "https://github.com/gtk-rs/gtk4-rs", branch = "master"}
|
||||||
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-allocators = { package = "gstreamer-allocators", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-gl = { package = "gstreamer-gl", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-gl-egl = { package = "gstreamer-gl-egl", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-gl-wayland = { package = "gstreamer-gl-wayland", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-gl-x11 = { package = "gstreamer-gl-x11", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-net = { package = "gstreamer-net", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-plugin-version-helper = { path="./version-helper" }
|
||||||
|
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-sdp = { package = "gstreamer-sdp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-utils = { package = "gstreamer-utils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
gst-webrtc = { package = "gstreamer-webrtc", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "main" }
|
||||||
|
|
|
@ -33,6 +33,9 @@ You will find the following plugins in this repository:
|
||||||
|
|
||||||
- `onvif`: Various elements for parsing, RTP (de)payloading, overlaying of ONVIF timed metadata.
|
- `onvif`: Various elements for parsing, RTP (de)payloading, overlaying of ONVIF timed metadata.
|
||||||
|
|
||||||
|
- `quinn`: Transfer data over the network using QUIC
|
||||||
|
- `quinnquicsink`/`quinnquicsrc`: Send and receive data using QUIC
|
||||||
|
|
||||||
- `raptorq`: Encoder/decoder element for RaptorQ RTP FEC mechanism.
|
- `raptorq`: Encoder/decoder element for RaptorQ RTP FEC mechanism.
|
||||||
|
|
||||||
- `reqwest`: An HTTP source element based on the [reqwest](https://github.com/seanmonstar/reqwest) library.
|
- `reqwest`: An HTTP source element based on the [reqwest](https://github.com/seanmonstar/reqwest) library.
|
||||||
|
@ -110,6 +113,8 @@ You will find the following plugins in this repository:
|
||||||
|
|
||||||
- `fmp4`: A fragmented MP4/ISOBMFF/CMAF muxer for generating e.g. DASH/HLS media fragments.
|
- `fmp4`: A fragmented MP4/ISOBMFF/CMAF muxer for generating e.g. DASH/HLS media fragments.
|
||||||
|
|
||||||
|
- `mp4`: A non-fragmented MP4 muxer for generating MP4 files.
|
||||||
|
|
||||||
* `text`
|
* `text`
|
||||||
- `ahead`: A plugin to display upcoming text buffers ahead.
|
- `ahead`: A plugin to display upcoming text buffers ahead.
|
||||||
|
|
||||||
|
@ -127,6 +132,9 @@ You will find the following plugins in this repository:
|
||||||
configuring a fallback audio/video if there are problems with the main
|
configuring a fallback audio/video if there are problems with the main
|
||||||
source.
|
source.
|
||||||
|
|
||||||
|
- `livesync`: Element to maintain a continuous live stream from a
|
||||||
|
potentially unstable source.
|
||||||
|
|
||||||
- `togglerecord`: Element to enable starting and stopping multiple streams together.
|
- `togglerecord`: Element to enable starting and stopping multiple streams together.
|
||||||
|
|
||||||
- `tracers`: Plugin with multiple tracers:
|
- `tracers`: Plugin with multiple tracers:
|
||||||
|
|
|
@ -1,27 +1,27 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-audiofx"
|
name = "gst-plugin-audiofx"
|
||||||
version = "0.9.0-alpha.1"
|
version.workspace = true
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository.workspace = true
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "GStreamer Rust Audio Effects Plugin"
|
description = "GStreamer Rust Audio Effects Plugin"
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.63"
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
|
gst = { workspace = true, features = ["v1_20"] }
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
|
gst-base = { workspace = true, features = ["v1_20"] }
|
||||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
|
gst-audio = { workspace = true, features = ["v1_20"] }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
once_cell = "1.0"
|
|
||||||
ebur128 = "0.1"
|
ebur128 = "0.1"
|
||||||
hrtf = "0.8"
|
hrtf = "0.8"
|
||||||
nnnoiseless = { version = "0.5", default-features = false }
|
nnnoiseless = { version = "0.5", default-features = false }
|
||||||
smallvec = "1"
|
smallvec = "1"
|
||||||
atomic_refcell = "0.1"
|
atomic_refcell = "0.1"
|
||||||
rayon = "1.5"
|
rayon = "1.5"
|
||||||
|
once_cell.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstrsaudiofx"
|
name = "gstrsaudiofx"
|
||||||
|
@ -29,11 +29,11 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
|
gst-check = { workspace = true, features = ["v1_18"] }
|
||||||
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-app.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
@ -41,7 +41,7 @@ capi = []
|
||||||
doc = ["gst/v1_18"]
|
doc = ["gst/v1_18"]
|
||||||
|
|
||||||
[package.metadata.capi]
|
[package.metadata.capi]
|
||||||
min_version = "0.8.0"
|
min_version = "0.9.21"
|
||||||
|
|
||||||
[package.metadata.capi.header]
|
[package.metadata.capi.header]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -49,6 +49,7 @@ enabled = false
|
||||||
[package.metadata.capi.library]
|
[package.metadata.capi.library]
|
||||||
install_subdir = "gstreamer-1.0"
|
install_subdir = "gstreamer-1.0"
|
||||||
versioning = false
|
versioning = false
|
||||||
|
import_library = false
|
||||||
|
|
||||||
[package.metadata.capi.pkg_config]
|
[package.metadata.capi.pkg_config]
|
||||||
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-audio-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-audio-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-MPL-2.0
|
|
373
audio/audiofx/LICENSE-MPL-2.0
Normal file
373
audio/audiofx/LICENSE-MPL-2.0
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
|
@ -36,10 +36,9 @@ fn run() -> Result<(), Error> {
|
||||||
let uri = &args[1];
|
let uri = &args[1];
|
||||||
let hrir = &args[2];
|
let hrir = &args[2];
|
||||||
|
|
||||||
let pipeline = gst::parse_launch(&format!(
|
let pipeline = gst::parse::launch(&format!(
|
||||||
"uridecodebin uri={} ! audioconvert ! audio/x-raw,channels=1 !
|
"uridecodebin uri={uri} ! audioconvert ! audio/x-raw,channels=1 !
|
||||||
hrtfrender hrir-file={} name=hrtf ! audioresample ! autoaudiosink",
|
hrtfrender hrir-file={hrir} name=hrtf ! audioresample ! autoaudiosink"
|
||||||
uri, hrir
|
|
||||||
))?
|
))?
|
||||||
.downcast::<gst::Pipeline>()
|
.downcast::<gst::Pipeline>()
|
||||||
.expect("type error");
|
.expect("type error");
|
||||||
|
@ -90,10 +89,10 @@ fn run() -> Result<(), Error> {
|
||||||
let new_z = -x * f32::sin(ROTATION) + z * f32::cos(ROTATION);
|
let new_z = -x * f32::sin(ROTATION) + z * f32::cos(ROTATION);
|
||||||
|
|
||||||
let objs = [gst::Structure::builder("application/spatial-object")
|
let objs = [gst::Structure::builder("application/spatial-object")
|
||||||
.field("x", &new_x)
|
.field("x", new_x)
|
||||||
.field("y", &y)
|
.field("y", y)
|
||||||
.field("z", &new_z)
|
.field("z", new_z)
|
||||||
.field("distance-gain", &gain)
|
.field("distance-gain", gain)
|
||||||
.build()];
|
.build()];
|
||||||
|
|
||||||
hrtf.set_property("spatial-objects", gst::Array::new(objs));
|
hrtf.set_property("spatial-objects", gst::Array::new(objs));
|
||||||
|
@ -110,7 +109,7 @@ fn run() -> Result<(), Error> {
|
||||||
|
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::StateChanged(state_changed) => {
|
MessageView::StateChanged(state_changed) => {
|
||||||
if state_changed.src().map(|s| s == pipeline).unwrap_or(false)
|
if state_changed.src().map(|s| s == &pipeline).unwrap_or(false)
|
||||||
&& state_changed.current() == gst::State::Playing
|
&& state_changed.current() == gst::State::Playing
|
||||||
{
|
{
|
||||||
let (lock, cvar) = &*state_cond;
|
let (lock, cvar) = &*state_cond;
|
||||||
|
@ -142,6 +141,6 @@ fn run() -> Result<(), Error> {
|
||||||
fn main() {
|
fn main() {
|
||||||
match run() {
|
match run() {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => eprintln!("Error! {}", e),
|
Err(e) => eprintln!("Error! {e}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst_base::subclass::prelude::*;
|
use gst_audio::subclass::prelude::*;
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::{cmp, u64};
|
use std::{cmp, u64};
|
||||||
|
@ -20,7 +20,7 @@ use num_traits::cast::{FromPrimitive, ToPrimitive};
|
||||||
use num_traits::float::Float;
|
use num_traits::float::Float;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
static _CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
gst::DebugCategory::new(
|
gst::DebugCategory::new(
|
||||||
"rsaudioecho",
|
"rsaudioecho",
|
||||||
gst::DebugColorFlags::empty(),
|
gst::DebugColorFlags::empty(),
|
||||||
|
@ -89,7 +89,7 @@ impl AudioEcho {
|
||||||
impl ObjectSubclass for AudioEcho {
|
impl ObjectSubclass for AudioEcho {
|
||||||
const NAME: &'static str = "GstRsAudioEcho";
|
const NAME: &'static str = "GstRsAudioEcho";
|
||||||
type Type = super::AudioEcho;
|
type Type = super::AudioEcho;
|
||||||
type ParentType = gst_base::BaseTransform;
|
type ParentType = gst_audio::AudioFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for AudioEcho {
|
impl ObjectImpl for AudioEcho {
|
||||||
|
@ -194,32 +194,6 @@ impl ElementImpl for AudioEcho {
|
||||||
|
|
||||||
Some(&*ELEMENT_METADATA)
|
Some(&*ELEMENT_METADATA)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
|
||||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
|
||||||
let caps = gst_audio::AudioCapsBuilder::new_interleaved()
|
|
||||||
.format_list([gst_audio::AUDIO_FORMAT_F32, gst_audio::AUDIO_FORMAT_F64])
|
|
||||||
.build();
|
|
||||||
let src_pad_template = gst::PadTemplate::new(
|
|
||||||
"src",
|
|
||||||
gst::PadDirection::Src,
|
|
||||||
gst::PadPresence::Always,
|
|
||||||
&caps,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let sink_pad_template = gst::PadTemplate::new(
|
|
||||||
"sink",
|
|
||||||
gst::PadDirection::Sink,
|
|
||||||
gst::PadPresence::Always,
|
|
||||||
&caps,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
vec![src_pad_template, sink_pad_template]
|
|
||||||
});
|
|
||||||
|
|
||||||
PAD_TEMPLATES.as_ref()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseTransformImpl for AudioEcho {
|
impl BaseTransformImpl for AudioEcho {
|
||||||
|
@ -252,28 +226,6 @@ impl BaseTransformImpl for AudioEcho {
|
||||||
Ok(gst::FlowSuccess::Ok)
|
Ok(gst::FlowSuccess::Ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_caps(&self, incaps: &gst::Caps, outcaps: &gst::Caps) -> Result<(), gst::LoggableError> {
|
|
||||||
if incaps != outcaps {
|
|
||||||
return Err(gst::loggable_error!(
|
|
||||||
CAT,
|
|
||||||
"Input and output caps are not the same"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let info = gst_audio::AudioInfo::from_caps(incaps)
|
|
||||||
.map_err(|_| gst::loggable_error!(CAT, "Failed to parse input caps"))?;
|
|
||||||
let max_delay = self.settings.lock().unwrap().max_delay;
|
|
||||||
let size = (max_delay * (info.rate() as u64)).seconds() as usize;
|
|
||||||
let buffer_size = size * (info.channels() as usize);
|
|
||||||
|
|
||||||
*self.state.lock().unwrap() = Some(State {
|
|
||||||
info,
|
|
||||||
buffer: RingBuffer::new(buffer_size),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
// Drop state
|
// Drop state
|
||||||
let _ = self.state.lock().unwrap().take();
|
let _ = self.state.lock().unwrap().take();
|
||||||
|
@ -281,3 +233,28 @@ impl BaseTransformImpl for AudioEcho {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AudioFilterImpl for AudioEcho {
|
||||||
|
fn allowed_caps() -> &'static gst::Caps {
|
||||||
|
static CAPS: Lazy<gst::Caps> = Lazy::new(|| {
|
||||||
|
gst_audio::AudioCapsBuilder::new_interleaved()
|
||||||
|
.format_list([gst_audio::AUDIO_FORMAT_F32, gst_audio::AUDIO_FORMAT_F64])
|
||||||
|
.build()
|
||||||
|
});
|
||||||
|
|
||||||
|
&CAPS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&self, info: &gst_audio::AudioInfo) -> Result<(), gst::LoggableError> {
|
||||||
|
let max_delay = self.settings.lock().unwrap().max_delay;
|
||||||
|
let size = (max_delay * (info.rate() as u64)).seconds() as usize;
|
||||||
|
let buffer_size = size * (info.channels() as usize);
|
||||||
|
|
||||||
|
*self.state.lock().unwrap() = Some(State {
|
||||||
|
info: info.clone(),
|
||||||
|
buffer: RingBuffer::new(buffer_size),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"rsaudioecho",
|
"rsaudioecho",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
AudioEcho::static_type(),
|
AudioEcho::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,8 @@ pub struct RingBuffer {
|
||||||
|
|
||||||
impl RingBuffer {
|
impl RingBuffer {
|
||||||
pub fn new(size: usize) -> Self {
|
pub fn new(size: usize) -> Self {
|
||||||
let mut buffer = Vec::with_capacity(size as usize);
|
let mut buffer = Vec::with_capacity(size);
|
||||||
buffer.extend(iter::repeat(0.0).take(size as usize));
|
buffer.extend(iter::repeat(0.0).take(size));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
buffer: buffer.into_boxed_slice(),
|
buffer: buffer.into_boxed_slice(),
|
||||||
|
|
|
@ -348,9 +348,9 @@ impl State {
|
||||||
true_peak
|
true_peak
|
||||||
);
|
);
|
||||||
|
|
||||||
// Difference between targetted and calculated LUFS loudness as a linear scalefactor.
|
// Difference between targeted and calculated LUFS loudness as a linear scalefactor.
|
||||||
let offset = f64::powf(10., (self.target_i - global) / 20.);
|
let offset = f64::powf(10., (self.target_i - global) / 20.);
|
||||||
// What the new peak would be after adjusting for the targetted loudness.
|
// What the new peak would be after adjusting for the targeted loudness.
|
||||||
let offset_tp = true_peak * offset;
|
let offset_tp = true_peak * offset;
|
||||||
|
|
||||||
// If the new peak would be more quiet than targeted one, take it. Otherwise only go as
|
// If the new peak would be more quiet than targeted one, take it. Otherwise only go as
|
||||||
|
@ -611,10 +611,9 @@ impl State {
|
||||||
// the position where we have to start writing the next 100ms in the next
|
// the position where we have to start writing the next 100ms in the next
|
||||||
// iteration.
|
// iteration.
|
||||||
|
|
||||||
let mut outbuf = gst::Buffer::with_size(
|
let mut outbuf =
|
||||||
self.current_samples_per_frame as usize * self.info.bpf() as usize,
|
gst::Buffer::with_size(self.current_samples_per_frame * self.info.bpf() as usize)
|
||||||
)
|
.map_err(|_| gst::FlowError::Error)?;
|
||||||
.map_err(|_| gst::FlowError::Error)?;
|
|
||||||
{
|
{
|
||||||
let outbuf = outbuf.get_mut().unwrap();
|
let outbuf = outbuf.get_mut().unwrap();
|
||||||
let mut dst = outbuf.map_writable().map_err(|_| gst::FlowError::Error)?;
|
let mut dst = outbuf.map_writable().map_err(|_| gst::FlowError::Error)?;
|
||||||
|
@ -706,7 +705,7 @@ impl State {
|
||||||
self.process_fill_final_frame(imp, num_samples, FRAME_SIZE);
|
self.process_fill_final_frame(imp, num_samples, FRAME_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now repeatadly run the limiter, output the output gain, update the gains, copy further
|
// Now repeatedly run the limiter, output the output gain, update the gains, copy further
|
||||||
// data from the buf to limiter_buf until we have output everything.
|
// data from the buf to limiter_buf until we have output everything.
|
||||||
//
|
//
|
||||||
// At this point we have to output 3s - (FRAME_SIZE - num_samples)
|
// At this point we have to output 3s - (FRAME_SIZE - num_samples)
|
||||||
|
@ -783,8 +782,8 @@ impl State {
|
||||||
self.offset
|
self.offset
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut outbuf = gst::Buffer::with_size(src.len() * mem::size_of::<f64>())
|
let mut outbuf =
|
||||||
.map_err(|_| gst::FlowError::Error)?;
|
gst::Buffer::with_size(mem::size_of_val(src)).map_err(|_| gst::FlowError::Error)?;
|
||||||
{
|
{
|
||||||
let outbuf = outbuf.get_mut().unwrap();
|
let outbuf = outbuf.get_mut().unwrap();
|
||||||
let mut dst = outbuf.map_writable().map_err(|_| gst::FlowError::Error)?;
|
let mut dst = outbuf.map_writable().map_err(|_| gst::FlowError::Error)?;
|
||||||
|
@ -819,7 +818,7 @@ impl State {
|
||||||
// adjustment. frame_type should only ever be set to Final at the end if we ended up in
|
// adjustment. frame_type should only ever be set to Final at the end if we ended up in
|
||||||
// Inner state before.
|
// Inner state before.
|
||||||
if self.frame_type == FrameType::First
|
if self.frame_type == FrameType::First
|
||||||
&& (src.len() / self.info.channels() as usize) < self.current_samples_per_frame as usize
|
&& (src.len() / self.info.channels() as usize) < self.current_samples_per_frame
|
||||||
{
|
{
|
||||||
self.process_first_frame_is_last(imp)?;
|
self.process_first_frame_is_last(imp)?;
|
||||||
}
|
}
|
||||||
|
@ -856,15 +855,15 @@ impl State {
|
||||||
smp_cnt += LIMITER_LOOKAHEAD + peak_delta - LIMITER_ATTACK_WINDOW;
|
smp_cnt += LIMITER_LOOKAHEAD + peak_delta - LIMITER_ATTACK_WINDOW;
|
||||||
|
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"Found peak {} at sample {}, going to attack state at sample {} (gain reduction {}-{})",
|
"Found peak {} at sample {}, going to attack state at sample {} (gain reduction {}-{})",
|
||||||
peak_value,
|
peak_value,
|
||||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||||
smp_cnt,
|
smp_cnt,
|
||||||
self.gain_reduction[0],
|
self.gain_reduction[0],
|
||||||
self.gain_reduction[1]
|
self.gain_reduction[1]
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Process all samples, no peak found
|
// Process all samples, no peak found
|
||||||
smp_cnt = nb_samples;
|
smp_cnt = nb_samples;
|
||||||
|
@ -993,15 +992,15 @@ impl State {
|
||||||
self.sustain_cnt = None;
|
self.sustain_cnt = None;
|
||||||
|
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"Found new peak {} at sample {}, restarting attack state at sample {} (gain reduction {}-{})",
|
"Found new peak {} at sample {}, restarting attack state at sample {} (gain reduction {}-{})",
|
||||||
peak_value,
|
peak_value,
|
||||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||||
smp_cnt,
|
smp_cnt,
|
||||||
self.gain_reduction[0],
|
self.gain_reduction[0],
|
||||||
self.gain_reduction[1],
|
self.gain_reduction[1],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// If the slope is lower we can't simply reduce the slope as we would
|
// If the slope is lower we can't simply reduce the slope as we would
|
||||||
// then have a lower gain reduction than needed at the previous peak.
|
// then have a lower gain reduction than needed at the previous peak.
|
||||||
|
@ -1042,15 +1041,15 @@ impl State {
|
||||||
self.sustain_cnt = Some(self.env_cnt);
|
self.sustain_cnt = Some(self.env_cnt);
|
||||||
|
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"Found new peak {} at sample {}, adjusting attack state at sample {} (gain reduction {}-{})",
|
"Found new peak {} at sample {}, adjusting attack state at sample {} (gain reduction {}-{})",
|
||||||
peak_value,
|
peak_value,
|
||||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||||
smp_cnt,
|
smp_cnt,
|
||||||
self.gain_reduction[0],
|
self.gain_reduction[0],
|
||||||
self.gain_reduction[1],
|
self.gain_reduction[1],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return smp_cnt;
|
return smp_cnt;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1152,25 +1151,25 @@ impl State {
|
||||||
self.gain_reduction[1] = gain_reduction;
|
self.gain_reduction[1] = gain_reduction;
|
||||||
|
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"Found new peak {} at sample {}, going back to attack state at sample {} (gain reduction {}-{})",
|
"Found new peak {} at sample {}, going back to attack state at sample {} (gain reduction {}-{})",
|
||||||
peak_value,
|
peak_value,
|
||||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||||
smp_cnt,
|
smp_cnt,
|
||||||
self.gain_reduction[0],
|
self.gain_reduction[0],
|
||||||
self.gain_reduction[1],
|
self.gain_reduction[1],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"Found new peak {} at sample {}, going sustain further at sample {} (gain reduction {})",
|
"Found new peak {} at sample {}, going sustain further at sample {} (gain reduction {})",
|
||||||
peak_value,
|
peak_value,
|
||||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||||
smp_cnt,
|
smp_cnt,
|
||||||
self.gain_reduction[1],
|
self.gain_reduction[1],
|
||||||
);
|
);
|
||||||
// We need to sustain until the peak at least
|
// We need to sustain until the peak at least
|
||||||
self.sustain_cnt = Some(LIMITER_LOOKAHEAD);
|
self.sustain_cnt = Some(LIMITER_LOOKAHEAD);
|
||||||
}
|
}
|
||||||
|
@ -1260,26 +1259,26 @@ impl State {
|
||||||
self.gain_reduction[1] = gain_reduction;
|
self.gain_reduction[1] = gain_reduction;
|
||||||
|
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"Found new peak {} at sample {}, going back to attack state at sample {} (gain reduction {}-{})",
|
"Found new peak {} at sample {}, going back to attack state at sample {} (gain reduction {}-{})",
|
||||||
peak_value,
|
peak_value,
|
||||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||||
smp_cnt,
|
smp_cnt,
|
||||||
self.gain_reduction[0],
|
self.gain_reduction[0],
|
||||||
self.gain_reduction[1],
|
self.gain_reduction[1],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
self.gain_reduction[1] = current_gain_reduction;
|
self.gain_reduction[1] = current_gain_reduction;
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: imp,
|
imp: imp,
|
||||||
"Going from release to sustain state at sample {} because of low peak {} at sample {} (gain reduction {})",
|
"Going from release to sustain state at sample {} because of low peak {} at sample {} (gain reduction {})",
|
||||||
smp_cnt,
|
smp_cnt,
|
||||||
peak_value,
|
peak_value,
|
||||||
smp_cnt + LIMITER_ATTACK_WINDOW,
|
smp_cnt + LIMITER_ATTACK_WINDOW,
|
||||||
self.gain_reduction[1]
|
self.gain_reduction[1]
|
||||||
);
|
);
|
||||||
self.limiter_state = LimiterState::Sustain;
|
self.limiter_state = LimiterState::Sustain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1560,7 +1559,7 @@ impl AudioLoudNorm {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to reset the state now
|
// Need to reset the state now
|
||||||
*state = State::new(&*self.settings.lock().unwrap(), state.info.clone());
|
*state = State::new(&self.settings.lock().unwrap(), state.info.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
state.adapter.push(buffer);
|
state.adapter.push(buffer);
|
||||||
|
@ -1602,7 +1601,7 @@ impl AudioLoudNorm {
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
*state = Some(State::new(&*self.settings.lock().unwrap(), info));
|
*state = Some(State::new(&self.settings.lock().unwrap(), info));
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
if let Some(outbuf) = outbuf {
|
if let Some(outbuf) = outbuf {
|
||||||
|
@ -1623,7 +1622,7 @@ impl AudioLoudNorm {
|
||||||
Err(gst::FlowError::Eos) => None,
|
Err(gst::FlowError::Eos) => None,
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
};
|
};
|
||||||
*state = State::new(&*self.settings.lock().unwrap(), state.info.clone());
|
*state = State::new(&self.settings.lock().unwrap(), state.info.clone());
|
||||||
}
|
}
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
|
@ -1691,7 +1690,7 @@ impl ObjectSubclass for AudioLoudNorm {
|
||||||
|
|
||||||
fn with_class(klass: &Self::Class) -> Self {
|
fn with_class(klass: &Self::Class) -> Self {
|
||||||
let templ = klass.pad_template("sink").unwrap();
|
let templ = klass.pad_template("sink").unwrap();
|
||||||
let sinkpad = gst::Pad::builder_with_template(&templ, Some("sink"))
|
let sinkpad = gst::Pad::builder_from_template(&templ)
|
||||||
.chain_function(|pad, parent, buffer| {
|
.chain_function(|pad, parent, buffer| {
|
||||||
Self::catch_panic_pad_function(
|
Self::catch_panic_pad_function(
|
||||||
parent,
|
parent,
|
||||||
|
@ -1706,7 +1705,7 @@ impl ObjectSubclass for AudioLoudNorm {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let templ = klass.pad_template("src").unwrap();
|
let templ = klass.pad_template("src").unwrap();
|
||||||
let srcpad = gst::Pad::builder_with_template(&templ, Some("src"))
|
let srcpad = gst::Pad::builder_from_template(&templ)
|
||||||
.query_function(|pad, parent, query| {
|
.query_function(|pad, parent, query| {
|
||||||
Self::catch_panic_pad_function(parent, || false, |this| this.src_query(pad, query))
|
Self::catch_panic_pad_function(parent, || false, |this| this.src_query(pad, query))
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"audioloudnorm",
|
"audioloudnorm",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
AudioLoudNorm::static_type(),
|
AudioLoudNorm::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,15 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
|
use gst_audio::subclass::prelude::*;
|
||||||
use gst_base::prelude::*;
|
use gst_base::prelude::*;
|
||||||
use gst_base::subclass::base_transform::BaseTransformImplExt;
|
use gst_base::subclass::base_transform::BaseTransformImplExt;
|
||||||
use gst_base::subclass::base_transform::GenerateOutputSuccess;
|
use gst_base::subclass::base_transform::GenerateOutputSuccess;
|
||||||
use gst_base::subclass::prelude::*;
|
|
||||||
|
|
||||||
use nnnoiseless::DenoiseState;
|
use nnnoiseless::DenoiseState;
|
||||||
|
|
||||||
|
@ -31,8 +33,22 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const DEFAULT_VOICE_ACTIVITY_THRESHOLD: f32 = 0.0;
|
||||||
const FRAME_SIZE: usize = DenoiseState::FRAME_SIZE;
|
const FRAME_SIZE: usize = DenoiseState::FRAME_SIZE;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Settings {
|
||||||
|
vad_threshold: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Settings {
|
||||||
|
vad_threshold: DEFAULT_VOICE_ACTIVITY_THRESHOLD,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ChannelDenoiser {
|
struct ChannelDenoiser {
|
||||||
denoiser: Box<DenoiseState<'static>>,
|
denoiser: Box<DenoiseState<'static>>,
|
||||||
frame_chunk: Box<[f32; FRAME_SIZE]>,
|
frame_chunk: Box<[f32; FRAME_SIZE]>,
|
||||||
|
@ -47,6 +63,7 @@ struct State {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AudioRNNoise {
|
pub struct AudioRNNoise {
|
||||||
|
settings: Mutex<Settings>,
|
||||||
state: AtomicRefCell<Option<State>>,
|
state: AtomicRefCell<Option<State>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,43 +99,6 @@ impl State {
|
||||||
fn needs_more_data(&self) -> bool {
|
fn needs_more_data(&self) -> bool {
|
||||||
self.adapter.available() < (FRAME_SIZE * self.in_info.bpf() as usize)
|
self.adapter.available() < (FRAME_SIZE * self.in_info.bpf() as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process(&mut self, input_plane: &[f32], output_plane: &mut [f32]) {
|
|
||||||
let channels = self.in_info.channels() as usize;
|
|
||||||
let size = FRAME_SIZE * channels;
|
|
||||||
|
|
||||||
for (out_frame, in_frame) in output_plane.chunks_mut(size).zip(input_plane.chunks(size)) {
|
|
||||||
for (index, item) in in_frame.iter().enumerate() {
|
|
||||||
let channel_index = index % channels;
|
|
||||||
let channel_denoiser = &mut self.denoisers[channel_index];
|
|
||||||
let pos = index / channels;
|
|
||||||
channel_denoiser.frame_chunk[pos] = *item;
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in (in_frame.len() / channels)..(size / channels) {
|
|
||||||
for c in 0..channels {
|
|
||||||
let channel_denoiser = &mut self.denoisers[c];
|
|
||||||
channel_denoiser.frame_chunk[i] = 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: The first chunks coming out of the denoisers contains some
|
|
||||||
// fade-in artifacts. We might want to discard those.
|
|
||||||
for channel_denoiser in &mut self.denoisers {
|
|
||||||
channel_denoiser.denoiser.process_frame(
|
|
||||||
&mut channel_denoiser.out_chunk[..],
|
|
||||||
&channel_denoiser.frame_chunk[..],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (index, item) in out_frame.iter_mut().enumerate() {
|
|
||||||
let channel_index = index % channels;
|
|
||||||
let channel_denoiser = &self.denoisers[channel_index];
|
|
||||||
let pos = index / channels;
|
|
||||||
*item = channel_denoiser.out_chunk[pos];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioRNNoise {
|
impl AudioRNNoise {
|
||||||
|
@ -131,6 +111,7 @@ impl AudioRNNoise {
|
||||||
return Ok(gst::FlowSuccess::Ok);
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let settings = *self.settings.lock().unwrap();
|
||||||
let mut buffer = gst::Buffer::with_size(available).map_err(|e| {
|
let mut buffer = gst::Buffer::with_size(available).map_err(|e| {
|
||||||
gst::error!(CAT, imp: self, "Failed to allocate buffer at EOS {:?}", e);
|
gst::error!(CAT, imp: self, "Failed to allocate buffer at EOS {:?}", e);
|
||||||
gst::FlowError::Flushing
|
gst::FlowError::Flushing
|
||||||
|
@ -148,10 +129,13 @@ impl AudioRNNoise {
|
||||||
buffer.set_duration(duration);
|
buffer.set_duration(duration);
|
||||||
buffer.set_pts(pts);
|
buffer.set_pts(pts);
|
||||||
|
|
||||||
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
let (level, has_voice) = {
|
||||||
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
|
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
||||||
|
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
|
||||||
|
self.process(state, &settings, in_data, out_data)
|
||||||
|
};
|
||||||
|
|
||||||
state.process(in_data, out_data);
|
gst_audio::AudioLevelMeta::add(buffer, level, has_voice);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.obj().src_pad().push(buffer)
|
self.obj().src_pad().push(buffer)
|
||||||
|
@ -164,6 +148,7 @@ impl AudioRNNoise {
|
||||||
let duration = state.buffer_duration(output_size as _);
|
let duration = state.buffer_duration(output_size as _);
|
||||||
let pts = state.current_pts();
|
let pts = state.current_pts();
|
||||||
|
|
||||||
|
let settings = *self.settings.lock().unwrap();
|
||||||
let mut buffer = gst::Buffer::with_size(output_size).map_err(|_| gst::FlowError::Error)?;
|
let mut buffer = gst::Buffer::with_size(output_size).map_err(|_| gst::FlowError::Error)?;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -178,24 +163,133 @@ impl AudioRNNoise {
|
||||||
buffer.set_duration(duration);
|
buffer.set_duration(duration);
|
||||||
buffer.set_pts(pts);
|
buffer.set_pts(pts);
|
||||||
|
|
||||||
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
let (level, has_voice) = {
|
||||||
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
|
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
||||||
|
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
|
||||||
|
self.process(state, &settings, in_data, out_data)
|
||||||
|
};
|
||||||
|
|
||||||
state.process(in_data, out_data);
|
gst_audio::AudioLevelMeta::add(buffer, level, has_voice);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(GenerateOutputSuccess::Buffer(buffer))
|
Ok(GenerateOutputSuccess::Buffer(buffer))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process(
|
||||||
|
&self,
|
||||||
|
state: &mut State,
|
||||||
|
settings: &Settings,
|
||||||
|
input_plane: &[f32],
|
||||||
|
output_plane: &mut [f32],
|
||||||
|
) -> (u8, bool) {
|
||||||
|
let channels = state.in_info.channels() as usize;
|
||||||
|
let size = FRAME_SIZE * channels;
|
||||||
|
let mut has_voice = false;
|
||||||
|
|
||||||
|
for (out_frame, in_frame) in output_plane.chunks_mut(size).zip(input_plane.chunks(size)) {
|
||||||
|
for (index, item) in in_frame.iter().enumerate() {
|
||||||
|
let channel_index = index % channels;
|
||||||
|
let channel_denoiser = &mut state.denoisers[channel_index];
|
||||||
|
let pos = index / channels;
|
||||||
|
channel_denoiser.frame_chunk[pos] = *item * 32767.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in (in_frame.len() / channels)..(size / channels) {
|
||||||
|
for c in 0..channels {
|
||||||
|
let channel_denoiser = &mut state.denoisers[c];
|
||||||
|
channel_denoiser.frame_chunk[i] = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: The first chunks coming out of the denoisers contains some
|
||||||
|
// fade-in artifacts. We might want to discard those.
|
||||||
|
let mut vad: f32 = 0.0;
|
||||||
|
for channel_denoiser in &mut state.denoisers {
|
||||||
|
vad = f32::max(
|
||||||
|
vad,
|
||||||
|
channel_denoiser.denoiser.process_frame(
|
||||||
|
&mut channel_denoiser.out_chunk[..],
|
||||||
|
&channel_denoiser.frame_chunk[..],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::trace!(CAT, imp: self, "Voice activity: {}", vad);
|
||||||
|
if vad < settings.vad_threshold {
|
||||||
|
out_frame.fill(0.0);
|
||||||
|
} else {
|
||||||
|
// Upon voice activity nnoiseless never really reports a 1.0
|
||||||
|
// VAD, so we use a hardcoded value close to 1.0 here.
|
||||||
|
if vad >= 0.98 {
|
||||||
|
has_voice = true;
|
||||||
|
}
|
||||||
|
for (index, item) in out_frame.iter_mut().enumerate() {
|
||||||
|
let channel_index = index % channels;
|
||||||
|
let channel_denoiser = &state.denoisers[channel_index];
|
||||||
|
let pos = index / channels;
|
||||||
|
*item = channel_denoiser.out_chunk[pos] / 32767.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let rms = output_plane.iter().copied().map(|x| x * x).sum::<f32>();
|
||||||
|
let level = (20.0 * f32::log10(rms + f32::EPSILON)) as u8;
|
||||||
|
|
||||||
|
gst::trace!(
|
||||||
|
CAT,
|
||||||
|
imp: self,
|
||||||
|
"rms: {}, level: {}, has_voice : {} ", rms,
|
||||||
|
level,
|
||||||
|
has_voice
|
||||||
|
);
|
||||||
|
|
||||||
|
(level, has_voice)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for AudioRNNoise {
|
impl ObjectSubclass for AudioRNNoise {
|
||||||
const NAME: &'static str = "GstAudioRNNoise";
|
const NAME: &'static str = "GstAudioRNNoise";
|
||||||
type Type = super::AudioRNNoise;
|
type Type = super::AudioRNNoise;
|
||||||
type ParentType = gst_base::BaseTransform;
|
type ParentType = gst_audio::AudioFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for AudioRNNoise {}
|
impl ObjectImpl for AudioRNNoise {
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![glib::ParamSpecFloat::builder("voice-activity-threshold")
|
||||||
|
.nick("Voice activity threshold")
|
||||||
|
.blurb("Threshold of the voice activity detector below which to mute the output")
|
||||||
|
.minimum(0.0)
|
||||||
|
.maximum(1.0)
|
||||||
|
.default_value(DEFAULT_VOICE_ACTIVITY_THRESHOLD)
|
||||||
|
.mutable_playing()
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
PROPERTIES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
match pspec.name() {
|
||||||
|
"voice-activity-threshold" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
settings.vad_threshold = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"voice-activity-threshold" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.vad_threshold.to_value()
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl GstObjectImpl for AudioRNNoise {}
|
impl GstObjectImpl for AudioRNNoise {}
|
||||||
|
|
||||||
|
@ -212,34 +306,6 @@ impl ElementImpl for AudioRNNoise {
|
||||||
|
|
||||||
Some(&*ELEMENT_METADATA)
|
Some(&*ELEMENT_METADATA)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
|
||||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
|
||||||
let caps = gst_audio::AudioCapsBuilder::new_interleaved()
|
|
||||||
.format(gst_audio::AUDIO_FORMAT_F32)
|
|
||||||
.rate(48000)
|
|
||||||
.build();
|
|
||||||
let src_pad_template = gst::PadTemplate::new(
|
|
||||||
"src",
|
|
||||||
gst::PadDirection::Src,
|
|
||||||
gst::PadPresence::Always,
|
|
||||||
&caps,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let sink_pad_template = gst::PadTemplate::new(
|
|
||||||
"sink",
|
|
||||||
gst::PadDirection::Sink,
|
|
||||||
gst::PadPresence::Always,
|
|
||||||
&caps,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
vec![src_pad_template, sink_pad_template]
|
|
||||||
});
|
|
||||||
|
|
||||||
PAD_TEMPLATES.as_ref()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseTransformImpl for AudioRNNoise {
|
impl BaseTransformImpl for AudioRNNoise {
|
||||||
|
@ -248,44 +314,6 @@ impl BaseTransformImpl for AudioRNNoise {
|
||||||
const PASSTHROUGH_ON_SAME_CAPS: bool = false;
|
const PASSTHROUGH_ON_SAME_CAPS: bool = false;
|
||||||
const TRANSFORM_IP_ON_PASSTHROUGH: bool = false;
|
const TRANSFORM_IP_ON_PASSTHROUGH: bool = false;
|
||||||
|
|
||||||
fn set_caps(&self, incaps: &gst::Caps, outcaps: &gst::Caps) -> Result<(), gst::LoggableError> {
|
|
||||||
// Flush previous state
|
|
||||||
if self.state.borrow_mut().is_some() {
|
|
||||||
self.drain().map_err(|e| {
|
|
||||||
gst::loggable_error!(CAT, "Error flushing previous state data {:?}", e)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
if incaps != outcaps {
|
|
||||||
return Err(gst::loggable_error!(
|
|
||||||
CAT,
|
|
||||||
"Input and output caps are not the same"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
gst::debug!(CAT, imp: self, "Set caps to {}", incaps);
|
|
||||||
|
|
||||||
let in_info = gst_audio::AudioInfo::from_caps(incaps)
|
|
||||||
.map_err(|e| gst::loggable_error!(CAT, "Failed to parse input caps {:?}", e))?;
|
|
||||||
|
|
||||||
let mut denoisers = vec![];
|
|
||||||
for _i in 0..in_info.channels() {
|
|
||||||
denoisers.push(ChannelDenoiser {
|
|
||||||
denoiser: DenoiseState::new(),
|
|
||||||
frame_chunk: Box::new([0.0; FRAME_SIZE]),
|
|
||||||
out_chunk: Box::new([0.0; FRAME_SIZE]),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut state_lock = self.state.borrow_mut();
|
|
||||||
*state_lock = Some(State {
|
|
||||||
in_info,
|
|
||||||
denoisers,
|
|
||||||
adapter: gst_base::UniqueAdapter::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_output(&self) -> Result<GenerateOutputSuccess, gst::FlowError> {
|
fn generate_output(&self) -> Result<GenerateOutputSuccess, gst::FlowError> {
|
||||||
// Check if there are enough data in the queued buffer and adapter,
|
// Check if there are enough data in the queued buffer and adapter,
|
||||||
// if it is not the case, just notify the parent class to not generate
|
// if it is not the case, just notify the parent class to not generate
|
||||||
|
@ -357,3 +385,45 @@ impl BaseTransformImpl for AudioRNNoise {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AudioFilterImpl for AudioRNNoise {
|
||||||
|
fn allowed_caps() -> &'static gst::Caps {
|
||||||
|
static CAPS: Lazy<gst::Caps> = Lazy::new(|| {
|
||||||
|
gst_audio::AudioCapsBuilder::new_interleaved()
|
||||||
|
.format(gst_audio::AUDIO_FORMAT_F32)
|
||||||
|
.rate(48000)
|
||||||
|
.build()
|
||||||
|
});
|
||||||
|
|
||||||
|
&CAPS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&self, info: &gst_audio::AudioInfo) -> Result<(), gst::LoggableError> {
|
||||||
|
// Flush previous state
|
||||||
|
if self.state.borrow_mut().is_some() {
|
||||||
|
self.drain().map_err(|e| {
|
||||||
|
gst::loggable_error!(CAT, "Error flushing previous state data {:?}", e)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::debug!(CAT, imp: self, "Set caps to {:?}", info);
|
||||||
|
|
||||||
|
let mut denoisers = vec![];
|
||||||
|
for _i in 0..info.channels() {
|
||||||
|
denoisers.push(ChannelDenoiser {
|
||||||
|
denoiser: DenoiseState::new(),
|
||||||
|
frame_chunk: Box::new([0.0; FRAME_SIZE]),
|
||||||
|
out_chunk: Box::new([0.0; FRAME_SIZE]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state_lock = self.state.borrow_mut();
|
||||||
|
*state_lock = Some(State {
|
||||||
|
in_info: info.clone(),
|
||||||
|
denoisers,
|
||||||
|
adapter: gst_base::UniqueAdapter::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"audiornnoise",
|
"audiornnoise",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
AudioRNNoise::static_type(),
|
AudioRNNoise::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
|
use gst_audio::subclass::prelude::*;
|
||||||
use gst_base::prelude::*;
|
use gst_base::prelude::*;
|
||||||
use gst_base::subclass::prelude::*;
|
|
||||||
|
|
||||||
use std::i32;
|
use std::i32;
|
||||||
use std::sync::atomic;
|
use std::sync::atomic;
|
||||||
|
@ -118,7 +118,7 @@ pub struct EbuR128Level {
|
||||||
impl ObjectSubclass for EbuR128Level {
|
impl ObjectSubclass for EbuR128Level {
|
||||||
const NAME: &'static str = "GstEbuR128Level";
|
const NAME: &'static str = "GstEbuR128Level";
|
||||||
type Type = super::EbuR128Level;
|
type Type = super::EbuR128Level;
|
||||||
type ParentType = gst_base::BaseTransform;
|
type ParentType = gst_audio::AudioFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for EbuR128Level {
|
impl ObjectImpl for EbuR128Level {
|
||||||
|
@ -138,7 +138,7 @@ impl ObjectImpl for EbuR128Level {
|
||||||
.build()]
|
.build()]
|
||||||
});
|
});
|
||||||
|
|
||||||
&*SIGNALS
|
&SIGNALS
|
||||||
}
|
}
|
||||||
|
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
@ -283,113 +283,6 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
const PASSTHROUGH_ON_SAME_CAPS: bool = true;
|
const PASSTHROUGH_ON_SAME_CAPS: bool = true;
|
||||||
const TRANSFORM_IP_ON_PASSTHROUGH: bool = true;
|
const TRANSFORM_IP_ON_PASSTHROUGH: bool = true;
|
||||||
|
|
||||||
fn set_caps(&self, incaps: &gst::Caps, _outcaps: &gst::Caps) -> Result<(), gst::LoggableError> {
|
|
||||||
let info = match gst_audio::AudioInfo::from_caps(incaps) {
|
|
||||||
Err(_) => return Err(gst::loggable_error!(CAT, "Failed to parse input caps")),
|
|
||||||
Ok(info) => info,
|
|
||||||
};
|
|
||||||
|
|
||||||
gst::debug!(CAT, imp: self, "Configured for caps {}", incaps,);
|
|
||||||
|
|
||||||
let settings = *self.settings.lock().unwrap();
|
|
||||||
|
|
||||||
let mut ebur128 = ebur128::EbuR128::new(info.channels(), info.rate(), settings.mode.into())
|
|
||||||
.map_err(|err| gst::loggable_error!(CAT, "Failed to create EBU R128: {}", err))?;
|
|
||||||
|
|
||||||
// Map channel positions if we can to give correct weighting
|
|
||||||
if let Some(positions) = info.positions() {
|
|
||||||
let channel_map = positions
|
|
||||||
.iter()
|
|
||||||
.map(|p| {
|
|
||||||
match p {
|
|
||||||
gst_audio::AudioChannelPosition::Mono => ebur128::Channel::DualMono,
|
|
||||||
gst_audio::AudioChannelPosition::FrontLeft => ebur128::Channel::Left,
|
|
||||||
gst_audio::AudioChannelPosition::FrontRight => ebur128::Channel::Right,
|
|
||||||
gst_audio::AudioChannelPosition::FrontCenter => ebur128::Channel::Center,
|
|
||||||
gst_audio::AudioChannelPosition::Lfe1
|
|
||||||
| gst_audio::AudioChannelPosition::Lfe2 => ebur128::Channel::Unused,
|
|
||||||
gst_audio::AudioChannelPosition::RearLeft => ebur128::Channel::Mp135,
|
|
||||||
gst_audio::AudioChannelPosition::RearRight => ebur128::Channel::Mm135,
|
|
||||||
gst_audio::AudioChannelPosition::FrontLeftOfCenter => {
|
|
||||||
ebur128::Channel::MpSC
|
|
||||||
}
|
|
||||||
gst_audio::AudioChannelPosition::FrontRightOfCenter => {
|
|
||||||
ebur128::Channel::MmSC
|
|
||||||
}
|
|
||||||
gst_audio::AudioChannelPosition::RearCenter => ebur128::Channel::Mp180,
|
|
||||||
gst_audio::AudioChannelPosition::SideLeft => ebur128::Channel::Mp090,
|
|
||||||
gst_audio::AudioChannelPosition::SideRight => ebur128::Channel::Mm090,
|
|
||||||
gst_audio::AudioChannelPosition::TopFrontLeft => ebur128::Channel::Up030,
|
|
||||||
gst_audio::AudioChannelPosition::TopFrontRight => ebur128::Channel::Um030,
|
|
||||||
gst_audio::AudioChannelPosition::TopFrontCenter => ebur128::Channel::Up000,
|
|
||||||
gst_audio::AudioChannelPosition::TopCenter => ebur128::Channel::Tp000,
|
|
||||||
gst_audio::AudioChannelPosition::TopRearLeft => ebur128::Channel::Up135,
|
|
||||||
gst_audio::AudioChannelPosition::TopRearRight => ebur128::Channel::Um135,
|
|
||||||
gst_audio::AudioChannelPosition::TopSideLeft => ebur128::Channel::Up090,
|
|
||||||
gst_audio::AudioChannelPosition::TopSideRight => ebur128::Channel::Um090,
|
|
||||||
gst_audio::AudioChannelPosition::TopRearCenter => ebur128::Channel::Up180,
|
|
||||||
gst_audio::AudioChannelPosition::BottomFrontCenter => {
|
|
||||||
ebur128::Channel::Bp000
|
|
||||||
}
|
|
||||||
gst_audio::AudioChannelPosition::BottomFrontLeft => ebur128::Channel::Bp045,
|
|
||||||
gst_audio::AudioChannelPosition::BottomFrontRight => {
|
|
||||||
ebur128::Channel::Bm045
|
|
||||||
}
|
|
||||||
gst_audio::AudioChannelPosition::WideLeft => {
|
|
||||||
ebur128::Channel::Mp135 // Mp110?
|
|
||||||
}
|
|
||||||
gst_audio::AudioChannelPosition::WideRight => {
|
|
||||||
ebur128::Channel::Mm135 // Mm110?
|
|
||||||
}
|
|
||||||
gst_audio::AudioChannelPosition::SurroundLeft => {
|
|
||||||
ebur128::Channel::Mp135 // Mp110?
|
|
||||||
}
|
|
||||||
gst_audio::AudioChannelPosition::SurroundRight => {
|
|
||||||
ebur128::Channel::Mm135 // Mm110?
|
|
||||||
}
|
|
||||||
gst_audio::AudioChannelPosition::Invalid
|
|
||||||
| gst_audio::AudioChannelPosition::None => ebur128::Channel::Unused,
|
|
||||||
val => {
|
|
||||||
gst::debug!(
|
|
||||||
CAT,
|
|
||||||
imp: self,
|
|
||||||
"Unknown channel position {:?}, ignoring channel",
|
|
||||||
val
|
|
||||||
);
|
|
||||||
ebur128::Channel::Unused
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
ebur128
|
|
||||||
.set_channel_map(&channel_map)
|
|
||||||
.map_err(|err| gst::loggable_error!(CAT, "Failed to set channel map: {}", err))?;
|
|
||||||
} else {
|
|
||||||
// Weight all channels equally if we have no channel map
|
|
||||||
let channel_map = std::iter::repeat(ebur128::Channel::Center)
|
|
||||||
.take(info.channels() as usize)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
ebur128
|
|
||||||
.set_channel_map(&channel_map)
|
|
||||||
.map_err(|err| gst::loggable_error!(CAT, "Failed to set channel map: {}", err))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let interval_frames = settings
|
|
||||||
.interval
|
|
||||||
.mul_div_floor(info.rate() as u64, *gst::ClockTime::SECOND)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
*self.state.borrow_mut() = Some(State {
|
|
||||||
info,
|
|
||||||
ebur128,
|
|
||||||
num_frames: 0,
|
|
||||||
interval_frames,
|
|
||||||
interval_frames_remaining: interval_frames,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
// Drop state
|
// Drop state
|
||||||
let _ = self.state.borrow_mut().take();
|
let _ = self.state.borrow_mut().take();
|
||||||
|
@ -482,7 +375,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
|
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::M) {
|
if state.ebur128.mode().contains(ebur128::Mode::M) {
|
||||||
match state.ebur128.loudness_momentary() {
|
match state.ebur128.loudness_momentary() {
|
||||||
Ok(loudness) => s.set("momentary-loudness", &loudness),
|
Ok(loudness) => s.set("momentary-loudness", loudness),
|
||||||
Err(err) => gst::error!(
|
Err(err) => gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -494,7 +387,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
|
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::S) {
|
if state.ebur128.mode().contains(ebur128::Mode::S) {
|
||||||
match state.ebur128.loudness_shortterm() {
|
match state.ebur128.loudness_shortterm() {
|
||||||
Ok(loudness) => s.set("shortterm-loudness", &loudness),
|
Ok(loudness) => s.set("shortterm-loudness", loudness),
|
||||||
Err(err) => gst::error!(
|
Err(err) => gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -506,7 +399,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
|
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::I) {
|
if state.ebur128.mode().contains(ebur128::Mode::I) {
|
||||||
match state.ebur128.loudness_global() {
|
match state.ebur128.loudness_global() {
|
||||||
Ok(loudness) => s.set("global-loudness", &loudness),
|
Ok(loudness) => s.set("global-loudness", loudness),
|
||||||
Err(err) => gst::error!(
|
Err(err) => gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -516,7 +409,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
}
|
}
|
||||||
|
|
||||||
match state.ebur128.relative_threshold() {
|
match state.ebur128.relative_threshold() {
|
||||||
Ok(threshold) => s.set("relative-threshold", &threshold),
|
Ok(threshold) => s.set("relative-threshold", threshold),
|
||||||
Err(err) => gst::error!(
|
Err(err) => gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -528,7 +421,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
|
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::LRA) {
|
if state.ebur128.mode().contains(ebur128::Mode::LRA) {
|
||||||
match state.ebur128.loudness_range() {
|
match state.ebur128.loudness_range() {
|
||||||
Ok(range) => s.set("loudness-range", &range),
|
Ok(range) => s.set("loudness-range", range),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
gst::error!(CAT, imp: self, "Failed to get loudness range: {}", err)
|
gst::error!(CAT, imp: self, "Failed to get loudness range: {}", err)
|
||||||
}
|
}
|
||||||
|
@ -538,10 +431,10 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::SAMPLE_PEAK) {
|
if state.ebur128.mode().contains(ebur128::Mode::SAMPLE_PEAK) {
|
||||||
let peaks = (0..state.info.channels())
|
let peaks = (0..state.info.channels())
|
||||||
.map(|c| state.ebur128.sample_peak(c).map(|p| p.to_send_value()))
|
.map(|c| state.ebur128.sample_peak(c).map(|p| p.to_send_value()))
|
||||||
.collect::<Result<Vec<_>, _>>();
|
.collect::<Result<gst::Array, _>>();
|
||||||
|
|
||||||
match peaks {
|
match peaks {
|
||||||
Ok(peaks) => s.set("sample-peak", gst::Array::from(peaks)),
|
Ok(peaks) => s.set("sample-peak", peaks),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
gst::error!(CAT, imp: self, "Failed to get sample peaks: {}", err)
|
gst::error!(CAT, imp: self, "Failed to get sample peaks: {}", err)
|
||||||
}
|
}
|
||||||
|
@ -551,10 +444,10 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::TRUE_PEAK) {
|
if state.ebur128.mode().contains(ebur128::Mode::TRUE_PEAK) {
|
||||||
let peaks = (0..state.info.channels())
|
let peaks = (0..state.info.channels())
|
||||||
.map(|c| state.ebur128.true_peak(c).map(|p| p.to_send_value()))
|
.map(|c| state.ebur128.true_peak(c).map(|p| p.to_send_value()))
|
||||||
.collect::<Result<Vec<_>, _>>();
|
.collect::<Result<gst::Array, _>>();
|
||||||
|
|
||||||
match peaks {
|
match peaks {
|
||||||
Ok(peaks) => s.set("true-peak", gst::Array::from(peaks)),
|
Ok(peaks) => s.set("true-peak", peaks),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
gst::error!(CAT, imp: self, "Failed to get true peaks: {}", err)
|
gst::error!(CAT, imp: self, "Failed to get true peaks: {}", err)
|
||||||
}
|
}
|
||||||
|
@ -587,6 +480,133 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AudioFilterImpl for EbuR128Level {
|
||||||
|
fn allowed_caps() -> &'static gst::Caps {
|
||||||
|
static CAPS: Lazy<gst::Caps> = Lazy::new(|| {
|
||||||
|
gst_audio::AudioCapsBuilder::new()
|
||||||
|
.format_list([
|
||||||
|
gst_audio::AUDIO_FORMAT_S16,
|
||||||
|
gst_audio::AUDIO_FORMAT_S32,
|
||||||
|
gst_audio::AUDIO_FORMAT_F32,
|
||||||
|
gst_audio::AUDIO_FORMAT_F64,
|
||||||
|
])
|
||||||
|
// Limit from ebur128
|
||||||
|
.rate_range(1..2_822_400)
|
||||||
|
// Limit from ebur128
|
||||||
|
.channels_range(1..64)
|
||||||
|
.layout_list([
|
||||||
|
gst_audio::AudioLayout::Interleaved,
|
||||||
|
gst_audio::AudioLayout::NonInterleaved,
|
||||||
|
])
|
||||||
|
.build()
|
||||||
|
});
|
||||||
|
|
||||||
|
&CAPS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&self, info: &gst_audio::AudioInfo) -> Result<(), gst::LoggableError> {
|
||||||
|
gst::debug!(CAT, imp: self, "Configured for caps {:?}", info);
|
||||||
|
|
||||||
|
let settings = *self.settings.lock().unwrap();
|
||||||
|
|
||||||
|
let mut ebur128 = ebur128::EbuR128::new(info.channels(), info.rate(), settings.mode.into())
|
||||||
|
.map_err(|err| gst::loggable_error!(CAT, "Failed to create EBU R128: {}", err))?;
|
||||||
|
|
||||||
|
// Map channel positions if we can to give correct weighting
|
||||||
|
if let Some(positions) = info.positions() {
|
||||||
|
let channel_map = positions
|
||||||
|
.iter()
|
||||||
|
.map(|p| {
|
||||||
|
match p {
|
||||||
|
gst_audio::AudioChannelPosition::Mono => ebur128::Channel::DualMono,
|
||||||
|
gst_audio::AudioChannelPosition::FrontLeft => ebur128::Channel::Left,
|
||||||
|
gst_audio::AudioChannelPosition::FrontRight => ebur128::Channel::Right,
|
||||||
|
gst_audio::AudioChannelPosition::FrontCenter => ebur128::Channel::Center,
|
||||||
|
gst_audio::AudioChannelPosition::Lfe1
|
||||||
|
| gst_audio::AudioChannelPosition::Lfe2 => ebur128::Channel::Unused,
|
||||||
|
gst_audio::AudioChannelPosition::RearLeft => ebur128::Channel::Mp135,
|
||||||
|
gst_audio::AudioChannelPosition::RearRight => ebur128::Channel::Mm135,
|
||||||
|
gst_audio::AudioChannelPosition::FrontLeftOfCenter => {
|
||||||
|
ebur128::Channel::MpSC
|
||||||
|
}
|
||||||
|
gst_audio::AudioChannelPosition::FrontRightOfCenter => {
|
||||||
|
ebur128::Channel::MmSC
|
||||||
|
}
|
||||||
|
gst_audio::AudioChannelPosition::RearCenter => ebur128::Channel::Mp180,
|
||||||
|
gst_audio::AudioChannelPosition::SideLeft => ebur128::Channel::Mp090,
|
||||||
|
gst_audio::AudioChannelPosition::SideRight => ebur128::Channel::Mm090,
|
||||||
|
gst_audio::AudioChannelPosition::TopFrontLeft => ebur128::Channel::Up030,
|
||||||
|
gst_audio::AudioChannelPosition::TopFrontRight => ebur128::Channel::Um030,
|
||||||
|
gst_audio::AudioChannelPosition::TopFrontCenter => ebur128::Channel::Up000,
|
||||||
|
gst_audio::AudioChannelPosition::TopCenter => ebur128::Channel::Tp000,
|
||||||
|
gst_audio::AudioChannelPosition::TopRearLeft => ebur128::Channel::Up135,
|
||||||
|
gst_audio::AudioChannelPosition::TopRearRight => ebur128::Channel::Um135,
|
||||||
|
gst_audio::AudioChannelPosition::TopSideLeft => ebur128::Channel::Up090,
|
||||||
|
gst_audio::AudioChannelPosition::TopSideRight => ebur128::Channel::Um090,
|
||||||
|
gst_audio::AudioChannelPosition::TopRearCenter => ebur128::Channel::Up180,
|
||||||
|
gst_audio::AudioChannelPosition::BottomFrontCenter => {
|
||||||
|
ebur128::Channel::Bp000
|
||||||
|
}
|
||||||
|
gst_audio::AudioChannelPosition::BottomFrontLeft => ebur128::Channel::Bp045,
|
||||||
|
gst_audio::AudioChannelPosition::BottomFrontRight => {
|
||||||
|
ebur128::Channel::Bm045
|
||||||
|
}
|
||||||
|
gst_audio::AudioChannelPosition::WideLeft => {
|
||||||
|
ebur128::Channel::Mp135 // Mp110?
|
||||||
|
}
|
||||||
|
gst_audio::AudioChannelPosition::WideRight => {
|
||||||
|
ebur128::Channel::Mm135 // Mm110?
|
||||||
|
}
|
||||||
|
gst_audio::AudioChannelPosition::SurroundLeft => {
|
||||||
|
ebur128::Channel::Mp135 // Mp110?
|
||||||
|
}
|
||||||
|
gst_audio::AudioChannelPosition::SurroundRight => {
|
||||||
|
ebur128::Channel::Mm135 // Mm110?
|
||||||
|
}
|
||||||
|
gst_audio::AudioChannelPosition::Invalid
|
||||||
|
| gst_audio::AudioChannelPosition::None => ebur128::Channel::Unused,
|
||||||
|
val => {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
imp: self,
|
||||||
|
"Unknown channel position {:?}, ignoring channel",
|
||||||
|
val
|
||||||
|
);
|
||||||
|
ebur128::Channel::Unused
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
ebur128
|
||||||
|
.set_channel_map(&channel_map)
|
||||||
|
.map_err(|err| gst::loggable_error!(CAT, "Failed to set channel map: {}", err))?;
|
||||||
|
} else {
|
||||||
|
// Weight all channels equally if we have no channel map
|
||||||
|
let channel_map = std::iter::repeat(ebur128::Channel::Center)
|
||||||
|
.take(info.channels() as usize)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
ebur128
|
||||||
|
.set_channel_map(&channel_map)
|
||||||
|
.map_err(|err| gst::loggable_error!(CAT, "Failed to set channel map: {}", err))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let interval_frames = settings
|
||||||
|
.interval
|
||||||
|
.mul_div_floor(info.rate() as u64, *gst::ClockTime::SECOND)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
*self.state.borrow_mut() = Some(State {
|
||||||
|
info: info.clone(),
|
||||||
|
ebur128,
|
||||||
|
num_frames: 0,
|
||||||
|
interval_frames,
|
||||||
|
interval_frames_remaining: interval_frames,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Helper struct to handle the different sample formats and layouts generically.
|
/// Helper struct to handle the different sample formats and layouts generically.
|
||||||
enum Frames<'a> {
|
enum Frames<'a> {
|
||||||
S16(&'a [i16], usize),
|
S16(&'a [i16], usize),
|
||||||
|
@ -759,8 +779,8 @@ fn non_interleaved_channel_data_into_slices<'a, T: FromByteSlice>(
|
||||||
|
|
||||||
/// Split a vector of slices into a tuple of slices with each slice split at `split_at`.
|
/// Split a vector of slices into a tuple of slices with each slice split at `split_at`.
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn split_vec<'a, 'b, T: Copy>(
|
fn split_vec<'a, T: Copy>(
|
||||||
vec: &'b SmallVec<[&'a [T]; 64]>,
|
vec: &SmallVec<[&'a [T]; 64]>,
|
||||||
split_at: usize,
|
split_at: usize,
|
||||||
) -> (SmallVec<[&'a [T]; 64]>, SmallVec<[&'a [T]; 64]>) {
|
) -> (SmallVec<[&'a [T]; 64]>, SmallVec<[&'a [T]; 64]>) {
|
||||||
let VecPair(first, second) = vec
|
let VecPair(first, second) = vec
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"ebur128level",
|
"ebur128level",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
EbuR128Level::static_type(),
|
EbuR128Level::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -373,7 +373,7 @@ impl HrtfRender {
|
||||||
let (prev_offset, _) = state.adapter.prev_offset();
|
let (prev_offset, _) = state.adapter.prev_offset();
|
||||||
let offset = prev_offset.checked_add(distance_samples).unwrap_or(0);
|
let offset = prev_offset.checked_add(distance_samples).unwrap_or(0);
|
||||||
|
|
||||||
let duration_samples = outputsz / outbpf as usize;
|
let duration_samples = outputsz / outbpf;
|
||||||
let duration = samples_to_time(duration_samples as u64);
|
let duration = samples_to_time(duration_samples as u64);
|
||||||
|
|
||||||
(pts, offset, duration)
|
(pts, offset, duration)
|
||||||
|
@ -514,15 +514,15 @@ impl ObjectImpl for HrtfRender {
|
||||||
}
|
}
|
||||||
"spatial-objects" => {
|
"spatial-objects" => {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
let spatial_objects = settings
|
|
||||||
|
settings
|
||||||
.spatial_objects
|
.spatial_objects
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap_or(&Vec::new())
|
.unwrap_or(&Vec::new())
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| gst::Structure::from(*x).to_send_value())
|
.map(|x| gst::Structure::from(*x).to_send_value())
|
||||||
.collect::<Vec<_>>();
|
.collect::<gst::Array>()
|
||||||
|
.to_value()
|
||||||
gst::Array::from(spatial_objects).to_value()
|
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
@ -649,7 +649,7 @@ impl BaseTransformImpl for HrtfRender {
|
||||||
|
|
||||||
if direction == gst::PadDirection::Sink {
|
if direction == gst::PadDirection::Sink {
|
||||||
s.set("channels", 2);
|
s.set("channels", 2);
|
||||||
s.set("channel-mask", 0x3);
|
s.set("channel-mask", gst::Bitmask(0x3));
|
||||||
} else {
|
} else {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
if let Some(objs) = &settings.spatial_objects {
|
if let Some(objs) = &settings.spatial_objects {
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"hrtfrender",
|
"hrtfrender",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
HrtfRender::static_type(),
|
HrtfRender::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,13 @@ fn run_test(
|
||||||
init();
|
init();
|
||||||
|
|
||||||
let format = if cfg!(target_endian = "little") {
|
let format = if cfg!(target_endian = "little") {
|
||||||
format!("audio/x-raw,format=F64LE,rate=192000,channels={}", channels)
|
format!("audio/x-raw,format=F64LE,rate=192000,channels={channels}")
|
||||||
} else {
|
} else {
|
||||||
format!("audio/x-raw,format=F64BE,rate=192000,channels={}", channels)
|
format!("audio/x-raw,format=F64BE,rate=192000,channels={channels}")
|
||||||
};
|
};
|
||||||
|
|
||||||
let pipeline = if let Some(second_input) = second_input {
|
let pipeline = if let Some(second_input) = second_input {
|
||||||
gst::parse_launch(&format!(
|
gst::parse::launch(&format!(
|
||||||
"audiotestsrc {first_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! audiomixer name=mixer output-buffer-duration={output_buffer_duration} ! {format} ! audioloudnorm ! appsink name=sink audiotestsrc {second_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! mixer.",
|
"audiotestsrc {first_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! audiomixer name=mixer output-buffer-duration={output_buffer_duration} ! {format} ! audioloudnorm ! appsink name=sink audiotestsrc {second_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! mixer.",
|
||||||
first_input = first_input,
|
first_input = first_input,
|
||||||
second_input = second_input,
|
second_input = second_input,
|
||||||
|
@ -49,12 +49,8 @@ fn run_test(
|
||||||
format = format,
|
format = format,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
gst::parse_launch(&format!(
|
gst::parse::launch(&format!(
|
||||||
"audiotestsrc {first_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! audioloudnorm ! appsink name=sink",
|
"audiotestsrc {first_input} num-buffers={num_buffers} samplesperbuffer={samples_per_buffer} ! {format} ! audioloudnorm ! appsink name=sink",
|
||||||
first_input = first_input,
|
|
||||||
num_buffers = num_buffers,
|
|
||||||
samples_per_buffer = samples_per_buffer,
|
|
||||||
format = format,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -129,17 +125,13 @@ fn run_test(
|
||||||
Ordering::Greater => {
|
Ordering::Greater => {
|
||||||
assert!(
|
assert!(
|
||||||
ts - expected_ts <= gst::ClockTime::NSECOND,
|
ts - expected_ts <= gst::ClockTime::NSECOND,
|
||||||
"TS is {} instead of {}",
|
"TS is {ts} instead of {expected_ts}"
|
||||||
ts,
|
|
||||||
expected_ts
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ordering::Less => {
|
Ordering::Less => {
|
||||||
assert!(
|
assert!(
|
||||||
expected_ts - ts <= gst::ClockTime::NSECOND,
|
expected_ts - ts <= gst::ClockTime::NSECOND,
|
||||||
"TS is {} instead of {}",
|
"TS is {ts} instead of {expected_ts}"
|
||||||
ts,
|
|
||||||
expected_ts
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ordering::Equal => (),
|
Ordering::Equal => (),
|
||||||
|
@ -164,27 +156,18 @@ fn run_test(
|
||||||
if expected_loudness.classify() == std::num::FpCategory::Infinite && expected_loudness < 0.0 {
|
if expected_loudness.classify() == std::num::FpCategory::Infinite && expected_loudness < 0.0 {
|
||||||
assert!(
|
assert!(
|
||||||
loudness.classify() == std::num::FpCategory::Infinite && loudness < 0.0,
|
loudness.classify() == std::num::FpCategory::Infinite && loudness < 0.0,
|
||||||
"Loudness is {} instead of {}",
|
"Loudness is {loudness} instead of {expected_loudness}",
|
||||||
loudness,
|
|
||||||
expected_loudness,
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
assert!(
|
assert!(
|
||||||
f64::abs(loudness - expected_loudness) < 1.0,
|
f64::abs(loudness - expected_loudness) < 1.0,
|
||||||
"Loudness is {} instead of {}",
|
"Loudness is {loudness} instead of {expected_loudness}",
|
||||||
loudness,
|
|
||||||
expected_loudness,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in 0..channels {
|
for c in 0..channels {
|
||||||
let peak = 20.0 * f64::log10(r128.sample_peak(c).unwrap());
|
let peak = 20.0 * f64::log10(r128.sample_peak(c).unwrap());
|
||||||
assert!(
|
assert!(peak <= -2.0, "Peak {c} for channel {peak} is above -2.0",);
|
||||||
peak <= -2.0,
|
|
||||||
"Peak {} for channel {} is above -2.0",
|
|
||||||
c,
|
|
||||||
peak,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,7 +156,7 @@ fn test_hrtfrender_explicit_spatial_objects() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// Caps negotation should fail if we have mismatch between input channels and
|
// Caps negotiation should fail if we have mismatch between input channels and
|
||||||
// of objects that we set via property. In this test case input has 6 channels
|
// of objects that we set via property. In this test case input has 6 channels
|
||||||
// but the number of spatial objects set is 2.
|
// but the number of spatial objects set is 2.
|
||||||
fn test_hrtfrender_caps_negotiation_fail() {
|
fn test_hrtfrender_caps_negotiation_fail() {
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-claxon"
|
name = "gst-plugin-claxon"
|
||||||
version = "0.9.0-alpha.1"
|
version.workspace = true
|
||||||
authors = ["Ruben Gonzalez <rgonzalez@fluendo.com>"]
|
authors = ["Ruben Gonzalez <rgonzalez@fluendo.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository.workspace = true
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "GStreamer Claxon FLAC Decoder Plugin"
|
description = "GStreamer Claxon FLAC Decoder Plugin"
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.63"
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst.workspace = true
|
||||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-audio.workspace = true
|
||||||
claxon = { version = "0.4" }
|
claxon = { version = "0.4" }
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
atomic_refcell = "0.1"
|
atomic_refcell = "0.1"
|
||||||
once_cell = "1"
|
once_cell.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-check.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstclaxon"
|
name = "gstclaxon"
|
||||||
|
@ -25,7 +25,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
@ -33,7 +33,7 @@ capi = []
|
||||||
doc = ["gst/v1_18"]
|
doc = ["gst/v1_18"]
|
||||||
|
|
||||||
[package.metadata.capi]
|
[package.metadata.capi]
|
||||||
min_version = "0.8.0"
|
min_version = "0.9.21"
|
||||||
|
|
||||||
[package.metadata.capi.header]
|
[package.metadata.capi.header]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -41,6 +41,7 @@ enabled = false
|
||||||
[package.metadata.capi.library]
|
[package.metadata.capi.library]
|
||||||
install_subdir = "gstreamer-1.0"
|
install_subdir = "gstreamer-1.0"
|
||||||
versioning = false
|
versioning = false
|
||||||
|
import_library = false
|
||||||
|
|
||||||
[package.metadata.capi.pkg_config]
|
[package.metadata.capi.pkg_config]
|
||||||
requires_private = "gstreamer-1.0, gstreamer-audio-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
requires_private = "gstreamer-1.0, gstreamer-audio-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-APACHE
|
|
201
audio/claxon/LICENSE-APACHE
Normal file
201
audio/claxon/LICENSE-APACHE
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-MIT
|
|
23
audio/claxon/LICENSE-MIT
Normal file
23
audio/claxon/LICENSE-MIT
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
|
@ -209,12 +209,12 @@ impl ClaxonDec {
|
||||||
indata: &[u8],
|
indata: &[u8],
|
||||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
let streaminfo = claxon_streaminfo(indata).map_err(|e| {
|
let streaminfo = claxon_streaminfo(indata).map_err(|e| {
|
||||||
gst::element_imp_error!(self, gst::StreamError::Decode, [e]);
|
gst::element_imp_error!(self, gst::StreamError::Decode, ["{e}"]);
|
||||||
gst::FlowError::Error
|
gst::FlowError::Error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let audio_info = gstaudioinfo(&streaminfo).map_err(|e| {
|
let audio_info = gstaudioinfo(&streaminfo).map_err(|e| {
|
||||||
gst::element_imp_error!(self, gst::StreamError::Decode, [&e]);
|
gst::element_imp_error!(self, gst::StreamError::Decode, ["{e}"]);
|
||||||
gst::FlowError::Error
|
gst::FlowError::Error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -392,7 +392,7 @@ fn gstaudioinfo(streaminfo: &claxon::metadata::StreamInfo) -> Result<gst_audio::
|
||||||
|
|
||||||
let audio_info = info_builder
|
let audio_info = info_builder
|
||||||
.build()
|
.build()
|
||||||
.map_err(|e| format!("failed to build audio info: {}", e))?;
|
.map_err(|e| format!("failed to build audio info: {e}"))?;
|
||||||
|
|
||||||
Ok(audio_info)
|
Ok(audio_info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"claxondec",
|
"claxondec",
|
||||||
gst::Rank::Marginal,
|
gst::Rank::MARGINAL,
|
||||||
ClaxonDec::static_type(),
|
ClaxonDec::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-csound"
|
name = "gst-plugin-csound"
|
||||||
version = "0.9.0-alpha.1"
|
version.workspace = true
|
||||||
authors = ["Natanael Mojica <neithanmo@gmail.com>"]
|
authors = ["Natanael Mojica <neithanmo@gmail.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository.workspace = true
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.63"
|
rust-version.workspace = true
|
||||||
description = "GStreamer Audio Filter plugin based on Csound"
|
description = "GStreamer Audio Filter plugin based on Csound"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst.workspace = true
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-base.workspace = true
|
||||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-audio.workspace = true
|
||||||
csound = "0.1.8"
|
csound = "0.1.8"
|
||||||
once_cell = "1.0"
|
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
|
once_cell.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-check.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstcsound"
|
name = "gstcsound"
|
||||||
|
@ -29,7 +29,7 @@ name = "csound-effect"
|
||||||
path = "examples/effect_example.rs"
|
path = "examples/effect_example.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path = "../../version-helper" }
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
@ -37,7 +37,7 @@ capi = []
|
||||||
doc = ["gst/v1_18"]
|
doc = ["gst/v1_18"]
|
||||||
|
|
||||||
[package.metadata.capi]
|
[package.metadata.capi]
|
||||||
min_version = "0.8.0"
|
min_version = "0.9.21"
|
||||||
|
|
||||||
[package.metadata.capi.header]
|
[package.metadata.capi.header]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -45,6 +45,7 @@ enabled = false
|
||||||
[package.metadata.capi.library]
|
[package.metadata.capi.library]
|
||||||
install_subdir = "gstreamer-1.0"
|
install_subdir = "gstreamer-1.0"
|
||||||
versioning = false
|
versioning = false
|
||||||
|
import_library = false
|
||||||
|
|
||||||
[package.metadata.capi.pkg_config]
|
[package.metadata.capi.pkg_config]
|
||||||
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-audio-1.0, gobject-2.0, glib-2.0, gmodule-2.0, csound"
|
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-audio-1.0, gobject-2.0, glib-2.0, gmodule-2.0, csound"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-MPL-2.0
|
|
373
audio/csound/LICENSE-MPL-2.0
Normal file
373
audio/csound/LICENSE-MPL-2.0
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
|
@ -19,7 +19,7 @@ const AUDIO_SINK: &str = "audioconvert ! autoaudiosink";
|
||||||
// from the global accumulator(gasig), then reads these buffers at a fixed delay time, creating the adelL, adelM and adelR buffers,
|
// from the global accumulator(gasig), then reads these buffers at a fixed delay time, creating the adelL, adelM and adelR buffers,
|
||||||
// also, It multiplies the audio samples in the right channel by 0.5 * kdel, being kdel a line of values starting at 0.5 at increments of 0.001.
|
// also, It multiplies the audio samples in the right channel by 0.5 * kdel, being kdel a line of values starting at 0.5 at increments of 0.001.
|
||||||
// Finally, those buffers are mixed with the accumulator, and an audio envelop is applied(aseg) to them.
|
// Finally, those buffers are mixed with the accumulator, and an audio envelop is applied(aseg) to them.
|
||||||
// The result is similar to an audio echo in which the buffered samples are read at different delay times and also modified in frecuency(right channel),
|
// The result is similar to an audio echo in which the buffered samples are read at different delay times and also modified in frequency(right channel),
|
||||||
// this creates an space effect using just one channel audio input.
|
// this creates an space effect using just one channel audio input.
|
||||||
const CSD: &str = "
|
const CSD: &str = "
|
||||||
<CsoundSynthesizer>
|
<CsoundSynthesizer>
|
||||||
|
@ -75,16 +75,16 @@ const CSD: &str = "
|
||||||
fn create_pipeline() -> Result<gst::Pipeline, Box<dyn Error>> {
|
fn create_pipeline() -> Result<gst::Pipeline, Box<dyn Error>> {
|
||||||
let pipeline = gst::Pipeline::default();
|
let pipeline = gst::Pipeline::default();
|
||||||
|
|
||||||
let audio_src = gst::parse_bin_from_description(AUDIO_SRC, true)?.upcast();
|
let audio_src = gst::parse::bin_from_description(AUDIO_SRC, true)?.upcast();
|
||||||
|
|
||||||
let audio_sink = gst::parse_bin_from_description(AUDIO_SINK, true)?.upcast();
|
let audio_sink = gst::parse::bin_from_description(AUDIO_SINK, true)?.upcast();
|
||||||
|
|
||||||
let csoundfilter = gst::ElementFactory::make("csoundfilter")
|
let csoundfilter = gst::ElementFactory::make("csoundfilter")
|
||||||
.property("csd-text", &CSD)
|
.property("csd-text", CSD)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
pipeline.add_many(&[&audio_src, &csoundfilter, &audio_sink])?;
|
pipeline.add_many([&audio_src, &csoundfilter, &audio_sink])?;
|
||||||
|
|
||||||
audio_src.link_pads(Some("src"), &csoundfilter, Some("sink"))?;
|
audio_src.link_pads(Some("src"), &csoundfilter, Some("sink"))?;
|
||||||
csoundfilter.link_pads(Some("src"), &audio_sink, Some("sink"))?;
|
csoundfilter.link_pads(Some("src"), &audio_sink, Some("sink"))?;
|
||||||
|
|
|
@ -127,9 +127,9 @@ impl CsoundFilter {
|
||||||
let spout = csound.get_spout().unwrap();
|
let spout = csound.get_spout().unwrap();
|
||||||
|
|
||||||
let in_chunks = idata.chunks_exact(spin.len());
|
let in_chunks = idata.chunks_exact(spin.len());
|
||||||
let out_chuncks = odata.chunks_exact_mut(spout.len());
|
let out_chunks = odata.chunks_exact_mut(spout.len());
|
||||||
let mut end_score = false;
|
let mut end_score = false;
|
||||||
for (ichunk, ochunk) in in_chunks.zip(out_chuncks) {
|
for (ichunk, ochunk) in in_chunks.zip(out_chunks) {
|
||||||
spin.copy_from_slice(ichunk);
|
spin.copy_from_slice(ichunk);
|
||||||
end_score = csound.perform_ksmps();
|
end_score = csound.perform_ksmps();
|
||||||
spout.copy_to_slice(ochunk);
|
spout.copy_to_slice(ochunk);
|
||||||
|
@ -144,11 +144,11 @@ impl CsoundFilter {
|
||||||
if let Some(ref location) = settings.location {
|
if let Some(ref location) = settings.location {
|
||||||
csound
|
csound
|
||||||
.compile_csd(location)
|
.compile_csd(location)
|
||||||
.map_err(|e| error_msg!(gst::LibraryError::Failed, [e]))?;
|
.map_err(|e| error_msg!(gst::LibraryError::Failed, ["{e}"]))?;
|
||||||
} else if let Some(ref text) = settings.csd_text {
|
} else if let Some(ref text) = settings.csd_text {
|
||||||
csound
|
csound
|
||||||
.compile_csd_text(text)
|
.compile_csd_text(text)
|
||||||
.map_err(|e| error_msg!(gst::LibraryError::Failed, [e]))?;
|
.map_err(|e| error_msg!(gst::LibraryError::Failed, ["{e}"]))?;
|
||||||
} else {
|
} else {
|
||||||
return Err(error_msg!(
|
return Err(error_msg!(
|
||||||
gst::LibraryError::Failed,
|
gst::LibraryError::Failed,
|
||||||
|
@ -254,7 +254,7 @@ impl CsoundFilter {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get the required amount of bytes to be read from
|
// Get the required amount of bytes to be read from
|
||||||
// the adapter to fill an ouput buffer of size output_size
|
// the adapter to fill an output buffer of size output_size
|
||||||
let bytes_to_read = state.bytes_to_read(output_size);
|
let bytes_to_read = state.bytes_to_read(output_size);
|
||||||
|
|
||||||
let indata = state
|
let indata = state
|
||||||
|
@ -470,7 +470,7 @@ impl BaseTransformImpl for CsoundFilter {
|
||||||
csound.set_score_offset_seconds(settings.offset);
|
csound.set_score_offset_seconds(settings.offset);
|
||||||
|
|
||||||
if let Err(e) = csound.start() {
|
if let Err(e) = csound.start() {
|
||||||
return Err(error_msg!(gst::LibraryError::Failed, [e]));
|
return Err(error_msg!(gst::LibraryError::Failed, ["{e}"]));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -517,15 +517,15 @@ impl BaseTransformImpl for CsoundFilter {
|
||||||
let ichannels = csound.input_channels() as i32;
|
let ichannels = csound.input_channels() as i32;
|
||||||
let ochannels = csound.output_channels() as i32;
|
let ochannels = csound.output_channels() as i32;
|
||||||
for s in new_caps.make_mut().iter_mut() {
|
for s in new_caps.make_mut().iter_mut() {
|
||||||
s.set("format", &gst_audio::AUDIO_FORMAT_F64.to_str());
|
s.set("format", gst_audio::AUDIO_FORMAT_F64.to_str());
|
||||||
s.set("rate", &sr);
|
s.set("rate", sr);
|
||||||
|
|
||||||
// replace the channel property with our values,
|
// replace the channel property with our values,
|
||||||
// if they are not supported, the negotiation will fail.
|
// if they are not supported, the negotiation will fail.
|
||||||
if direction == gst::PadDirection::Src {
|
if direction == gst::PadDirection::Src {
|
||||||
s.set("channels", &ichannels);
|
s.set("channels", ichannels);
|
||||||
} else {
|
} else {
|
||||||
s.set("channels", &ochannels);
|
s.set("channels", ochannels);
|
||||||
}
|
}
|
||||||
// Csound does not have a concept of channel-mask
|
// Csound does not have a concept of channel-mask
|
||||||
s.remove_field("channel-mask");
|
s.remove_field("channel-mask");
|
||||||
|
@ -554,7 +554,7 @@ impl BaseTransformImpl for CsoundFilter {
|
||||||
// Flush previous state
|
// Flush previous state
|
||||||
if self.state.lock().unwrap().is_some() {
|
if self.state.lock().unwrap().is_some() {
|
||||||
self.drain()
|
self.drain()
|
||||||
.map_err(|e| loggable_error!(CAT, "Error flusing previous state data {:?}", e))?;
|
.map_err(|e| loggable_error!(CAT, "Error flushing previous state data {:?}", e))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let in_info = gst_audio::AudioInfo::from_caps(incaps)
|
let in_info = gst_audio::AudioInfo::from_caps(incaps)
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"csoundfilter",
|
"csoundfilter",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
CsoundFilter::static_type(),
|
CsoundFilter::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ fn init() {
|
||||||
|
|
||||||
fn build_harness(src_caps: gst::Caps, sink_caps: gst::Caps, csd: &str) -> gst_check::Harness {
|
fn build_harness(src_caps: gst::Caps, sink_caps: gst::Caps, csd: &str) -> gst_check::Harness {
|
||||||
let filter = gst::ElementFactory::make("csoundfilter")
|
let filter = gst::ElementFactory::make("csoundfilter")
|
||||||
.property("csd-text", &csd)
|
.property("csd-text", csd)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -260,10 +260,7 @@ fn csound_filter_underflow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(num_buffers, UNDERFLOW_NUM_BUFFERS / 2);
|
assert_eq!(num_buffers, UNDERFLOW_NUM_BUFFERS / 2);
|
||||||
assert_eq!(
|
assert_eq!(num_samples, UNDERFLOW_NUM_SAMPLES * UNDERFLOW_NUM_BUFFERS);
|
||||||
num_samples as usize,
|
|
||||||
UNDERFLOW_NUM_SAMPLES * UNDERFLOW_NUM_BUFFERS
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifies that the caps negotiation is properly done, by pushing buffers whose caps
|
// Verifies that the caps negotiation is properly done, by pushing buffers whose caps
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-lewton"
|
name = "gst-plugin-lewton"
|
||||||
version = "0.9.0-alpha.1"
|
version.workspace = true
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository.workspace = true
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "GStreamer lewton Vorbis Decoder Plugin"
|
description = "GStreamer lewton Vorbis Decoder Plugin"
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.63"
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst.workspace = true
|
||||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-audio.workspace = true
|
||||||
lewton = { version = "0.10", default-features = false }
|
lewton = { version = "0.10", default-features = false }
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
atomic_refcell = "0.1"
|
atomic_refcell = "0.1"
|
||||||
once_cell = "1.0"
|
once_cell.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-check.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstlewton"
|
name = "gstlewton"
|
||||||
|
@ -25,7 +25,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
@ -33,7 +33,7 @@ capi = []
|
||||||
doc = ["gst/v1_18"]
|
doc = ["gst/v1_18"]
|
||||||
|
|
||||||
[package.metadata.capi]
|
[package.metadata.capi]
|
||||||
min_version = "0.8.0"
|
min_version = "0.9.21"
|
||||||
|
|
||||||
[package.metadata.capi.header]
|
[package.metadata.capi.header]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -41,6 +41,7 @@ enabled = false
|
||||||
[package.metadata.capi.library]
|
[package.metadata.capi.library]
|
||||||
install_subdir = "gstreamer-1.0"
|
install_subdir = "gstreamer-1.0"
|
||||||
versioning = false
|
versioning = false
|
||||||
|
import_library = false
|
||||||
|
|
||||||
[package.metadata.capi.pkg_config]
|
[package.metadata.capi.pkg_config]
|
||||||
requires_private = "gstreamer-1.0, gstreamer-audio-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
requires_private = "gstreamer-1.0, gstreamer-audio-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-APACHE
|
|
201
audio/lewton/LICENSE-APACHE
Normal file
201
audio/lewton/LICENSE-APACHE
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-MIT
|
|
23
audio/lewton/LICENSE-MIT
Normal file
23
audio/lewton/LICENSE-MIT
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
|
@ -132,7 +132,7 @@ impl AudioDecoderImpl for LewtonDec {
|
||||||
reorder_map: None,
|
reorder_map: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut state = state_guard.as_mut().unwrap();
|
let state = state_guard.as_mut().unwrap();
|
||||||
|
|
||||||
let s = caps.structure(0).unwrap();
|
let s = caps.structure(0).unwrap();
|
||||||
if let Ok(Some(streamheaders)) = s.get_optional::<gst::ArrayRef>("streamheader") {
|
if let Ok(Some(streamheaders)) = s.get_optional::<gst::ArrayRef>("streamheader") {
|
||||||
|
@ -405,7 +405,7 @@ impl LewtonDec {
|
||||||
let outbuf = if let Some(ref reorder_map) = state.reorder_map {
|
let outbuf = if let Some(ref reorder_map) = state.reorder_map {
|
||||||
let mut outbuf = self
|
let mut outbuf = self
|
||||||
.obj()
|
.obj()
|
||||||
.allocate_output_buffer(sample_count as usize * audio_info.bpf() as usize);
|
.allocate_output_buffer(sample_count * audio_info.bpf() as usize);
|
||||||
{
|
{
|
||||||
// And copy the decoded data into our output buffer while reordering the channels to the
|
// And copy the decoded data into our output buffer while reordering the channels to the
|
||||||
// GStreamer channel order
|
// GStreamer channel order
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"lewtondec",
|
"lewtondec",
|
||||||
gst::Rank::Marginal,
|
gst::Rank::MARGINAL,
|
||||||
LewtonDec::static_type(),
|
LewtonDec::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-spotify"
|
name = "gst-plugin-spotify"
|
||||||
version = "0.9.0-alpha.1"
|
version.workspace = true
|
||||||
authors = ["Guillaume Desmottes <guillaume@desmottes.be>"]
|
authors = ["Guillaume Desmottes <guillaume@desmottes.be>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository.workspace = true
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "GStreamer Spotify Plugin"
|
description = "GStreamer Spotify Plugin"
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.63"
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst.workspace = true
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-base.workspace = true
|
||||||
once_cell = "1.0"
|
|
||||||
librespot = { version = "0.4", default-features = false }
|
librespot = { version = "0.4", default-features = false }
|
||||||
tokio = "1.0"
|
tokio = "1.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
url = "2.3"
|
url = "2.3"
|
||||||
|
once_cell.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstspotify"
|
name = "gstspotify"
|
||||||
|
@ -24,7 +24,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
@ -32,7 +32,7 @@ capi = []
|
||||||
doc = ["gst/v1_18"]
|
doc = ["gst/v1_18"]
|
||||||
|
|
||||||
[package.metadata.capi]
|
[package.metadata.capi]
|
||||||
min_version = "0.8.0"
|
min_version = "0.9.21"
|
||||||
|
|
||||||
[package.metadata.capi.header]
|
[package.metadata.capi.header]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -40,6 +40,7 @@ enabled = false
|
||||||
[package.metadata.capi.library]
|
[package.metadata.capi.library]
|
||||||
install_subdir = "gstreamer-1.0"
|
install_subdir = "gstreamer-1.0"
|
||||||
versioning = false
|
versioning = false
|
||||||
|
import_library = false
|
||||||
|
|
||||||
[package.metadata.capi.pkg_config]
|
[package.metadata.capi.pkg_config]
|
||||||
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-MPL-2.0
|
|
373
audio/spotify/LICENSE-MPL-2.0
Normal file
373
audio/spotify/LICENSE-MPL-2.0
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
|
@ -8,10 +8,11 @@ to respect their legal/licensing restrictions.
|
||||||
|
|
||||||
## Spotify Credentials
|
## Spotify Credentials
|
||||||
|
|
||||||
This plugin requires a [Spotify Premium](https://www.spotify.com/premium/) account configured
|
This plugin requires a [Spotify Premium](https://www.spotify.com/premium/) account.
|
||||||
with a [device password](https://www.spotify.com/us/account/set-device-password/).
|
If your account is linked with Facebook, you'll need to setup
|
||||||
|
a [device username and password](https://www.spotify.com/us/account/set-device-password/).
|
||||||
|
|
||||||
You can then set the device username and password using the `username` and `password` properties.
|
Those username and password are then set using the `username` and `password` properties.
|
||||||
|
|
||||||
You may also want to cache credentials and downloaded files, see the `cache-` properties on the element.
|
You may also want to cache credentials and downloaded files, see the `cache-` properties on the element.
|
||||||
|
|
||||||
|
|
194
audio/spotify/src/common.rs
Normal file
194
audio/spotify/src/common.rs
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
// Copyright (C) 2021 Guillaume Desmottes <guillaume@desmottes.be>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use anyhow::bail;
|
||||||
|
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
use librespot::core::{
|
||||||
|
cache::Cache, config::SessionConfig, session::Session, spotify_id::SpotifyId,
|
||||||
|
};
|
||||||
|
use librespot::discovery::Credentials;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
username: String,
|
||||||
|
password: String,
|
||||||
|
cache_credentials: String,
|
||||||
|
cache_files: String,
|
||||||
|
cache_max_size: u64,
|
||||||
|
pub track: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Settings {
|
||||||
|
pub fn properties() -> Vec<glib::ParamSpec> {
|
||||||
|
vec![glib::ParamSpecString::builder("username")
|
||||||
|
.nick("Username")
|
||||||
|
.blurb("Spotify username, Facebook accounts need a device username from https://www.spotify.com/us/account/set-device-password/")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecString::builder("password")
|
||||||
|
.nick("Password")
|
||||||
|
.blurb("Spotify password, Facebook accounts need a device password from https://www.spotify.com/us/account/set-device-password/")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecString::builder("cache-credentials")
|
||||||
|
.nick("Credentials cache")
|
||||||
|
.blurb("Directory where to cache Spotify credentials")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecString::builder("cache-files")
|
||||||
|
.nick("Files cache")
|
||||||
|
.blurb("Directory where to cache downloaded files from Spotify")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecUInt64::builder("cache-max-size")
|
||||||
|
.nick("Cache max size")
|
||||||
|
.blurb("The max allowed size of the cache, in bytes, or 0 to disable the cache limit")
|
||||||
|
.default_value(0)
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecString::builder("track")
|
||||||
|
.nick("Spotify URI")
|
||||||
|
.blurb("Spotify track URI, in the form 'spotify:track:$SPOTIFY_ID'")
|
||||||
|
.default_value(Some(""))
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_property(&mut self, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
match pspec.name() {
|
||||||
|
"username" => {
|
||||||
|
self.username = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"password" => {
|
||||||
|
self.password = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"cache-credentials" => {
|
||||||
|
self.cache_credentials = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"cache-files" => {
|
||||||
|
self.cache_files = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"cache-max-size" => {
|
||||||
|
self.cache_max_size = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
"track" => {
|
||||||
|
self.track = value.get().expect("type checked upstream");
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn property(&self, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"username" => self.username.to_value(),
|
||||||
|
"password" => self.password.to_value(),
|
||||||
|
"cache-credentials" => self.cache_credentials.to_value(),
|
||||||
|
"cache-files" => self.cache_files.to_value(),
|
||||||
|
"cache-max-size" => self.cache_max_size.to_value(),
|
||||||
|
"track" => self.track.to_value(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn connect_session<T>(
|
||||||
|
&self,
|
||||||
|
src: T,
|
||||||
|
cat: &gst::DebugCategory,
|
||||||
|
) -> anyhow::Result<Session>
|
||||||
|
where
|
||||||
|
T: IsA<glib::Object>,
|
||||||
|
{
|
||||||
|
let credentials_cache = if self.cache_credentials.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.cache_credentials)
|
||||||
|
};
|
||||||
|
|
||||||
|
let files_cache = if self.cache_files.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(&self.cache_files)
|
||||||
|
};
|
||||||
|
|
||||||
|
let max_size = if self.cache_max_size != 0 {
|
||||||
|
Some(self.cache_max_size)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let cache = Cache::new(credentials_cache, None, files_cache, max_size)?;
|
||||||
|
|
||||||
|
if let Some(cached_cred) = cache.credentials() {
|
||||||
|
if !self.username.is_empty() && self.username != cached_cred.username {
|
||||||
|
gst::debug!(
|
||||||
|
cat,
|
||||||
|
obj: &src,
|
||||||
|
"ignore cached credentials for user {} which mismatch user {}",
|
||||||
|
cached_cred.username,
|
||||||
|
self.username
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
gst::debug!(
|
||||||
|
cat,
|
||||||
|
obj: &src,
|
||||||
|
"reuse cached credentials for user {}",
|
||||||
|
cached_cred.username
|
||||||
|
);
|
||||||
|
if let Ok((session, _credentials)) = Session::connect(
|
||||||
|
SessionConfig::default(),
|
||||||
|
cached_cred,
|
||||||
|
Some(cache.clone()),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
return Ok(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::debug!(
|
||||||
|
cat,
|
||||||
|
obj: &src,
|
||||||
|
"credentials not in cache or cached credentials invalid",
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.username.is_empty() {
|
||||||
|
bail!("username is not set and credentials are not in cache");
|
||||||
|
}
|
||||||
|
if self.password.is_empty() {
|
||||||
|
bail!("password is not set and credentials are not in cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
let cred = Credentials::with_password(&self.username, &self.password);
|
||||||
|
|
||||||
|
let (session, _credentials) =
|
||||||
|
Session::connect(SessionConfig::default(), cred, Some(cache), true).await?;
|
||||||
|
|
||||||
|
Ok(session)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn track_id(&self) -> anyhow::Result<SpotifyId> {
|
||||||
|
if self.track.is_empty() {
|
||||||
|
bail!("track is not set");
|
||||||
|
}
|
||||||
|
let track = SpotifyId::from_uri(&self.track).map_err(|_| {
|
||||||
|
anyhow::anyhow!("failed to create Spotify URI from track {}", self.track)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(track)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,7 @@
|
||||||
*/
|
*/
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
|
||||||
|
mod common;
|
||||||
mod spotifyaudiosrc;
|
mod spotifyaudiosrc;
|
||||||
|
|
||||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use std::sync::{mpsc, Arc, Mutex};
|
use std::sync::{mpsc, Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
use anyhow::bail;
|
use futures::future::{AbortHandle, Abortable, Aborted};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::{runtime, task::JoinHandle};
|
use tokio::{runtime, task::JoinHandle};
|
||||||
|
|
||||||
|
@ -17,10 +17,6 @@ use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst_base::subclass::{base_src::CreateSuccess, prelude::*};
|
use gst_base::subclass::{base_src::CreateSuccess, prelude::*};
|
||||||
|
|
||||||
use librespot::core::{
|
|
||||||
cache::Cache, config::SessionConfig, session::Session, spotify_id::SpotifyId,
|
|
||||||
};
|
|
||||||
use librespot::discovery::Credentials;
|
|
||||||
use librespot::playback::{
|
use librespot::playback::{
|
||||||
audio_backend::{Sink, SinkResult},
|
audio_backend::{Sink, SinkResult},
|
||||||
config::PlayerConfig,
|
config::PlayerConfig,
|
||||||
|
@ -30,6 +26,8 @@ use librespot::playback::{
|
||||||
player::{Player, PlayerEvent},
|
player::{Player, PlayerEvent},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::Bitrate;
|
||||||
|
|
||||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
gst::DebugCategory::new(
|
gst::DebugCategory::new(
|
||||||
"spotifyaudiosrc",
|
"spotifyaudiosrc",
|
||||||
|
@ -64,20 +62,22 @@ struct State {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Settings {
|
struct Settings {
|
||||||
username: String,
|
common: crate::common::Settings,
|
||||||
password: String,
|
bitrate: Bitrate,
|
||||||
cache_credentials: String,
|
|
||||||
cache_files: String,
|
|
||||||
cache_max_size: u64,
|
|
||||||
track: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SpotifyAudioSrc {
|
pub struct SpotifyAudioSrc {
|
||||||
|
setup_thread: Mutex<Option<SetupThread>>,
|
||||||
state: Arc<Mutex<Option<State>>>,
|
state: Arc<Mutex<Option<State>>>,
|
||||||
settings: Mutex<Settings>,
|
settings: Mutex<Settings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SetupThread {
|
||||||
|
thread_handle: std::thread::JoinHandle<Result<anyhow::Result<()>, Aborted>>,
|
||||||
|
abort_handle: AbortHandle,
|
||||||
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for SpotifyAudioSrc {
|
impl ObjectSubclass for SpotifyAudioSrc {
|
||||||
const NAME: &'static str = "GstSpotifyAudioSrc";
|
const NAME: &'static str = "GstSpotifyAudioSrc";
|
||||||
|
@ -89,105 +89,39 @@ impl ObjectSubclass for SpotifyAudioSrc {
|
||||||
impl ObjectImpl for SpotifyAudioSrc {
|
impl ObjectImpl for SpotifyAudioSrc {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
vec![glib::ParamSpecString::builder("username")
|
let mut props = crate::common::Settings::properties();
|
||||||
.nick("Username")
|
let default = Settings::default();
|
||||||
.blurb("Spotify device username from https://www.spotify.com/us/account/set-device-password/")
|
|
||||||
.default_value(Some(""))
|
props.push(
|
||||||
|
glib::ParamSpecEnum::builder_with_default::<Bitrate>("bitrate", default.bitrate)
|
||||||
|
.nick("Spotify bitrate")
|
||||||
|
.blurb("Spotify audio bitrate in kbit/s")
|
||||||
.mutable_ready()
|
.mutable_ready()
|
||||||
.build(),
|
.build(),
|
||||||
glib::ParamSpecString::builder("password")
|
);
|
||||||
.nick("Password")
|
props
|
||||||
.blurb("Spotify device password from https://www.spotify.com/us/account/set-device-password/")
|
|
||||||
.default_value(Some(""))
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecString::builder("cache-credentials")
|
|
||||||
.nick("Credentials cache")
|
|
||||||
.blurb("Directory where to cache Spotify credentials")
|
|
||||||
.default_value(Some(""))
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecString::builder("cache-files")
|
|
||||||
.nick("Files cache")
|
|
||||||
.blurb("Directory where to cache downloaded files from Spotify")
|
|
||||||
.default_value(Some(""))
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecUInt64::builder("cache-max-size")
|
|
||||||
.nick("Cache max size")
|
|
||||||
.blurb("The max allowed size of the cache, in bytes, or 0 to disable the cache limit")
|
|
||||||
.default_value(0)
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecString::builder("track")
|
|
||||||
.nick("Spotify URI")
|
|
||||||
.blurb("Spotify track URI, in the form 'spotify:track:$SPOTIFY_ID'")
|
|
||||||
.default_value(Some(""))
|
|
||||||
.mutable_ready()
|
|
||||||
.build(),
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PROPERTIES.as_ref()
|
PROPERTIES.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
|
||||||
match pspec.name() {
|
match pspec.name() {
|
||||||
"username" => {
|
"bitrate" => {
|
||||||
let mut settings = self.settings.lock().unwrap();
|
settings.bitrate = value.get().expect("type checked upstream");
|
||||||
settings.username = value.get().expect("type checked upstream");
|
|
||||||
}
|
}
|
||||||
"password" => {
|
_ => settings.common.set_property(value, pspec),
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.password = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"cache-credentials" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_credentials = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"cache-files" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_files = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"cache-max-size" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_max_size = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
"track" => {
|
|
||||||
let mut settings = self.settings.lock().unwrap();
|
|
||||||
settings.track = value.get().expect("type checked upstream");
|
|
||||||
}
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
|
||||||
match pspec.name() {
|
match pspec.name() {
|
||||||
"username" => {
|
"bitrate" => settings.bitrate.to_value(),
|
||||||
let settings = self.settings.lock().unwrap();
|
_ => settings.common.property(pspec),
|
||||||
settings.username.to_value()
|
|
||||||
}
|
|
||||||
"password" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.password.to_value()
|
|
||||||
}
|
|
||||||
"cache-credentials" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_credentials.to_value()
|
|
||||||
}
|
|
||||||
"cache-files" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_files.to_value()
|
|
||||||
}
|
|
||||||
"cache-max-size" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.cache_max_size.to_value()
|
|
||||||
}
|
|
||||||
"track" => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
settings.track.to_value()
|
|
||||||
}
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,17 +171,22 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = RUNTIME.block_on(async move { self.setup().await }) {
|
{
|
||||||
let details = format!("{:?}", err);
|
let setup_thread = self.setup_thread.lock().unwrap();
|
||||||
gst::error!(CAT, imp: self, "failed to start: {}", details);
|
if setup_thread.is_some() {
|
||||||
gst::element_imp_error!(self, gst::ResourceError::Settings, [&details]);
|
// already starting
|
||||||
return Err(gst::error_msg!(gst::ResourceError::Settings, [&details]));
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.start_setup(setup_thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
|
// stop the setup if it's not completed yet
|
||||||
|
self.cancel_setup();
|
||||||
|
|
||||||
if let Some(state) = self.state.lock().unwrap().take() {
|
if let Some(state) = self.state.lock().unwrap().take() {
|
||||||
gst::debug!(CAT, imp: self, "stopping");
|
gst::debug!(CAT, imp: self, "stopping");
|
||||||
state.player.stop();
|
state.player.stop();
|
||||||
|
@ -258,6 +197,12 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unlock(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
|
self.cancel_setup();
|
||||||
|
|
||||||
|
self.parent_unlock()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PushSrcImpl for SpotifyAudioSrc {
|
impl PushSrcImpl for SpotifyAudioSrc {
|
||||||
|
@ -265,6 +210,40 @@ impl PushSrcImpl for SpotifyAudioSrc {
|
||||||
&self,
|
&self,
|
||||||
_buffer: Option<&mut gst::BufferRef>,
|
_buffer: Option<&mut gst::BufferRef>,
|
||||||
) -> Result<CreateSuccess, gst::FlowError> {
|
) -> Result<CreateSuccess, gst::FlowError> {
|
||||||
|
let state_set = {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
state.is_some()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !state_set {
|
||||||
|
let setup_thread = self.setup_thread.lock().unwrap();
|
||||||
|
if setup_thread.is_none() {
|
||||||
|
// unlock() could potentially cancel the setup, and create() can be called after unlock() without going through start() again.
|
||||||
|
self.start_setup(setup_thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// wait for the setup to be completed
|
||||||
|
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||||
|
if let Some(setup) = setup_thread.take() {
|
||||||
|
let res = setup.thread_handle.join().unwrap();
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Err(_aborted) => {
|
||||||
|
gst::debug!(CAT, imp: self, "setup has been cancelled");
|
||||||
|
return Err(gst::FlowError::Flushing);
|
||||||
|
}
|
||||||
|
Ok(Err(err)) => {
|
||||||
|
gst::error!(CAT, imp: self, "failed to start: {err:?}");
|
||||||
|
gst::element_imp_error!(self, gst::ResourceError::Settings, ["{err:?}"]);
|
||||||
|
return Err(gst::FlowError::Error);
|
||||||
|
}
|
||||||
|
Ok(Ok(_)) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let state = self.state.lock().unwrap();
|
let state = self.state.lock().unwrap();
|
||||||
let state = state.as_ref().unwrap();
|
let state = state.as_ref().unwrap();
|
||||||
|
|
||||||
|
@ -290,112 +269,6 @@ impl PushSrcImpl for SpotifyAudioSrc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpotifyAudioSrc {
|
|
||||||
async fn setup(&self) -> anyhow::Result<()> {
|
|
||||||
let (credentials, cache, track) = {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
|
||||||
|
|
||||||
let credentials_cache = if settings.cache_credentials.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&settings.cache_credentials)
|
|
||||||
};
|
|
||||||
|
|
||||||
let files_cache = if settings.cache_files.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(&settings.cache_files)
|
|
||||||
};
|
|
||||||
|
|
||||||
let max_size = if settings.cache_max_size != 0 {
|
|
||||||
Some(settings.cache_max_size)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let cache = Cache::new(credentials_cache, None, files_cache, max_size)?;
|
|
||||||
|
|
||||||
let credentials = match cache.credentials() {
|
|
||||||
Some(cached_cred) => {
|
|
||||||
gst::debug!(CAT, imp: self, "reuse credentials from cache",);
|
|
||||||
cached_cred
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
gst::debug!(CAT, imp: self, "credentials not in cache",);
|
|
||||||
|
|
||||||
if settings.username.is_empty() {
|
|
||||||
bail!("username is not set and credentials are not in cache");
|
|
||||||
}
|
|
||||||
if settings.password.is_empty() {
|
|
||||||
bail!("password is not set and credentials are not in cache");
|
|
||||||
}
|
|
||||||
|
|
||||||
let cred = Credentials::with_password(&settings.username, &settings.password);
|
|
||||||
cache.save_credentials(&cred);
|
|
||||||
cred
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if settings.track.is_empty() {
|
|
||||||
bail!("track is not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
(credentials, cache, settings.track.clone())
|
|
||||||
};
|
|
||||||
|
|
||||||
let state = self.state.clone();
|
|
||||||
|
|
||||||
let (session, _credentials) =
|
|
||||||
Session::connect(SessionConfig::default(), credentials, Some(cache), false).await?;
|
|
||||||
|
|
||||||
let player_config = PlayerConfig {
|
|
||||||
passthrough: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// use a sync channel to prevent buffering the whole track inside the channel
|
|
||||||
let (sender, receiver) = mpsc::sync_channel(2);
|
|
||||||
let sender_clone = sender.clone();
|
|
||||||
|
|
||||||
let (mut player, mut player_event_channel) =
|
|
||||||
Player::new(player_config, session, Box::new(NoOpVolume), || {
|
|
||||||
Box::new(BufferSink { sender })
|
|
||||||
});
|
|
||||||
|
|
||||||
let track = match SpotifyId::from_uri(&track) {
|
|
||||||
Ok(track) => track,
|
|
||||||
Err(_) => bail!("Failed to create Spotify URI from track"),
|
|
||||||
};
|
|
||||||
|
|
||||||
player.load(track, true, 0);
|
|
||||||
|
|
||||||
let player_channel_handle = RUNTIME.spawn(async move {
|
|
||||||
let sender = sender_clone;
|
|
||||||
|
|
||||||
while let Some(event) = player_event_channel.recv().await {
|
|
||||||
match event {
|
|
||||||
PlayerEvent::EndOfTrack { .. } => {
|
|
||||||
let _ = sender.send(Message::Eos);
|
|
||||||
}
|
|
||||||
PlayerEvent::Unavailable { .. } => {
|
|
||||||
let _ = sender.send(Message::Unavailable);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut state = state.lock().unwrap();
|
|
||||||
state.replace(State {
|
|
||||||
player,
|
|
||||||
receiver,
|
|
||||||
player_channel_handle,
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct BufferSink {
|
struct BufferSink {
|
||||||
sender: mpsc::SyncSender<Message>,
|
sender: mpsc::SyncSender<Message>,
|
||||||
}
|
}
|
||||||
|
@ -425,10 +298,10 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
||||||
fn uri(&self) -> Option<String> {
|
fn uri(&self) -> Option<String> {
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
|
|
||||||
if settings.track.is_empty() {
|
if settings.common.track.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(settings.track.clone())
|
Some(settings.common.track.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -436,7 +309,7 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
||||||
gst::debug!(CAT, imp: self, "set URI: {}", uri);
|
gst::debug!(CAT, imp: self, "set URI: {}", uri);
|
||||||
|
|
||||||
let url = url::Url::parse(uri)
|
let url = url::Url::parse(uri)
|
||||||
.map_err(|e| glib::Error::new(gst::URIError::BadUri, &format!("{:?}", e)))?;
|
.map_err(|e| glib::Error::new(gst::URIError::BadUri, &format!("{e:?}")))?;
|
||||||
|
|
||||||
// allow to configure auth and cache settings from the URI
|
// allow to configure auth and cache settings from the URI
|
||||||
for (key, value) in url.query_pairs() {
|
for (key, value) in url.query_pairs() {
|
||||||
|
@ -456,3 +329,103 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SpotifyAudioSrc {
|
||||||
|
fn start_setup(&self, mut setup_thread: MutexGuard<Option<SetupThread>>) {
|
||||||
|
let self_ = self.to_owned();
|
||||||
|
|
||||||
|
// run the runtime from another thread to prevent the "start a runtime from within a runtime" panic
|
||||||
|
// when the plugin is statically linked.
|
||||||
|
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||||
|
let thread_handle = std::thread::spawn(move || {
|
||||||
|
RUNTIME.block_on(async move {
|
||||||
|
let future = Abortable::new(self_.setup(), abort_registration);
|
||||||
|
future.await
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
setup_thread.replace(SetupThread {
|
||||||
|
thread_handle,
|
||||||
|
abort_handle,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn setup(&self) -> anyhow::Result<()> {
|
||||||
|
{
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
if state.is_some() {
|
||||||
|
// already setup
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = self.obj();
|
||||||
|
|
||||||
|
let (session, track, bitrate) = {
|
||||||
|
let (common, bitrate) = {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
let bitrate = settings.bitrate.into();
|
||||||
|
|
||||||
|
(settings.common.clone(), bitrate)
|
||||||
|
};
|
||||||
|
|
||||||
|
let session = common.connect_session(src.clone(), &CAT).await?;
|
||||||
|
let track = common.track_id()?;
|
||||||
|
gst::debug!(CAT, imp: self, "Requesting bitrate {:?}", bitrate);
|
||||||
|
|
||||||
|
(session, track, bitrate)
|
||||||
|
};
|
||||||
|
|
||||||
|
let player_config = PlayerConfig {
|
||||||
|
passthrough: true,
|
||||||
|
bitrate,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// use a sync channel to prevent buffering the whole track inside the channel
|
||||||
|
let (sender, receiver) = mpsc::sync_channel(2);
|
||||||
|
let sender_clone = sender.clone();
|
||||||
|
|
||||||
|
let (mut player, mut player_event_channel) =
|
||||||
|
Player::new(player_config, session, Box::new(NoOpVolume), || {
|
||||||
|
Box::new(BufferSink { sender })
|
||||||
|
});
|
||||||
|
|
||||||
|
player.load(track, true, 0);
|
||||||
|
|
||||||
|
let player_channel_handle = RUNTIME.spawn(async move {
|
||||||
|
let sender = sender_clone;
|
||||||
|
|
||||||
|
while let Some(event) = player_event_channel.recv().await {
|
||||||
|
match event {
|
||||||
|
PlayerEvent::EndOfTrack { .. } => {
|
||||||
|
let _ = sender.send(Message::Eos);
|
||||||
|
}
|
||||||
|
PlayerEvent::Unavailable { .. } => {
|
||||||
|
let _ = sender.send(Message::Unavailable);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
state.replace(State {
|
||||||
|
player,
|
||||||
|
receiver,
|
||||||
|
player_channel_handle,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_setup(&self) {
|
||||||
|
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(setup) = setup_thread.take() {
|
||||||
|
setup.abort_handle.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -11,15 +11,46 @@ use gst::prelude::*;
|
||||||
|
|
||||||
mod imp;
|
mod imp;
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy, glib::Enum)]
|
||||||
|
#[repr(u32)]
|
||||||
|
#[enum_type(name = "GstRsSpotifyBitrate")]
|
||||||
|
enum Bitrate {
|
||||||
|
#[enum_value(name = "96 kbit/s", nick = "96")]
|
||||||
|
B96,
|
||||||
|
#[enum_value(name = "160 kbit/s", nick = "160")]
|
||||||
|
B160,
|
||||||
|
#[enum_value(name = "320 kbit/s", nick = "320")]
|
||||||
|
B320,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Bitrate {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::B160
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Bitrate> for librespot::playback::config::Bitrate {
|
||||||
|
fn from(value: Bitrate) -> Self {
|
||||||
|
match value {
|
||||||
|
Bitrate::B96 => Self::Bitrate96,
|
||||||
|
Bitrate::B160 => Self::Bitrate160,
|
||||||
|
Bitrate::B320 => Self::Bitrate320,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
pub struct SpotifyAudioSrc(ObjectSubclass<imp::SpotifyAudioSrc>) @extends gst_base::PushSrc, gst_base::BaseSrc, gst::Element, gst::Object, @implements gst::URIHandler;
|
pub struct SpotifyAudioSrc(ObjectSubclass<imp::SpotifyAudioSrc>) @extends gst_base::PushSrc, gst_base::BaseSrc, gst::Element, gst::Object, @implements gst::URIHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
#[cfg(feature = "doc")]
|
||||||
|
Bitrate::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||||
|
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"spotifyaudiosrc",
|
"spotifyaudiosrc",
|
||||||
gst::Rank::Primary,
|
gst::Rank::PRIMARY,
|
||||||
SpotifyAudioSrc::static_type(),
|
SpotifyAudioSrc::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
114
cargo_wrapper.py
114
cargo_wrapper.py
|
@ -7,6 +7,7 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import shlex
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from pathlib import Path as P
|
from pathlib import Path as P
|
||||||
|
|
||||||
|
@ -16,24 +17,44 @@ PARSER.add_argument('build_dir', type=P)
|
||||||
PARSER.add_argument('src_dir', type=P)
|
PARSER.add_argument('src_dir', type=P)
|
||||||
PARSER.add_argument('root_dir', type=P)
|
PARSER.add_argument('root_dir', type=P)
|
||||||
PARSER.add_argument('target', choices=['release', 'debug'])
|
PARSER.add_argument('target', choices=['release', 'debug'])
|
||||||
PARSER.add_argument('include')
|
|
||||||
PARSER.add_argument('extra_env')
|
|
||||||
PARSER.add_argument('prefix', type=P)
|
PARSER.add_argument('prefix', type=P)
|
||||||
PARSER.add_argument('libdir', type=P)
|
PARSER.add_argument('libdir', type=P)
|
||||||
PARSER.add_argument('--version', default=None)
|
PARSER.add_argument('--version', default=None)
|
||||||
PARSER.add_argument('--bin', default=None, type=P)
|
PARSER.add_argument('--bin', default=None, type=P)
|
||||||
PARSER.add_argument('--exts', nargs="+", default=[])
|
PARSER.add_argument('--features', nargs="+", default=[])
|
||||||
|
PARSER.add_argument('--packages', nargs="+", default=[])
|
||||||
|
PARSER.add_argument('--examples', nargs="+", default=[])
|
||||||
|
PARSER.add_argument('--lib-suffixes', nargs="+", default=[])
|
||||||
|
PARSER.add_argument('--exe-suffix')
|
||||||
PARSER.add_argument('--depfile')
|
PARSER.add_argument('--depfile')
|
||||||
PARSER.add_argument('--disable-doc', action="store_true", default=False)
|
PARSER.add_argument('--disable-doc', action="store_true", default=False)
|
||||||
|
|
||||||
|
|
||||||
|
def shlex_join(args):
|
||||||
|
if hasattr(shlex, 'join'):
|
||||||
|
return shlex.join(args)
|
||||||
|
return ' '.join([shlex.quote(arg) for arg in args])
|
||||||
|
|
||||||
|
|
||||||
def generate_depfile_for(fpath):
|
def generate_depfile_for(fpath):
|
||||||
file_stem = fpath.parent / fpath.stem
|
file_stem = fpath.parent / fpath.stem
|
||||||
depfile_content = ""
|
depfile_content = ""
|
||||||
with open(f"{file_stem}.d", 'r') as depfile:
|
with open(f"{file_stem}.d", 'r') as depfile:
|
||||||
for l in depfile.readlines():
|
for l in depfile.readlines():
|
||||||
if l.startswith(str(file_stem)):
|
if l.startswith(str(file_stem)):
|
||||||
output, srcs = l.split(":", maxsplit=2)
|
# We can't blindly split on `:` because on Windows that's part
|
||||||
|
# of the drive letter. Lucky for us, the format of the dep file
|
||||||
|
# is one of:
|
||||||
|
#
|
||||||
|
# /path/to/output: /path/to/src1 /path/to/src2
|
||||||
|
# /path/to/output:
|
||||||
|
#
|
||||||
|
# So we parse these two cases specifically
|
||||||
|
if l.endswith(':'):
|
||||||
|
output = l[:-1]
|
||||||
|
srcs = ''
|
||||||
|
else:
|
||||||
|
output, srcs = l.split(": ", maxsplit=2)
|
||||||
|
|
||||||
all_deps = []
|
all_deps = []
|
||||||
for src in srcs.split(" "):
|
for src in srcs.split(" "):
|
||||||
|
@ -53,33 +74,45 @@ def generate_depfile_for(fpath):
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
opts = PARSER.parse_args()
|
opts = PARSER.parse_args()
|
||||||
|
logdir = opts.root_dir / 'meson-logs'
|
||||||
logfile = open(opts.root_dir / 'meson-logs' /
|
logfile_path = logdir / f'{opts.src_dir.name}-cargo-wrapper.log'
|
||||||
f'{opts.src_dir.name}-cargo-wrapper.log', 'w')
|
logfile = open(logfile_path, mode='w', buffering=1)
|
||||||
|
|
||||||
print(opts, file=logfile)
|
print(opts, file=logfile)
|
||||||
cargo_target_dir = opts.build_dir / 'target'
|
cargo_target_dir = opts.build_dir / 'target'
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['CARGO_TARGET_DIR'] = str(cargo_target_dir)
|
if 'PKG_CONFIG_PATH' in env:
|
||||||
|
pkg_config_path = env['PKG_CONFIG_PATH'].split(os.pathsep)
|
||||||
pkg_config_path = env.get('PKG_CONFIG_PATH', '').split(':')
|
else:
|
||||||
|
pkg_config_path = []
|
||||||
pkg_config_path.append(str(opts.root_dir / 'meson-uninstalled'))
|
pkg_config_path.append(str(opts.root_dir / 'meson-uninstalled'))
|
||||||
env['PKG_CONFIG_PATH'] = ':'.join(pkg_config_path)
|
env['PKG_CONFIG_PATH'] = os.pathsep.join(pkg_config_path)
|
||||||
|
|
||||||
if opts.extra_env:
|
if 'NASM' in env:
|
||||||
for e in opts.extra_env.split(','):
|
env['PATH'] = os.pathsep.join([os.path.dirname(env['NASM']), env['PATH']])
|
||||||
k, v = e.split(':')
|
|
||||||
env[k] = v
|
|
||||||
|
|
||||||
|
rustc_target = None
|
||||||
|
if 'RUSTC' in env:
|
||||||
|
rustc_cmdline = shlex.split(env['RUSTC'])
|
||||||
|
# grab target from RUSTFLAGS
|
||||||
|
rust_flags = rustc_cmdline[1:] + shlex.split(env.get('RUSTFLAGS', ''))
|
||||||
|
if '--target' in rust_flags:
|
||||||
|
rustc_target_idx = rust_flags.index('--target')
|
||||||
|
_ = rust_flags.pop(rustc_target_idx) # drop '--target'
|
||||||
|
rustc_target = rust_flags.pop(rustc_target_idx)
|
||||||
|
env['RUSTFLAGS'] = shlex_join(rust_flags)
|
||||||
|
env['RUSTC'] = rustc_cmdline[0]
|
||||||
|
|
||||||
|
features = opts.features
|
||||||
if opts.command == 'build':
|
if opts.command == 'build':
|
||||||
cargo_cmd = ['cargo']
|
cargo_cmd = ['cargo']
|
||||||
if opts.bin:
|
if opts.bin or opts.examples:
|
||||||
cargo_cmd += ['build']
|
cargo_cmd += ['build']
|
||||||
else:
|
else:
|
||||||
cargo_cmd += ['cbuild']
|
cargo_cmd += ['cbuild']
|
||||||
if not opts.disable_doc:
|
if not opts.disable_doc:
|
||||||
cargo_cmd += ['--features', "doc"]
|
features += ['doc']
|
||||||
if opts.target == 'release':
|
if opts.target == 'release':
|
||||||
cargo_cmd.append('--release')
|
cargo_cmd.append('--release')
|
||||||
elif opts.command == 'test':
|
elif opts.command == 'test':
|
||||||
|
@ -89,40 +122,43 @@ if __name__ == "__main__":
|
||||||
print("Unknown command:", opts.command, file=logfile)
|
print("Unknown command:", opts.command, file=logfile)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
cwd = None
|
if rustc_target:
|
||||||
if not opts.bin:
|
cargo_cmd += ['--target', rustc_target]
|
||||||
cargo_cmd.extend(['--manifest-path', opts.src_dir / 'Cargo.toml'])
|
if features:
|
||||||
cargo_cmd.extend(['--prefix', opts.prefix, '--libdir',
|
cargo_cmd += ['--features', ','.join(features)]
|
||||||
opts.prefix / opts.libdir])
|
cargo_cmd += ['--target-dir', cargo_target_dir]
|
||||||
for p in opts.include.split(','):
|
cargo_cmd += ['--manifest-path', opts.src_dir / 'Cargo.toml']
|
||||||
cargo_cmd.extend(['-p', p])
|
if opts.bin:
|
||||||
else:
|
|
||||||
cargo_cmd.extend(['--bin', opts.bin.name])
|
cargo_cmd.extend(['--bin', opts.bin.name])
|
||||||
cwd = opts.src_dir
|
else:
|
||||||
|
if not opts.examples:
|
||||||
|
cargo_cmd.extend(['--prefix', opts.prefix, '--libdir',
|
||||||
|
opts.prefix / opts.libdir])
|
||||||
|
for p in opts.packages:
|
||||||
|
cargo_cmd.extend(['-p', p])
|
||||||
|
for e in opts.examples:
|
||||||
|
cargo_cmd.extend(['--example', e])
|
||||||
|
|
||||||
def run(cargo_cmd, env, cwd=cwd):
|
def run(cargo_cmd, env):
|
||||||
|
print(cargo_cmd, env, file=logfile)
|
||||||
try:
|
try:
|
||||||
subprocess.run(cargo_cmd, env=env, check=True, cwd=cwd)
|
subprocess.run(cargo_cmd, env=env, cwd=opts.src_dir, check=True)
|
||||||
except subprocess.SubprocessError:
|
except subprocess.SubprocessError:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
run(cargo_cmd, env, cwd)
|
run(cargo_cmd, env)
|
||||||
|
|
||||||
if opts.command == 'build':
|
if opts.command == 'build':
|
||||||
target_dir = cargo_target_dir / '**' / opts.target
|
target_dir = cargo_target_dir / '**' / opts.target
|
||||||
if opts.bin:
|
if opts.bin:
|
||||||
if opts.exts[0]:
|
exe = glob.glob(str(target_dir / opts.bin) + opts.exe_suffix, recursive=True)[0]
|
||||||
ext = f'.{opts.exts[0]}'
|
|
||||||
else:
|
|
||||||
ext = ''
|
|
||||||
exe = glob.glob(str(target_dir / opts.bin) + ext, recursive=True)[0]
|
|
||||||
shutil.copy2(exe, opts.build_dir)
|
shutil.copy2(exe, opts.build_dir)
|
||||||
depfile_content = generate_depfile_for(P(exe))
|
depfile_content = generate_depfile_for(P(exe))
|
||||||
else:
|
else:
|
||||||
# Copy so files to build dir
|
# Copy so files to build dir
|
||||||
depfile_content = ""
|
depfile_content = ""
|
||||||
for ext in opts.exts:
|
for suffix in opts.lib_suffixes:
|
||||||
for f in glob.glob(str(target_dir / f'*.{ext}'), recursive=True):
|
for f in glob.glob(str(target_dir / f'*.{suffix}'), recursive=True):
|
||||||
libfile = P(f)
|
libfile = P(f)
|
||||||
|
|
||||||
depfile_content += generate_depfile_for(libfile)
|
depfile_content += generate_depfile_for(libfile)
|
||||||
|
@ -137,6 +173,12 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
print(f"Copying {copied_file}", file=logfile)
|
print(f"Copying {copied_file}", file=logfile)
|
||||||
shutil.copy2(f, opts.build_dir)
|
shutil.copy2(f, opts.build_dir)
|
||||||
|
# Copy examples to builddir
|
||||||
|
for example in opts.examples:
|
||||||
|
example_glob = str(target_dir / 'examples' / example) + opts.exe_suffix
|
||||||
|
exe = glob.glob(example_glob, recursive=True)[0]
|
||||||
|
shutil.copy2(exe, opts.build_dir)
|
||||||
|
depfile_content += generate_depfile_for(P(exe))
|
||||||
|
|
||||||
with open(opts.depfile, 'w') as depfile:
|
with open(opts.depfile, 'w') as depfile:
|
||||||
depfile.write(depfile_content)
|
depfile.write(depfile_content)
|
||||||
|
|
103
ci/cerbero/trigger_cerbero_pipeline.py
Executable file
103
ci/cerbero/trigger_cerbero_pipeline.py
Executable file
|
@ -0,0 +1,103 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# Copied from gstreamer.git/ci/gitlab/trigger_cerbero_pipeline.py
|
||||||
|
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import gitlab
|
||||||
|
|
||||||
|
CERBERO_PROJECT = 'gstreamer/cerbero'
|
||||||
|
|
||||||
|
|
||||||
|
class Status:
|
||||||
|
FAILED = 'failed'
|
||||||
|
MANUAL = 'manual'
|
||||||
|
CANCELED = 'canceled'
|
||||||
|
SUCCESS = 'success'
|
||||||
|
SKIPPED = 'skipped'
|
||||||
|
CREATED = 'created'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_finished(cls, state):
|
||||||
|
return state in [
|
||||||
|
cls.FAILED,
|
||||||
|
cls.MANUAL,
|
||||||
|
cls.CANCELED,
|
||||||
|
cls.SUCCESS,
|
||||||
|
cls.SKIPPED,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def fprint(msg):
|
||||||
|
print(msg, end="")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = os.environ['CI_SERVER_URL']
|
||||||
|
gl = gitlab.Gitlab(server,
|
||||||
|
private_token=os.environ.get('GITLAB_API_TOKEN'),
|
||||||
|
job_token=os.environ.get('CI_JOB_TOKEN'))
|
||||||
|
|
||||||
|
def get_matching_user_project(project, branch):
|
||||||
|
cerbero = gl.projects.get(project)
|
||||||
|
# Search for matching branches, return only if the branch name matches
|
||||||
|
# exactly
|
||||||
|
for b in cerbero.branches.list(search=cerbero_branch, iterator=True):
|
||||||
|
if branch == b.name:
|
||||||
|
return cerbero
|
||||||
|
return None
|
||||||
|
|
||||||
|
cerbero = None
|
||||||
|
# We do not want to run on (often out of date) user upstream branch
|
||||||
|
if os.environ["CI_COMMIT_REF_NAME"] != os.environ['CERBERO_UPSTREAM_BRANCH']:
|
||||||
|
try:
|
||||||
|
cerbero_name = f'{os.environ["CI_PROJECT_NAMESPACE"]}/cerbero'
|
||||||
|
cerbero_branch = os.environ["CI_COMMIT_REF_NAME"]
|
||||||
|
cerbero = get_matching_user_project(cerbero_name, cerbero_branch)
|
||||||
|
except gitlab.exceptions.GitlabGetError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if cerbero is None:
|
||||||
|
cerbero_name = CERBERO_PROJECT
|
||||||
|
cerbero_branch = os.environ["CERBERO_UPSTREAM_BRANCH"]
|
||||||
|
cerbero = gl.projects.get(cerbero_name)
|
||||||
|
|
||||||
|
fprint(f"-> Triggering on branch {cerbero_branch} in {cerbero_name}\n")
|
||||||
|
|
||||||
|
# CI_PROJECT_URL is not necessarily the project where the branch we need to
|
||||||
|
# build resides, for instance merge request pipelines can be run on
|
||||||
|
# 'gstreamer' namespace. Fetch the branch name in the same way, just in
|
||||||
|
# case it breaks in the future.
|
||||||
|
if 'CI_MERGE_REQUEST_SOURCE_PROJECT_URL' in os.environ:
|
||||||
|
project_url = os.environ['CI_MERGE_REQUEST_SOURCE_PROJECT_URL']
|
||||||
|
project_branch = os.environ['CI_MERGE_REQUEST_SOURCE_BRANCH_NAME']
|
||||||
|
else:
|
||||||
|
project_url = os.environ['CI_PROJECT_URL']
|
||||||
|
project_branch = os.environ['CI_COMMIT_REF_NAME']
|
||||||
|
|
||||||
|
variables = {
|
||||||
|
"CI_GST_PLUGINS_RS_URL": project_url,
|
||||||
|
"CI_GST_PLUGINS_RS_REF_NAME": project_branch,
|
||||||
|
# This tells cerbero CI that this is a pipeline started via the
|
||||||
|
# trigger API, which means it can use a deps cache instead of
|
||||||
|
# building from scratch.
|
||||||
|
"CI_GSTREAMER_TRIGGERED": "true",
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe = cerbero.trigger_pipeline(
|
||||||
|
token=os.environ['CI_JOB_TOKEN'],
|
||||||
|
ref=cerbero_branch,
|
||||||
|
variables=variables,
|
||||||
|
)
|
||||||
|
|
||||||
|
fprint(f'Cerbero pipeline running at {pipe.web_url} ')
|
||||||
|
while True:
|
||||||
|
time.sleep(15)
|
||||||
|
pipe.refresh()
|
||||||
|
if Status.is_finished(pipe.status):
|
||||||
|
fprint(f": {pipe.status}\n")
|
||||||
|
sys.exit(0 if pipe.status == Status.SUCCESS else 1)
|
||||||
|
else:
|
||||||
|
fprint(".")
|
12
ci/check-for-symlinks.sh
Executable file
12
ci/check-for-symlinks.sh
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
LINKS=`find -type l`
|
||||||
|
if test -z $LINKS; then
|
||||||
|
echo "No symlinks found."
|
||||||
|
else
|
||||||
|
echo "===> FOUND SYMLINKS!"
|
||||||
|
echo
|
||||||
|
echo "$LINKS"
|
||||||
|
echo
|
||||||
|
echo "Please replace these with actual files."
|
||||||
|
exit 1;
|
||||||
|
fi
|
14
ci/check-meson-version.sh
Executable file
14
ci/check-meson-version.sh
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
MESON_VERSION=`head -n5 meson.build | grep ' version\s*:' | sed -e "s/.*version\s*:\s*'//" -e "s/',.*//"`
|
||||||
|
CARGO_VERSION=`cat Cargo.toml | grep -A1 workspace.package | grep ^version | sed -e 's/^version = "\(.*\)"/\1/'`
|
||||||
|
|
||||||
|
echo "gst-plugins-rs version (meson.build) : $MESON_VERSION"
|
||||||
|
echo "gst-plugins-rs version (Cargo.toml) : $CARGO_VERSION"
|
||||||
|
|
||||||
|
if test "x$MESON_VERSION" != "x$CARGO_VERSION"; then
|
||||||
|
echo
|
||||||
|
echo "===> Version mismatch between meson.build and Cargo.toml! <==="
|
||||||
|
echo
|
||||||
|
exit 1;
|
||||||
|
fi
|
|
@ -7,7 +7,8 @@ import os
|
||||||
from utils import iterate_plugins
|
from utils import iterate_plugins
|
||||||
|
|
||||||
# the csound version used on ci does not ship a .pc file
|
# the csound version used on ci does not ship a .pc file
|
||||||
IGNORE = ['csound']
|
# threadshare we skip in meson static build as well
|
||||||
|
IGNORE = ['csound', 'threadshare', 'gtk4']
|
||||||
|
|
||||||
outdir = sys.argv[1]
|
outdir = sys.argv[1]
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
RELEASE=1.0.0
|
RELEASE=1.1.0
|
||||||
|
|
||||||
git clone https://code.videolan.org/videolan/dav1d.git --branch $RELEASE
|
git clone https://code.videolan.org/videolan/dav1d.git --branch $RELEASE
|
||||||
cd dav1d
|
cd dav1d
|
||||||
|
|
|
@ -3,4 +3,4 @@ source ./ci/env.sh
|
||||||
set -e
|
set -e
|
||||||
export CARGO_HOME='/usr/local/cargo'
|
export CARGO_HOME='/usr/local/cargo'
|
||||||
|
|
||||||
cargo install cargo-c --version 0.9.12+cargo-0.64
|
cargo install cargo-c --version 0.9.15+cargo-0.67
|
||||||
|
|
|
@ -17,11 +17,18 @@ function Run-Tests {
|
||||||
param (
|
param (
|
||||||
$Features
|
$Features
|
||||||
)
|
)
|
||||||
|
$local_exclude = $exclude_crates;
|
||||||
|
|
||||||
|
# In this case the plugin will pull x11/wayland features
|
||||||
|
# which will fail to build on windows.
|
||||||
|
if (($Features -eq '--all-features') -or ($Features -eq '')) {
|
||||||
|
$local_exclude += @("--exclude", "gst-plugin-gtk4")
|
||||||
|
}
|
||||||
|
|
||||||
Write-Host "Features: $Features"
|
Write-Host "Features: $Features"
|
||||||
Write-Host "Exclude string: $exclude_crates"
|
Write-Host "Exclude string: $local_exclude"
|
||||||
|
|
||||||
cargo build --color=always --workspace $exclude_crates --all-targets $Features
|
cargo build --color=always --workspace $local_exclude --all-targets $Features
|
||||||
|
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Build failed"
|
Write-Host "Build failed"
|
||||||
|
@ -29,7 +36,8 @@ function Run-Tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
$env:G_DEBUG="fatal_warnings"
|
$env:G_DEBUG="fatal_warnings"
|
||||||
cargo test --no-fail-fast --color=always --workspace $exclude_crates --all-targets $Features
|
$env:RUST_BACKTRACE="1"
|
||||||
|
cargo test --no-fail-fast --color=always --workspace $local_exclude --all-targets $Features
|
||||||
|
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Tests failed"
|
Write-Host "Tests failed"
|
||||||
|
|
|
@ -22,6 +22,8 @@ RS_PREFIXED = [
|
||||||
'png',
|
'png',
|
||||||
'tracers',
|
'tracers',
|
||||||
'rtp',
|
'rtp',
|
||||||
|
'rtsp',
|
||||||
|
'inter',
|
||||||
]
|
]
|
||||||
|
|
||||||
OVERRIDE = {
|
OVERRIDE = {
|
||||||
|
|
263
deny.toml
263
deny.toml
|
@ -9,8 +9,7 @@ ignore = [
|
||||||
"RUSTSEC-2021-0059",
|
"RUSTSEC-2021-0059",
|
||||||
"RUSTSEC-2021-0060",
|
"RUSTSEC-2021-0060",
|
||||||
"RUSTSEC-2021-0061",
|
"RUSTSEC-2021-0061",
|
||||||
# https://github.com/chronotope/chrono/issues/499
|
"RUSTSEC-2021-0145",
|
||||||
"RUSTSEC-2020-0071",
|
|
||||||
# sodiumoxide is deprecated
|
# sodiumoxide is deprecated
|
||||||
"RUSTSEC-2021-0137",
|
"RUSTSEC-2021-0137",
|
||||||
]
|
]
|
||||||
|
@ -18,16 +17,10 @@ ignore = [
|
||||||
[licenses]
|
[licenses]
|
||||||
unlicensed = "deny"
|
unlicensed = "deny"
|
||||||
allow = [
|
allow = [
|
||||||
"Apache-2.0",
|
"MPL-2.0",
|
||||||
]
|
]
|
||||||
deny = [
|
default = "deny"
|
||||||
"GPL-1.0",
|
copyleft = "deny"
|
||||||
"GPL-2.0",
|
|
||||||
"GPL-3.0",
|
|
||||||
"AGPL-1.0",
|
|
||||||
"AGPL-3.0",
|
|
||||||
]
|
|
||||||
copyleft = "allow"
|
|
||||||
allow-osi-fsf-free = "either"
|
allow-osi-fsf-free = "either"
|
||||||
confidence-threshold = 0.8
|
confidence-threshold = 0.8
|
||||||
|
|
||||||
|
@ -39,61 +32,29 @@ license-files = [
|
||||||
{ path = "LICENSE", hash = 0xbd0eed23 }
|
{ path = "LICENSE", hash = 0xbd0eed23 }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Allow AGPL3 from dssim-core, which is optionally used in gst-plugin-videofx
|
||||||
|
[[licenses.exceptions]]
|
||||||
|
allow = ["AGPL-3.0"]
|
||||||
|
name = "dssim-core"
|
||||||
|
version = "3.2"
|
||||||
|
|
||||||
|
# Allow LGPL 2.1 for the threadshare plugin as it includes some LGPL code
|
||||||
|
[[licenses.exceptions]]
|
||||||
|
allow = ["LGPL-2.1"]
|
||||||
|
name = "gst-plugin-threadshare"
|
||||||
|
|
||||||
[bans]
|
[bans]
|
||||||
multiple-versions = "deny"
|
multiple-versions = "deny"
|
||||||
highlight = "all"
|
highlight = "all"
|
||||||
wildcards = "allow"
|
wildcards = "allow"
|
||||||
|
|
||||||
# ignore duplicated deps because of chrono, cookie, cookie_store, hyper,
|
|
||||||
# hyperx, reqwest depending on old time
|
|
||||||
# https://github.com/chronotope/chrono/issues/400
|
|
||||||
# https://github.com/pfernie/cookie_store/issues/11
|
|
||||||
# https://github.com/hyperium/hyper/pull/2139
|
|
||||||
# https://github.com/dekellum/hyperx/issues/21
|
|
||||||
# https://github.com/seanmonstar/reqwest/issues/934
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "time"
|
|
||||||
version = "0.1"
|
|
||||||
|
|
||||||
# ignore duplicated textwrap dependency because clap depends on an old version
|
|
||||||
# https://github.com/clap-rs/clap/pull/1994
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "textwrap"
|
|
||||||
version = "0.11"
|
|
||||||
|
|
||||||
# ignore duplicated rustc_version dependency because rav1e depends on an old version
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "rustc_version"
|
|
||||||
version = "0.3"
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "semver"
|
|
||||||
version = "0.11"
|
|
||||||
|
|
||||||
# ignore duplicated system-deps dependency because dav1d depends on an old version
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "system-deps"
|
|
||||||
version = "3"
|
|
||||||
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "version-compare"
|
|
||||||
version = "0.0"
|
|
||||||
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "cfg-expr"
|
|
||||||
version = "0.7"
|
|
||||||
|
|
||||||
# ignore duplicated crc dependency because ffv1 depends on an old version
|
# ignore duplicated crc dependency because ffv1 depends on an old version
|
||||||
# https://github.com/rust-av/ffv1/issues/21
|
# https://github.com/rust-av/ffv1/issues/21
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "1.8"
|
version = "1.8"
|
||||||
|
|
||||||
# ignore duplicated heck dependency because various crates depend on an old version
|
# Ignore various duplicated dependencies because librespot depends on an old versions
|
||||||
[[bans.skip]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.3"
|
|
||||||
|
|
||||||
# ignore duplicated sha-1/digest/block-buffer dependencies because librespot depends on an old version
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.9"
|
version = "0.9"
|
||||||
|
@ -103,17 +64,195 @@ version = "0.9"
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.9"
|
version = "0.9"
|
||||||
|
|
||||||
# ignore duplicated wasi dependency because various crates depends on an old version
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "wasi"
|
name = "env_logger"
|
||||||
version = "0.10"
|
version = "0.9"
|
||||||
|
|
||||||
# ignore duplicated spin dependency because various crates depend on an old version
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "spin"
|
name = "hmac"
|
||||||
|
version = "0.11"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.6"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "multimap"
|
||||||
|
version = "0.8"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "nix"
|
||||||
|
version = "0.23"
|
||||||
|
|
||||||
|
# field-offset and nix depend on an older memoffset
|
||||||
|
# https://github.com/Diggsey/rust-field-offset/pull/23
|
||||||
|
# https://github.com/nix-rust/nix/pull/1885
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "memoffset"
|
||||||
|
version = "0.6"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of hermit-abi
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of base64
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.13"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of socket2
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.4"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of bitflags
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.0"
|
||||||
|
|
||||||
|
# tracing-subscriber depends on an older version of regex-syntax
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.6"
|
||||||
|
|
||||||
|
# publicsuffix depends on an older version of idna
|
||||||
|
# https://github.com/rushmorem/publicsuffix/pull/39
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.3"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of indexmap / hashbrown
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "1.0"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.12"
|
||||||
|
|
||||||
|
# various livekit dependencies depend on an old version of itertools
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.11"
|
||||||
|
|
||||||
|
# various rav1e / dssim-core depend on an old version of itertools
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12"
|
||||||
|
|
||||||
|
# matchers depends on an old version of regex-automata
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.1"
|
||||||
|
|
||||||
|
# Various crates depend on old versions of the windows crates
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of crypto-bigint
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "crypto-bigint"
|
||||||
|
version = "0.4"
|
||||||
|
|
||||||
|
# livekit-api depends on an older version of tokio-tungstenite
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "tokio-tungstenite"
|
||||||
|
version = "0.20"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.20"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of http
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "http"
|
||||||
|
version = "0.2"
|
||||||
|
|
||||||
|
# proc-macro-crate depends on an older version of toml_edit
|
||||||
|
# https://github.com/bkchr/proc-macro-crate/pull/50
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.21"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "winnow"
|
||||||
version = "0.5"
|
version = "0.5"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of heck
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4"
|
||||||
|
|
||||||
|
# Various crates depend on an older version of hyper / reqwest / headers / etc
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "0.14"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "hyper-tls"
|
||||||
|
version = "0.5"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "0.4"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "headers-core"
|
||||||
|
version = "0.2"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "headers"
|
||||||
|
version = "0.3"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.3"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "reqwest"
|
||||||
|
version = "0.11"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "1.0"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "winreg"
|
||||||
|
version = "0.50"
|
||||||
|
|
||||||
|
# The AWS SDK uses old versions of rustls and related crates
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.21"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "rustls-native-certs"
|
||||||
|
version = "0.6"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.101"
|
||||||
|
|
||||||
|
# warp depends on an older version of tokio-tungstenite
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "tokio-tungstenite"
|
||||||
|
version = "0.21"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.21"
|
||||||
|
|
||||||
[sources]
|
[sources]
|
||||||
unknown-registry = "deny"
|
unknown-registry = "deny"
|
||||||
unknown-git = "deny"
|
unknown-git = "deny"
|
||||||
|
|
|
@ -19,6 +19,8 @@ except ImportError:
|
||||||
PARSER = ArgumentParser()
|
PARSER = ArgumentParser()
|
||||||
PARSER.add_argument('src_dir', type=Path)
|
PARSER.add_argument('src_dir', type=Path)
|
||||||
PARSER.add_argument('plugins', nargs='*')
|
PARSER.add_argument('plugins', nargs='*')
|
||||||
|
PARSER.add_argument('--features', action="store_true", help="Get list of features to activate")
|
||||||
|
PARSER.add_argument('--gst-version', help="Get list of features to activate")
|
||||||
|
|
||||||
|
|
||||||
# Map plugin name to directory name, for those that does not match.
|
# Map plugin name to directory name, for those that does not match.
|
||||||
|
@ -27,6 +29,7 @@ RENAMES = {
|
||||||
'rsfile': 'file',
|
'rsfile': 'file',
|
||||||
'rsflv': 'flavors',
|
'rsflv': 'flavors',
|
||||||
'rsrtp': 'rtp',
|
'rsrtp': 'rtp',
|
||||||
|
'rsrtsp': 'rtsp',
|
||||||
'rswebp': 'webp',
|
'rswebp': 'webp',
|
||||||
'rsonvif': 'onvif',
|
'rsonvif': 'onvif',
|
||||||
'rstracers': 'tracers',
|
'rstracers': 'tracers',
|
||||||
|
@ -34,31 +37,78 @@ RENAMES = {
|
||||||
'rswebrtc': 'webrtc',
|
'rswebrtc': 'webrtc',
|
||||||
'rspng': 'png',
|
'rspng': 'png',
|
||||||
'rsvideofx': 'videofx',
|
'rsvideofx': 'videofx',
|
||||||
|
'rsinter': 'inter',
|
||||||
'textahead': 'ahead',
|
'textahead': 'ahead',
|
||||||
'textwrap': 'wrap',
|
'textwrap': 'wrap',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CargoAnalyzer:
|
||||||
|
def __init__(self):
|
||||||
|
self.src_dir = None
|
||||||
|
self.plugins = None
|
||||||
|
self.features = False
|
||||||
|
self.gst_version = "1.18"
|
||||||
|
|
||||||
|
def extract_version(self, feature_name):
|
||||||
|
if feature_name.startswith('v'):
|
||||||
|
verindex = 1
|
||||||
|
elif feature_name.startswith('gst'):
|
||||||
|
verindex = 3
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
(majver, minver) = feature_name[verindex:].split("_")
|
||||||
|
return (int(majver), int(minver))
|
||||||
|
|
||||||
|
def extract_features(self, cargo_data):
|
||||||
|
features = cargo_data['features']
|
||||||
|
wanted_features = set()
|
||||||
|
gst_version_major = int(self.gst_version.split('.')[0])
|
||||||
|
gst_version_minor = int(self.gst_version.split('.')[1])
|
||||||
|
for (name, value) in features.items():
|
||||||
|
version = self.extract_version(name)
|
||||||
|
|
||||||
|
if version is None:
|
||||||
|
continue
|
||||||
|
(majver, minver) = version
|
||||||
|
|
||||||
|
if gst_version_major < majver or gst_version_minor < minver:
|
||||||
|
continue
|
||||||
|
wanted_features |= set(value)
|
||||||
|
if name.startswith("gst"):
|
||||||
|
# Required for some reason for rswebrtc which has a specific feature
|
||||||
|
wanted_features |= {f"{cargo_data['package']['name']}/{name}"}
|
||||||
|
|
||||||
|
return wanted_features
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
with (opts.src_dir / 'Cargo.toml').open('rb') as f:
|
||||||
|
crates = tomllib.load(f)['workspace']['members']
|
||||||
|
res = set()
|
||||||
|
for name in opts.plugins:
|
||||||
|
if name.startswith('gst'):
|
||||||
|
name = name[3:]
|
||||||
|
|
||||||
|
name = RENAMES.get(name, name)
|
||||||
|
crate_path = None
|
||||||
|
for crate in crates:
|
||||||
|
if Path(crate).name == name:
|
||||||
|
crate_path = opts.src_dir / crate / 'Cargo.toml'
|
||||||
|
assert crate_path
|
||||||
|
with crate_path.open('rb') as f:
|
||||||
|
data = tomllib.load(f)
|
||||||
|
if opts.features:
|
||||||
|
res |= self.extract_features(data)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
requires = data['package']['metadata']['capi']['pkg_config']['requires_private']
|
||||||
|
except KeyError:
|
||||||
|
continue
|
||||||
|
res.update([i.strip().replace('>', "|>").replace('<', "|<").replace("==", "|==") for i in requires.split(',')])
|
||||||
|
return res
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
opts = PARSER.parse_args()
|
analyzer = CargoAnalyzer()
|
||||||
|
opts = PARSER.parse_args(namespace=analyzer)
|
||||||
|
|
||||||
with (opts.src_dir / 'Cargo.toml').open('rb') as f:
|
print(','.join(analyzer.run()))
|
||||||
crates = tomllib.load(f)['workspace']['members']
|
|
||||||
deps = set()
|
|
||||||
for p in opts.plugins:
|
|
||||||
assert p.startswith('gst')
|
|
||||||
name = p[3:]
|
|
||||||
name = RENAMES.get(name, name)
|
|
||||||
crate_path = None
|
|
||||||
for crate in crates:
|
|
||||||
if Path(crate).name == name:
|
|
||||||
crate_path = opts.src_dir / crate / 'Cargo.toml'
|
|
||||||
assert crate_path
|
|
||||||
with crate_path.open('rb') as f:
|
|
||||||
data = tomllib.load(f)
|
|
||||||
try:
|
|
||||||
requires = data['package']['metadata']['capi']['pkg_config']['requires_private']
|
|
||||||
except KeyError:
|
|
||||||
continue
|
|
||||||
deps.update([i.strip().replace('>', "|>").replace('<', "|<").replace("==", "|==") for i in requires.split(',')])
|
|
||||||
print(','.join(deps))
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
build_hotdoc = false
|
build_hotdoc = false
|
||||||
|
|
||||||
|
if get_option('doc').disabled()
|
||||||
|
subdir_done()
|
||||||
|
endif
|
||||||
|
|
||||||
if meson.is_cross_build()
|
if meson.is_cross_build()
|
||||||
if get_option('doc').enabled()
|
if get_option('doc').enabled()
|
||||||
error('Documentation enabled but building the doc while cross building is not supported yet.')
|
error('Documentation enabled but building the doc while cross building is not supported yet.')
|
||||||
|
@ -9,7 +13,7 @@ if meson.is_cross_build()
|
||||||
subdir_done()
|
subdir_done()
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if static_build
|
if default_library == 'static'
|
||||||
if get_option('doc').enabled()
|
if get_option('doc').enabled()
|
||||||
error('Documentation enabled but not supported when building statically.')
|
error('Documentation enabled but not supported when building statically.')
|
||||||
endif
|
endif
|
||||||
|
@ -99,9 +103,17 @@ foreach plugin_name: list_plugin_res.stdout().split(':')
|
||||||
gst_index: 'plugins/index.md',
|
gst_index: 'plugins/index.md',
|
||||||
include_paths: join_paths(meson.current_source_dir(), '..'),
|
include_paths: join_paths(meson.current_source_dir(), '..'),
|
||||||
gst_smart_index: true,
|
gst_smart_index: true,
|
||||||
|
gst_c_source_filters: [
|
||||||
|
'../target/*/*.rs',
|
||||||
|
'../target/*/*/*.rs',
|
||||||
|
'../target/*/*/*/*.rs',
|
||||||
|
'../target/*/*/*/*/*.rs',
|
||||||
|
'../target/*/*/*/*/*/*.rs',
|
||||||
|
],
|
||||||
gst_c_sources: [
|
gst_c_sources: [
|
||||||
'../*/*/*/*.rs',
|
'../*/*/*/*.rs',
|
||||||
'../*/*/*/*/*.rs',
|
'../*/*/*/*/*.rs',
|
||||||
|
'../*/*/*/*/*/*.rs',
|
||||||
],
|
],
|
||||||
dependencies: [gst_dep],
|
dependencies: [gst_dep],
|
||||||
gst_order_generated_subpages: true,
|
gst_order_generated_subpages: true,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,18 +1,18 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-file"
|
name = "gst-plugin-file"
|
||||||
version = "0.9.0-alpha.1"
|
version.workspace = true
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository.workspace = true
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
description = "GStreamer Rust File Source/Sink Plugin"
|
description = "GStreamer Rust File Source/Sink Plugin"
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.63"
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
url = "2"
|
url = "2"
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst.workspace = true
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-base.workspace = true
|
||||||
once_cell = "1.0"
|
once_cell.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstrsfile"
|
name = "gstrsfile"
|
||||||
|
@ -20,7 +20,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
@ -28,7 +28,7 @@ capi = []
|
||||||
doc = ["gst/v1_18"]
|
doc = ["gst/v1_18"]
|
||||||
|
|
||||||
[package.metadata.capi]
|
[package.metadata.capi]
|
||||||
min_version = "0.8.0"
|
min_version = "0.9.21"
|
||||||
|
|
||||||
[package.metadata.capi.header]
|
[package.metadata.capi.header]
|
||||||
enabled = false
|
enabled = false
|
||||||
|
@ -36,6 +36,7 @@ enabled = false
|
||||||
[package.metadata.capi.library]
|
[package.metadata.capi.library]
|
||||||
install_subdir = "gstreamer-1.0"
|
install_subdir = "gstreamer-1.0"
|
||||||
versioning = false
|
versioning = false
|
||||||
|
import_library = false
|
||||||
|
|
||||||
[package.metadata.capi.pkg_config]
|
[package.metadata.capi.pkg_config]
|
||||||
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-APACHE
|
|
201
generic/file/LICENSE-APACHE
Normal file
201
generic/file/LICENSE-APACHE
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -1 +0,0 @@
|
||||||
../../LICENSE-MIT
|
|
23
generic/file/LICENSE-MIT
Normal file
23
generic/file/LICENSE-MIT
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
|
@ -33,14 +33,14 @@ impl FileLocation {
|
||||||
if url.scheme() != "file" {
|
if url.scheme() != "file" {
|
||||||
return Err(glib::Error::new(
|
return Err(glib::Error::new(
|
||||||
gst::URIError::UnsupportedProtocol,
|
gst::URIError::UnsupportedProtocol,
|
||||||
format!("Unsupported URI {}", uri_str).as_str(),
|
format!("Unsupported URI {uri_str}").as_str(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let path = url.to_file_path().map_err(|_| {
|
let path = url.to_file_path().map_err(|_| {
|
||||||
glib::Error::new(
|
glib::Error::new(
|
||||||
gst::URIError::BadUri,
|
gst::URIError::BadUri,
|
||||||
format!("Unsupported URI {}", uri_str).as_str(),
|
format!("Unsupported URI {uri_str}").as_str(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ impl FileLocation {
|
||||||
}
|
}
|
||||||
Err(err) => Err(glib::Error::new(
|
Err(err) => Err(glib::Error::new(
|
||||||
gst::URIError::BadUri,
|
gst::URIError::BadUri,
|
||||||
format!("Couldn't parse URI {}: {}", uri_str, err).as_str(),
|
format!("Couldn't parse URI {uri_str}: {err}").as_str(),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,14 +57,14 @@ impl FileLocation {
|
||||||
let location_str = location.to_str().ok_or_else(|| {
|
let location_str = location.to_str().ok_or_else(|| {
|
||||||
glib::Error::new(
|
glib::Error::new(
|
||||||
gst::URIError::BadReference,
|
gst::URIError::BadReference,
|
||||||
format!("Invalid path {:?}", location).as_str(),
|
format!("Invalid path {location:?}").as_str(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let file_name = location.file_name().ok_or_else(|| {
|
let file_name = location.file_name().ok_or_else(|| {
|
||||||
glib::Error::new(
|
glib::Error::new(
|
||||||
gst::URIError::BadReference,
|
gst::URIError::BadReference,
|
||||||
format!("Expected a path with a filename, got {}", location_str,).as_str(),
|
format!("Expected a path with a filename, got {location_str}",).as_str(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ impl FileLocation {
|
||||||
.parent()
|
.parent()
|
||||||
.expect("FileSink::set_location `location` with filename but without a parent")
|
.expect("FileSink::set_location `location` with filename but without a parent")
|
||||||
.to_owned();
|
.to_owned();
|
||||||
if parent_dir.is_relative() && parent_dir.components().next() == None {
|
if parent_dir.is_relative() && parent_dir.components().next().is_none() {
|
||||||
// `location` only contains the filename
|
// `location` only contains the filename
|
||||||
// need to specify "." for `canonicalize` to resolve the actual path
|
// need to specify "." for `canonicalize` to resolve the actual path
|
||||||
parent_dir = PathBuf::from(".");
|
parent_dir = PathBuf::from(".");
|
||||||
|
@ -83,7 +83,7 @@ impl FileLocation {
|
||||||
let parent_canonical = parent_dir.canonicalize().map_err(|err| {
|
let parent_canonical = parent_dir.canonicalize().map_err(|err| {
|
||||||
glib::Error::new(
|
glib::Error::new(
|
||||||
gst::URIError::BadReference,
|
gst::URIError::BadReference,
|
||||||
format!("Could not resolve path {}: {}", location_str, err,).as_str(),
|
format!("Could not resolve path {location_str}: {err}",).as_str(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ impl FileLocation {
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
glib::Error::new(
|
glib::Error::new(
|
||||||
gst::URIError::BadReference,
|
gst::URIError::BadReference,
|
||||||
format!("Could not resolve path to URL {}", location_str).as_str(),
|
format!("Could not resolve path to URL {location_str}").as_str(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.map(|_| FileLocation(location_canonical))
|
.map(|_| FileLocation(location_canonical))
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
|
use gst_base::prelude::*;
|
||||||
use gst_base::subclass::prelude::*;
|
use gst_base::subclass::prelude::*;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
@ -38,15 +39,14 @@ impl Default for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
enum State {
|
enum State {
|
||||||
|
#[default]
|
||||||
Stopped,
|
Stopped,
|
||||||
Started { file: File, position: u64 },
|
Started {
|
||||||
}
|
file: File,
|
||||||
|
position: u64,
|
||||||
impl Default for State {
|
},
|
||||||
fn default() -> State {
|
|
||||||
State::Stopped
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -112,6 +112,12 @@ impl ObjectSubclass for FileSink {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for FileSink {
|
impl ObjectImpl for FileSink {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
self.obj().set_sync(false);
|
||||||
|
}
|
||||||
|
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
vec![glib::ParamSpecString::builder("location")
|
vec![glib::ParamSpecString::builder("location")
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"rsfilesink",
|
"rsfilesink",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
FileSink::static_type(),
|
FileSink::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,15 +38,14 @@ impl Default for Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
enum State {
|
enum State {
|
||||||
|
#[default]
|
||||||
Stopped,
|
Stopped,
|
||||||
Started { file: File, position: u64 },
|
Started {
|
||||||
}
|
file: File,
|
||||||
|
position: u64,
|
||||||
impl Default for State {
|
},
|
||||||
fn default() -> State {
|
|
||||||
State::Stopped
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -80,14 +79,14 @@ impl FileSrc {
|
||||||
if !location.exists() {
|
if !location.exists() {
|
||||||
return Err(glib::Error::new(
|
return Err(glib::Error::new(
|
||||||
gst::URIError::BadReference,
|
gst::URIError::BadReference,
|
||||||
format!("{} doesn't exist", location).as_str(),
|
format!("{location} doesn't exist").as_str(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !location.is_file() {
|
if !location.is_file() {
|
||||||
return Err(glib::Error::new(
|
return Err(glib::Error::new(
|
||||||
gst::URIError::BadReference,
|
gst::URIError::BadReference,
|
||||||
format!("{} is not a file", location).as_str(),
|
format!("{location} is not a file").as_str(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"rsfilesrc",
|
"rsfilesrc",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
FileSrc::static_type(),
|
FileSrc::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
44
generic/gopbuffer/Cargo.toml
Normal file
44
generic/gopbuffer/Cargo.toml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
[package]
|
||||||
|
name = "gst-plugin-gopbuffer"
|
||||||
|
version.workspace = true
|
||||||
|
authors = ["Matthew Waters <matthew@centricular.com>"]
|
||||||
|
license = "MPL-2.0"
|
||||||
|
description = "Store complete groups of pictures at a time"
|
||||||
|
repository.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
gst = { workspace = true, features = ["v1_18"] }
|
||||||
|
gst-video = { workspace = true, features = ["v1_18"] }
|
||||||
|
once_cell.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "gstgopbuffer"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
gst-app = { workspace = true, features = ["v1_18"] }
|
||||||
|
gst-check = { workspace = true, features = ["v1_18"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
gst-plugin-version-helper = { path="../../version-helper" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
static = []
|
||||||
|
capi = []
|
||||||
|
|
||||||
|
[package.metadata.capi]
|
||||||
|
min_version = "0.8.0"
|
||||||
|
|
||||||
|
[package.metadata.capi.header]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[package.metadata.capi.library]
|
||||||
|
install_subdir = "gstreamer-1.0"
|
||||||
|
versioning = false
|
||||||
|
|
||||||
|
[package.metadata.capi.pkg_config]
|
||||||
|
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-audio-1.0, gstreamer-video-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
373
generic/gopbuffer/LICENSE-MPL-2.0
Normal file
373
generic/gopbuffer/LICENSE-MPL-2.0
Normal file
|
@ -0,0 +1,373 @@
|
||||||
|
Mozilla Public License Version 2.0
|
||||||
|
==================================
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
--------------
|
||||||
|
|
||||||
|
1.1. "Contributor"
|
||||||
|
means each individual or legal entity that creates, contributes to
|
||||||
|
the creation of, or owns Covered Software.
|
||||||
|
|
||||||
|
1.2. "Contributor Version"
|
||||||
|
means the combination of the Contributions of others (if any) used
|
||||||
|
by a Contributor and that particular Contributor's Contribution.
|
||||||
|
|
||||||
|
1.3. "Contribution"
|
||||||
|
means Covered Software of a particular Contributor.
|
||||||
|
|
||||||
|
1.4. "Covered Software"
|
||||||
|
means Source Code Form to which the initial Contributor has attached
|
||||||
|
the notice in Exhibit A, the Executable Form of such Source Code
|
||||||
|
Form, and Modifications of such Source Code Form, in each case
|
||||||
|
including portions thereof.
|
||||||
|
|
||||||
|
1.5. "Incompatible With Secondary Licenses"
|
||||||
|
means
|
||||||
|
|
||||||
|
(a) that the initial Contributor has attached the notice described
|
||||||
|
in Exhibit B to the Covered Software; or
|
||||||
|
|
||||||
|
(b) that the Covered Software was made available under the terms of
|
||||||
|
version 1.1 or earlier of the License, but not also under the
|
||||||
|
terms of a Secondary License.
|
||||||
|
|
||||||
|
1.6. "Executable Form"
|
||||||
|
means any form of the work other than Source Code Form.
|
||||||
|
|
||||||
|
1.7. "Larger Work"
|
||||||
|
means a work that combines Covered Software with other material, in
|
||||||
|
a separate file or files, that is not Covered Software.
|
||||||
|
|
||||||
|
1.8. "License"
|
||||||
|
means this document.
|
||||||
|
|
||||||
|
1.9. "Licensable"
|
||||||
|
means having the right to grant, to the maximum extent possible,
|
||||||
|
whether at the time of the initial grant or subsequently, any and
|
||||||
|
all of the rights conveyed by this License.
|
||||||
|
|
||||||
|
1.10. "Modifications"
|
||||||
|
means any of the following:
|
||||||
|
|
||||||
|
(a) any file in Source Code Form that results from an addition to,
|
||||||
|
deletion from, or modification of the contents of Covered
|
||||||
|
Software; or
|
||||||
|
|
||||||
|
(b) any new file in Source Code Form that contains any Covered
|
||||||
|
Software.
|
||||||
|
|
||||||
|
1.11. "Patent Claims" of a Contributor
|
||||||
|
means any patent claim(s), including without limitation, method,
|
||||||
|
process, and apparatus claims, in any patent Licensable by such
|
||||||
|
Contributor that would be infringed, but for the grant of the
|
||||||
|
License, by the making, using, selling, offering for sale, having
|
||||||
|
made, import, or transfer of either its Contributions or its
|
||||||
|
Contributor Version.
|
||||||
|
|
||||||
|
1.12. "Secondary License"
|
||||||
|
means either the GNU General Public License, Version 2.0, the GNU
|
||||||
|
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||||
|
Public License, Version 3.0, or any later versions of those
|
||||||
|
licenses.
|
||||||
|
|
||||||
|
1.13. "Source Code Form"
|
||||||
|
means the form of the work preferred for making modifications.
|
||||||
|
|
||||||
|
1.14. "You" (or "Your")
|
||||||
|
means an individual or a legal entity exercising rights under this
|
||||||
|
License. For legal entities, "You" includes any entity that
|
||||||
|
controls, is controlled by, or is under common control with You. For
|
||||||
|
purposes of this definition, "control" means (a) the power, direct
|
||||||
|
or indirect, to cause the direction or management of such entity,
|
||||||
|
whether by contract or otherwise, or (b) ownership of more than
|
||||||
|
fifty percent (50%) of the outstanding shares or beneficial
|
||||||
|
ownership of such entity.
|
||||||
|
|
||||||
|
2. License Grants and Conditions
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
2.1. Grants
|
||||||
|
|
||||||
|
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||||
|
non-exclusive license:
|
||||||
|
|
||||||
|
(a) under intellectual property rights (other than patent or trademark)
|
||||||
|
Licensable by such Contributor to use, reproduce, make available,
|
||||||
|
modify, display, perform, distribute, and otherwise exploit its
|
||||||
|
Contributions, either on an unmodified basis, with Modifications, or
|
||||||
|
as part of a Larger Work; and
|
||||||
|
|
||||||
|
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||||
|
for sale, have made, import, and otherwise transfer either its
|
||||||
|
Contributions or its Contributor Version.
|
||||||
|
|
||||||
|
2.2. Effective Date
|
||||||
|
|
||||||
|
The licenses granted in Section 2.1 with respect to any Contribution
|
||||||
|
become effective for each Contribution on the date the Contributor first
|
||||||
|
distributes such Contribution.
|
||||||
|
|
||||||
|
2.3. Limitations on Grant Scope
|
||||||
|
|
||||||
|
The licenses granted in this Section 2 are the only rights granted under
|
||||||
|
this License. No additional rights or licenses will be implied from the
|
||||||
|
distribution or licensing of Covered Software under this License.
|
||||||
|
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||||
|
Contributor:
|
||||||
|
|
||||||
|
(a) for any code that a Contributor has removed from Covered Software;
|
||||||
|
or
|
||||||
|
|
||||||
|
(b) for infringements caused by: (i) Your and any other third party's
|
||||||
|
modifications of Covered Software, or (ii) the combination of its
|
||||||
|
Contributions with other software (except as part of its Contributor
|
||||||
|
Version); or
|
||||||
|
|
||||||
|
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||||
|
its Contributions.
|
||||||
|
|
||||||
|
This License does not grant any rights in the trademarks, service marks,
|
||||||
|
or logos of any Contributor (except as may be necessary to comply with
|
||||||
|
the notice requirements in Section 3.4).
|
||||||
|
|
||||||
|
2.4. Subsequent Licenses
|
||||||
|
|
||||||
|
No Contributor makes additional grants as a result of Your choice to
|
||||||
|
distribute the Covered Software under a subsequent version of this
|
||||||
|
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||||
|
permitted under the terms of Section 3.3).
|
||||||
|
|
||||||
|
2.5. Representation
|
||||||
|
|
||||||
|
Each Contributor represents that the Contributor believes its
|
||||||
|
Contributions are its original creation(s) or it has sufficient rights
|
||||||
|
to grant the rights to its Contributions conveyed by this License.
|
||||||
|
|
||||||
|
2.6. Fair Use
|
||||||
|
|
||||||
|
This License is not intended to limit any rights You have under
|
||||||
|
applicable copyright doctrines of fair use, fair dealing, or other
|
||||||
|
equivalents.
|
||||||
|
|
||||||
|
2.7. Conditions
|
||||||
|
|
||||||
|
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||||
|
in Section 2.1.
|
||||||
|
|
||||||
|
3. Responsibilities
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
3.1. Distribution of Source Form
|
||||||
|
|
||||||
|
All distribution of Covered Software in Source Code Form, including any
|
||||||
|
Modifications that You create or to which You contribute, must be under
|
||||||
|
the terms of this License. You must inform recipients that the Source
|
||||||
|
Code Form of the Covered Software is governed by the terms of this
|
||||||
|
License, and how they can obtain a copy of this License. You may not
|
||||||
|
attempt to alter or restrict the recipients' rights in the Source Code
|
||||||
|
Form.
|
||||||
|
|
||||||
|
3.2. Distribution of Executable Form
|
||||||
|
|
||||||
|
If You distribute Covered Software in Executable Form then:
|
||||||
|
|
||||||
|
(a) such Covered Software must also be made available in Source Code
|
||||||
|
Form, as described in Section 3.1, and You must inform recipients of
|
||||||
|
the Executable Form how they can obtain a copy of such Source Code
|
||||||
|
Form by reasonable means in a timely manner, at a charge no more
|
||||||
|
than the cost of distribution to the recipient; and
|
||||||
|
|
||||||
|
(b) You may distribute such Executable Form under the terms of this
|
||||||
|
License, or sublicense it under different terms, provided that the
|
||||||
|
license for the Executable Form does not attempt to limit or alter
|
||||||
|
the recipients' rights in the Source Code Form under this License.
|
||||||
|
|
||||||
|
3.3. Distribution of a Larger Work
|
||||||
|
|
||||||
|
You may create and distribute a Larger Work under terms of Your choice,
|
||||||
|
provided that You also comply with the requirements of this License for
|
||||||
|
the Covered Software. If the Larger Work is a combination of Covered
|
||||||
|
Software with a work governed by one or more Secondary Licenses, and the
|
||||||
|
Covered Software is not Incompatible With Secondary Licenses, this
|
||||||
|
License permits You to additionally distribute such Covered Software
|
||||||
|
under the terms of such Secondary License(s), so that the recipient of
|
||||||
|
the Larger Work may, at their option, further distribute the Covered
|
||||||
|
Software under the terms of either this License or such Secondary
|
||||||
|
License(s).
|
||||||
|
|
||||||
|
3.4. Notices
|
||||||
|
|
||||||
|
You may not remove or alter the substance of any license notices
|
||||||
|
(including copyright notices, patent notices, disclaimers of warranty,
|
||||||
|
or limitations of liability) contained within the Source Code Form of
|
||||||
|
the Covered Software, except that You may alter any license notices to
|
||||||
|
the extent required to remedy known factual inaccuracies.
|
||||||
|
|
||||||
|
3.5. Application of Additional Terms
|
||||||
|
|
||||||
|
You may choose to offer, and to charge a fee for, warranty, support,
|
||||||
|
indemnity or liability obligations to one or more recipients of Covered
|
||||||
|
Software. However, You may do so only on Your own behalf, and not on
|
||||||
|
behalf of any Contributor. You must make it absolutely clear that any
|
||||||
|
such warranty, support, indemnity, or liability obligation is offered by
|
||||||
|
You alone, and You hereby agree to indemnify every Contributor for any
|
||||||
|
liability incurred by such Contributor as a result of warranty, support,
|
||||||
|
indemnity or liability terms You offer. You may include additional
|
||||||
|
disclaimers of warranty and limitations of liability specific to any
|
||||||
|
jurisdiction.
|
||||||
|
|
||||||
|
4. Inability to Comply Due to Statute or Regulation
|
||||||
|
---------------------------------------------------
|
||||||
|
|
||||||
|
If it is impossible for You to comply with any of the terms of this
|
||||||
|
License with respect to some or all of the Covered Software due to
|
||||||
|
statute, judicial order, or regulation then You must: (a) comply with
|
||||||
|
the terms of this License to the maximum extent possible; and (b)
|
||||||
|
describe the limitations and the code they affect. Such description must
|
||||||
|
be placed in a text file included with all distributions of the Covered
|
||||||
|
Software under this License. Except to the extent prohibited by statute
|
||||||
|
or regulation, such description must be sufficiently detailed for a
|
||||||
|
recipient of ordinary skill to be able to understand it.
|
||||||
|
|
||||||
|
5. Termination
|
||||||
|
--------------
|
||||||
|
|
||||||
|
5.1. The rights granted under this License will terminate automatically
|
||||||
|
if You fail to comply with any of its terms. However, if You become
|
||||||
|
compliant, then the rights granted under this License from a particular
|
||||||
|
Contributor are reinstated (a) provisionally, unless and until such
|
||||||
|
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||||
|
ongoing basis, if such Contributor fails to notify You of the
|
||||||
|
non-compliance by some reasonable means prior to 60 days after You have
|
||||||
|
come back into compliance. Moreover, Your grants from a particular
|
||||||
|
Contributor are reinstated on an ongoing basis if such Contributor
|
||||||
|
notifies You of the non-compliance by some reasonable means, this is the
|
||||||
|
first time You have received notice of non-compliance with this License
|
||||||
|
from such Contributor, and You become compliant prior to 30 days after
|
||||||
|
Your receipt of the notice.
|
||||||
|
|
||||||
|
5.2. If You initiate litigation against any entity by asserting a patent
|
||||||
|
infringement claim (excluding declaratory judgment actions,
|
||||||
|
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||||
|
directly or indirectly infringes any patent, then the rights granted to
|
||||||
|
You by any and all Contributors for the Covered Software under Section
|
||||||
|
2.1 of this License shall terminate.
|
||||||
|
|
||||||
|
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||||
|
end user license agreements (excluding distributors and resellers) which
|
||||||
|
have been validly granted by You or Your distributors under this License
|
||||||
|
prior to termination shall survive termination.
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 6. Disclaimer of Warranty *
|
||||||
|
* ------------------------- *
|
||||||
|
* *
|
||||||
|
* Covered Software is provided under this License on an "as is" *
|
||||||
|
* basis, without warranty of any kind, either expressed, implied, or *
|
||||||
|
* statutory, including, without limitation, warranties that the *
|
||||||
|
* Covered Software is free of defects, merchantable, fit for a *
|
||||||
|
* particular purpose or non-infringing. The entire risk as to the *
|
||||||
|
* quality and performance of the Covered Software is with You. *
|
||||||
|
* Should any Covered Software prove defective in any respect, You *
|
||||||
|
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||||
|
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||||
|
* essential part of this License. No use of any Covered Software is *
|
||||||
|
* authorized under this License except under this disclaimer. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
************************************************************************
|
||||||
|
* *
|
||||||
|
* 7. Limitation of Liability *
|
||||||
|
* -------------------------- *
|
||||||
|
* *
|
||||||
|
* Under no circumstances and under no legal theory, whether tort *
|
||||||
|
* (including negligence), contract, or otherwise, shall any *
|
||||||
|
* Contributor, or anyone who distributes Covered Software as *
|
||||||
|
* permitted above, be liable to You for any direct, indirect, *
|
||||||
|
* special, incidental, or consequential damages of any character *
|
||||||
|
* including, without limitation, damages for lost profits, loss of *
|
||||||
|
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||||
|
* and all other commercial damages or losses, even if such party *
|
||||||
|
* shall have been informed of the possibility of such damages. This *
|
||||||
|
* limitation of liability shall not apply to liability for death or *
|
||||||
|
* personal injury resulting from such party's negligence to the *
|
||||||
|
* extent applicable law prohibits such limitation. Some *
|
||||||
|
* jurisdictions do not allow the exclusion or limitation of *
|
||||||
|
* incidental or consequential damages, so this exclusion and *
|
||||||
|
* limitation may not apply to You. *
|
||||||
|
* *
|
||||||
|
************************************************************************
|
||||||
|
|
||||||
|
8. Litigation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Any litigation relating to this License may be brought only in the
|
||||||
|
courts of a jurisdiction where the defendant maintains its principal
|
||||||
|
place of business and such litigation shall be governed by laws of that
|
||||||
|
jurisdiction, without reference to its conflict-of-law provisions.
|
||||||
|
Nothing in this Section shall prevent a party's ability to bring
|
||||||
|
cross-claims or counter-claims.
|
||||||
|
|
||||||
|
9. Miscellaneous
|
||||||
|
----------------
|
||||||
|
|
||||||
|
This License represents the complete agreement concerning the subject
|
||||||
|
matter hereof. If any provision of this License is held to be
|
||||||
|
unenforceable, such provision shall be reformed only to the extent
|
||||||
|
necessary to make it enforceable. Any law or regulation which provides
|
||||||
|
that the language of a contract shall be construed against the drafter
|
||||||
|
shall not be used to construe this License against a Contributor.
|
||||||
|
|
||||||
|
10. Versions of the License
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
10.1. New Versions
|
||||||
|
|
||||||
|
Mozilla Foundation is the license steward. Except as provided in Section
|
||||||
|
10.3, no one other than the license steward has the right to modify or
|
||||||
|
publish new versions of this License. Each version will be given a
|
||||||
|
distinguishing version number.
|
||||||
|
|
||||||
|
10.2. Effect of New Versions
|
||||||
|
|
||||||
|
You may distribute the Covered Software under the terms of the version
|
||||||
|
of the License under which You originally received the Covered Software,
|
||||||
|
or under the terms of any subsequent version published by the license
|
||||||
|
steward.
|
||||||
|
|
||||||
|
10.3. Modified Versions
|
||||||
|
|
||||||
|
If you create software not governed by this License, and you want to
|
||||||
|
create a new license for such software, you may create and use a
|
||||||
|
modified version of this License if you rename the license and remove
|
||||||
|
any references to the name of the license steward (except to note that
|
||||||
|
such modified license differs from this License).
|
||||||
|
|
||||||
|
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||||
|
Licenses
|
||||||
|
|
||||||
|
If You choose to distribute Source Code Form that is Incompatible With
|
||||||
|
Secondary Licenses under the terms of this version of the License, the
|
||||||
|
notice described in Exhibit B of this License must be attached.
|
||||||
|
|
||||||
|
Exhibit A - Source Code Form License Notice
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
If it is not possible or desirable to put the notice in a particular
|
||||||
|
file, then You may include the notice in a location (such as a LICENSE
|
||||||
|
file in a relevant directory) where a recipient would be likely to look
|
||||||
|
for such a notice.
|
||||||
|
|
||||||
|
You may add additional accurate notices of copyright ownership.
|
||||||
|
|
||||||
|
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
|
defined by the Mozilla Public License, v. 2.0.
|
3
generic/gopbuffer/build.rs
Normal file
3
generic/gopbuffer/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
gst_plugin_version_helper::info()
|
||||||
|
}
|
880
generic/gopbuffer/src/gopbuffer/imp.rs
Normal file
880
generic/gopbuffer/src/gopbuffer/imp.rs
Normal file
|
@ -0,0 +1,880 @@
|
||||||
|
// Copyright (C) 2023 Matthew Waters <matthew@centricular.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:element-gopbuffer
|
||||||
|
*
|
||||||
|
* #gopbuffer is an element that can be used to store a minimum duration of data delimited by
|
||||||
|
* discrete GOPs (Group of Picture). It does this in by differentiation on the DELTA_UNIT
|
||||||
|
* flag on each input buffer.
|
||||||
|
*
|
||||||
|
* One example of the usefulness of #gopbuffer is its ability to store a backlog of data starting
|
||||||
|
* on a key frame boundary if say the previous 10s seconds of a stream would like to be recorded to
|
||||||
|
* disk.
|
||||||
|
*
|
||||||
|
* ## Example pipeline
|
||||||
|
*
|
||||||
|
* |[
|
||||||
|
* gst-launch videotestsrc ! vp8enc ! gopbuffer minimum-duration=10000000000 ! fakesink
|
||||||
|
* ]|
|
||||||
|
*
|
||||||
|
* Since: plugins-rs-0.13.0
|
||||||
|
*/
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
use gst::subclass::prelude::*;
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
|
gst::DebugCategory::new(
|
||||||
|
"gopbuffer",
|
||||||
|
gst::DebugColorFlags::empty(),
|
||||||
|
Some("GopBuffer Element"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
const DEFAULT_MIN_TIME: gst::ClockTime = gst::ClockTime::from_seconds(1);
|
||||||
|
const DEFAULT_MAX_TIME: Option<gst::ClockTime> = None;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Settings {
|
||||||
|
min_time: gst::ClockTime,
|
||||||
|
max_time: Option<gst::ClockTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Settings {
|
||||||
|
min_time: DEFAULT_MIN_TIME,
|
||||||
|
max_time: DEFAULT_MAX_TIME,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub(crate) enum DeltaFrames {
|
||||||
|
/// Only single completely decodable frames
|
||||||
|
IntraOnly,
|
||||||
|
/// Frames may depend on past frames
|
||||||
|
PredictiveOnly,
|
||||||
|
/// Frames may depend on past or future frames
|
||||||
|
Bidirectional,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DeltaFrames {
|
||||||
|
/// Whether dts is required to order buffers differently from presentation order
|
||||||
|
pub(crate) fn requires_dts(&self) -> bool {
|
||||||
|
matches!(self, Self::Bidirectional)
|
||||||
|
}
|
||||||
|
/// Whether this coding structure does not allow delta flags on buffers
|
||||||
|
pub(crate) fn intra_only(&self) -> bool {
|
||||||
|
matches!(self, Self::IntraOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_caps(caps: &gst::CapsRef) -> Option<Self> {
|
||||||
|
let s = caps.structure(0)?;
|
||||||
|
Some(match s.name().as_str() {
|
||||||
|
"video/x-h264" | "video/x-h265" => DeltaFrames::Bidirectional,
|
||||||
|
"video/x-vp8" | "video/x-vp9" | "video/x-av1" => DeltaFrames::PredictiveOnly,
|
||||||
|
"image/jpeg" | "image/png" | "video/x-raw" => DeltaFrames::IntraOnly,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add buffer list support
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum GopItem {
|
||||||
|
Buffer(gst::Buffer),
|
||||||
|
Event(gst::Event),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Gop {
|
||||||
|
// all times are in running time
|
||||||
|
start_pts: gst::ClockTime,
|
||||||
|
start_dts: Option<gst::Signed<gst::ClockTime>>,
|
||||||
|
earliest_pts: gst::ClockTime,
|
||||||
|
final_earliest_pts: bool,
|
||||||
|
end_pts: gst::ClockTime,
|
||||||
|
end_dts: Option<gst::Signed<gst::ClockTime>>,
|
||||||
|
final_end_pts: bool,
|
||||||
|
// Buffer or event
|
||||||
|
data: VecDeque<GopItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gop {
|
||||||
|
fn push_on_pad(mut self, pad: &gst::Pad) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
|
let mut iter = self.data.iter().filter_map(|item| match item {
|
||||||
|
GopItem::Buffer(buffer) => buffer.pts(),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
let first_pts = iter.next();
|
||||||
|
let last_pts = iter.last();
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
"pushing gop with start pts {} end pts {}",
|
||||||
|
first_pts.display(),
|
||||||
|
last_pts.display(),
|
||||||
|
);
|
||||||
|
for item in self.data.drain(..) {
|
||||||
|
match item {
|
||||||
|
GopItem::Buffer(buffer) => {
|
||||||
|
pad.push(buffer)?;
|
||||||
|
}
|
||||||
|
GopItem::Event(event) => {
|
||||||
|
pad.push_event(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(gst::FlowSuccess::Ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Stream {
|
||||||
|
sinkpad: gst::Pad,
|
||||||
|
srcpad: gst::Pad,
|
||||||
|
|
||||||
|
sink_segment: Option<gst::FormattedSegment<gst::ClockTime>>,
|
||||||
|
|
||||||
|
delta_frames: DeltaFrames,
|
||||||
|
|
||||||
|
queued_gops: VecDeque<Gop>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream {
|
||||||
|
fn queue_buffer(
|
||||||
|
&mut self,
|
||||||
|
buffer: gst::Buffer,
|
||||||
|
segment: &gst::FormattedSegment<gst::ClockTime>,
|
||||||
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
|
let pts_position = buffer.pts().unwrap();
|
||||||
|
let end_pts_position = pts_position
|
||||||
|
.opt_add(buffer.duration())
|
||||||
|
.unwrap_or(pts_position);
|
||||||
|
|
||||||
|
let pts = segment
|
||||||
|
.to_running_time_full(pts_position)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
gst::error!(CAT, obj: self.sinkpad, "Couldn't convert PTS to running time");
|
||||||
|
gst::FlowError::Error
|
||||||
|
})?
|
||||||
|
.positive()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
gst::warning!(CAT, obj: self.sinkpad, "Negative PTSs are not supported");
|
||||||
|
gst::ClockTime::ZERO
|
||||||
|
});
|
||||||
|
let end_pts = segment
|
||||||
|
.to_running_time_full(end_pts_position)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
gst::error!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"Couldn't convert end PTS to running time"
|
||||||
|
);
|
||||||
|
gst::FlowError::Error
|
||||||
|
})?
|
||||||
|
.positive()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
gst::warning!(CAT, obj: self.sinkpad, "Negative PTSs are not supported");
|
||||||
|
gst::ClockTime::ZERO
|
||||||
|
});
|
||||||
|
|
||||||
|
let (dts, end_dts) = if !self.delta_frames.requires_dts() {
|
||||||
|
(None, None)
|
||||||
|
} else {
|
||||||
|
let dts_position = buffer.dts().expect("No dts");
|
||||||
|
let end_dts_position = buffer
|
||||||
|
.duration()
|
||||||
|
.opt_add(dts_position)
|
||||||
|
.unwrap_or(dts_position);
|
||||||
|
|
||||||
|
let dts = segment.to_running_time_full(dts_position).ok_or_else(|| {
|
||||||
|
gst::error!(CAT, obj: self.sinkpad, "Couldn't convert DTS to running time");
|
||||||
|
gst::FlowError::Error
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let end_dts = segment
|
||||||
|
.to_running_time_full(end_dts_position)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
gst::error!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"Couldn't convert end DTS to running time"
|
||||||
|
);
|
||||||
|
gst::FlowError::Error
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let end_dts = std::cmp::max(end_dts, dts);
|
||||||
|
|
||||||
|
(Some(dts), Some(end_dts))
|
||||||
|
};
|
||||||
|
|
||||||
|
if !buffer.flags().contains(gst::BufferFlags::DELTA_UNIT) {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
"New GOP detected with buffer pts {} dts {}",
|
||||||
|
buffer.pts().display(),
|
||||||
|
buffer.dts().display()
|
||||||
|
);
|
||||||
|
let gop = Gop {
|
||||||
|
start_pts: pts,
|
||||||
|
start_dts: dts,
|
||||||
|
earliest_pts: pts,
|
||||||
|
final_earliest_pts: false,
|
||||||
|
end_pts: pts,
|
||||||
|
end_dts,
|
||||||
|
final_end_pts: false,
|
||||||
|
data: VecDeque::from([GopItem::Buffer(buffer)]),
|
||||||
|
};
|
||||||
|
self.queued_gops.push_front(gop);
|
||||||
|
if let Some(prev_gop) = self.queued_gops.get_mut(1) {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"Updating previous GOP starting at PTS {} to end PTS {}",
|
||||||
|
prev_gop.earliest_pts,
|
||||||
|
pts,
|
||||||
|
);
|
||||||
|
|
||||||
|
prev_gop.end_pts = std::cmp::max(prev_gop.end_pts, pts);
|
||||||
|
prev_gop.end_dts = std::cmp::max(prev_gop.end_dts, dts);
|
||||||
|
|
||||||
|
if !self.delta_frames.requires_dts() {
|
||||||
|
prev_gop.final_end_pts = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !prev_gop.final_earliest_pts {
|
||||||
|
// Don't bother logging this for intra-only streams as it would be for every
|
||||||
|
// single buffer.
|
||||||
|
if self.delta_frames.requires_dts() {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"Previous GOP has final earliest PTS at {}",
|
||||||
|
prev_gop.earliest_pts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_gop.final_earliest_pts = true;
|
||||||
|
if let Some(prev_prev_gop) = self.queued_gops.get_mut(2) {
|
||||||
|
prev_prev_gop.final_end_pts = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(gop) = self.queued_gops.front_mut() {
|
||||||
|
gop.end_pts = std::cmp::max(gop.end_pts, end_pts);
|
||||||
|
gop.end_dts = gop.end_dts.opt_max(end_dts);
|
||||||
|
gop.data.push_back(GopItem::Buffer(buffer));
|
||||||
|
|
||||||
|
if self.delta_frames.requires_dts() {
|
||||||
|
let dts = dts.unwrap();
|
||||||
|
|
||||||
|
if gop.earliest_pts > pts && !gop.final_earliest_pts {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"Updating current GOP earliest PTS from {} to {}",
|
||||||
|
gop.earliest_pts,
|
||||||
|
pts
|
||||||
|
);
|
||||||
|
gop.earliest_pts = pts;
|
||||||
|
|
||||||
|
if let Some(prev_gop) = self.queued_gops.get_mut(1) {
|
||||||
|
if prev_gop.end_pts < pts {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"Updating previous GOP starting PTS {} end time from {} to {}",
|
||||||
|
pts,
|
||||||
|
prev_gop.end_pts,
|
||||||
|
pts
|
||||||
|
);
|
||||||
|
prev_gop.end_pts = pts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let gop = self.queued_gops.front_mut().unwrap();
|
||||||
|
|
||||||
|
// The earliest PTS is known when the current DTS is bigger or equal to the first
|
||||||
|
// PTS that was observed in this GOP. If there was another frame later that had a
|
||||||
|
// lower PTS then it wouldn't be possible to display it in time anymore, i.e. the
|
||||||
|
// stream would be invalid.
|
||||||
|
if gop.start_pts <= dts && !gop.final_earliest_pts {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"GOP has final earliest PTS at {}",
|
||||||
|
gop.earliest_pts
|
||||||
|
);
|
||||||
|
gop.final_earliest_pts = true;
|
||||||
|
|
||||||
|
if let Some(prev_gop) = self.queued_gops.get_mut(1) {
|
||||||
|
prev_gop.final_end_pts = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
"dropping buffer before first GOP with pts {} dts {}",
|
||||||
|
buffer.pts().display(),
|
||||||
|
buffer.dts().display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((prev_gop, first_gop)) = Option::zip(
|
||||||
|
self.queued_gops.iter().find(|gop| gop.final_end_pts),
|
||||||
|
self.queued_gops.back(),
|
||||||
|
) {
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"Queued full GOPs duration updated to {}",
|
||||||
|
prev_gop.end_pts.saturating_sub(first_gop.earliest_pts),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj: self.sinkpad,
|
||||||
|
"Queued duration updated to {}",
|
||||||
|
Option::zip(self.queued_gops.front(), self.queued_gops.back())
|
||||||
|
.map(|(end, start)| end.end_pts.saturating_sub(start.start_pts))
|
||||||
|
.unwrap_or(gst::ClockTime::ZERO)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(gst::FlowSuccess::Ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn oldest_gop(&mut self) -> Option<Gop> {
|
||||||
|
self.queued_gops.pop_back()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_oldest_gop(&self) -> Option<&Gop> {
|
||||||
|
self.queued_gops.back()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn peek_second_oldest_gop(&self) -> Option<&Gop> {
|
||||||
|
if self.queued_gops.len() <= 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
self.queued_gops.get(self.queued_gops.len() - 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drain_all(&mut self) -> impl Iterator<Item = Gop> + '_ {
|
||||||
|
self.queued_gops.drain(..)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) {
|
||||||
|
self.queued_gops.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct State {
|
||||||
|
streams: Vec<Stream>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
fn stream_from_sink_pad(&self, pad: &gst::Pad) -> Option<&Stream> {
|
||||||
|
self.streams.iter().find(|stream| &stream.sinkpad == pad)
|
||||||
|
}
|
||||||
|
fn stream_from_sink_pad_mut(&mut self, pad: &gst::Pad) -> Option<&mut Stream> {
|
||||||
|
self.streams
|
||||||
|
.iter_mut()
|
||||||
|
.find(|stream| &stream.sinkpad == pad)
|
||||||
|
}
|
||||||
|
fn stream_from_src_pad(&self, pad: &gst::Pad) -> Option<&Stream> {
|
||||||
|
self.streams.iter().find(|stream| &stream.srcpad == pad)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub(crate) struct GopBuffer {
|
||||||
|
state: Mutex<State>,
|
||||||
|
settings: Mutex<Settings>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GopBuffer {
|
||||||
|
fn sink_chain(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
buffer: gst::Buffer,
|
||||||
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
|
let obj = self.obj();
|
||||||
|
if buffer.pts().is_none() {
|
||||||
|
gst::error!(CAT, obj: obj, "Require timestamped buffers!");
|
||||||
|
return Err(gst::FlowError::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let settings = self.settings.lock().unwrap().clone();
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
let stream = state
|
||||||
|
.stream_from_sink_pad_mut(pad)
|
||||||
|
.expect("pad without an internal Stream");
|
||||||
|
|
||||||
|
let Some(segment) = stream.sink_segment.clone() else {
|
||||||
|
gst::element_imp_error!(self, gst::CoreError::Clock, ["Got buffer before segment"]);
|
||||||
|
return Err(gst::FlowError::Error);
|
||||||
|
};
|
||||||
|
|
||||||
|
if stream.delta_frames.intra_only() && buffer.flags().contains(gst::BufferFlags::DELTA_UNIT)
|
||||||
|
{
|
||||||
|
gst::error!(CAT, obj: pad, "Intra-only stream with delta units");
|
||||||
|
return Err(gst::FlowError::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if stream.delta_frames.requires_dts() && buffer.dts().is_none() {
|
||||||
|
gst::error!(CAT, obj: pad, "Require DTS for video streams");
|
||||||
|
return Err(gst::FlowError::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let srcpad = stream.srcpad.clone();
|
||||||
|
stream.queue_buffer(buffer, &segment)?;
|
||||||
|
let mut gops_to_push = vec![];
|
||||||
|
|
||||||
|
let Some(newest_gop) = stream.queued_gops.front() else {
|
||||||
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
|
};
|
||||||
|
// we are looking for the latest pts value here (which should be the largest)
|
||||||
|
let newest_ts = if stream.delta_frames.requires_dts() {
|
||||||
|
newest_gop.end_dts.unwrap()
|
||||||
|
} else {
|
||||||
|
gst::Signed::Positive(newest_gop.end_pts)
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// check stored times as though the oldest GOP doesn't exist.
|
||||||
|
let Some(second_oldest_gop) = stream.peek_second_oldest_gop() else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
// we are looking for the oldest pts here (with the largest value). This is our potentially
|
||||||
|
// new end time.
|
||||||
|
let oldest_ts = if stream.delta_frames.requires_dts() {
|
||||||
|
second_oldest_gop.start_dts.unwrap()
|
||||||
|
} else {
|
||||||
|
gst::Signed::Positive(second_oldest_gop.start_pts)
|
||||||
|
};
|
||||||
|
|
||||||
|
let stored_duration_without_oldest = newest_ts.saturating_sub(oldest_ts);
|
||||||
|
gst::trace!(
|
||||||
|
CAT,
|
||||||
|
obj: obj,
|
||||||
|
"newest_pts {}, second oldest_pts {}, stored_duration_without_oldest_gop {}, min-time {}",
|
||||||
|
newest_ts.display(),
|
||||||
|
oldest_ts.display(),
|
||||||
|
stored_duration_without_oldest.display(),
|
||||||
|
settings.min_time.display()
|
||||||
|
);
|
||||||
|
if stored_duration_without_oldest < settings.min_time {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gops_to_push.push(stream.oldest_gop().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(max_time) = settings.max_time {
|
||||||
|
while let Some(oldest_gop) = stream.peek_oldest_gop() {
|
||||||
|
let oldest_ts = oldest_gop.data.iter().rev().find_map(|item| match item {
|
||||||
|
GopItem::Buffer(buffer) => {
|
||||||
|
if stream.delta_frames.requires_dts() {
|
||||||
|
Some(gst::Signed::Positive(buffer.dts().unwrap()))
|
||||||
|
} else {
|
||||||
|
Some(gst::Signed::Positive(buffer.pts().unwrap()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
if newest_ts
|
||||||
|
.opt_saturating_sub(oldest_ts)
|
||||||
|
.map_or(false, |diff| diff > gst::Signed::Positive(max_time))
|
||||||
|
{
|
||||||
|
gst::warning!(CAT, obj: obj, "Stored data has overflowed the maximum allowed stored time {}, pushing oldest GOP", max_time.display());
|
||||||
|
gops_to_push.push(stream.oldest_gop().unwrap());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(state);
|
||||||
|
for gop in gops_to_push.into_iter() {
|
||||||
|
gop.push_on_pad(&srcpad)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(gst::FlowSuccess::Ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_event(&self, pad: &gst::Pad, event: gst::Event) -> bool {
|
||||||
|
let obj = self.obj();
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
let stream = state
|
||||||
|
.stream_from_sink_pad_mut(pad)
|
||||||
|
.expect("pad without an internal Stream!");
|
||||||
|
match event.view() {
|
||||||
|
gst::EventView::Caps(caps) => {
|
||||||
|
let Some(delta_frames) = DeltaFrames::from_caps(caps.caps()) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
stream.delta_frames = delta_frames;
|
||||||
|
}
|
||||||
|
gst::EventView::FlushStop(_flush) => {
|
||||||
|
gst::debug!(CAT, obj: obj, "flushing stored data");
|
||||||
|
stream.flush();
|
||||||
|
}
|
||||||
|
gst::EventView::Eos(_eos) => {
|
||||||
|
gst::debug!(CAT, obj: obj, "draining data at EOS");
|
||||||
|
let gops = stream.drain_all().collect::<Vec<_>>();
|
||||||
|
let srcpad = stream.srcpad.clone();
|
||||||
|
drop(state);
|
||||||
|
for gop in gops.into_iter() {
|
||||||
|
let _ = gop.push_on_pad(&srcpad);
|
||||||
|
}
|
||||||
|
// once we've pushed all the data, we can push the corresponding eos
|
||||||
|
gst::Pad::event_default(pad, Some(&*obj), event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
gst::EventView::Segment(segment) => {
|
||||||
|
let Ok(segment) = segment.segment().clone().downcast::<gst::ClockTime>() else {
|
||||||
|
gst::error!(CAT, "Non TIME segments are not supported");
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
stream.sink_segment = Some(segment);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
|
||||||
|
if event.is_serialized() {
|
||||||
|
if stream.peek_oldest_gop().is_none() {
|
||||||
|
// if there is nothing queued, the event can go straight through
|
||||||
|
gst::trace!(CAT, obj: obj, "nothing queued, event {:?} passthrough", event.structure().map(|s| s.name().as_str()));
|
||||||
|
drop(state);
|
||||||
|
return gst::Pad::event_default(pad, Some(&*obj), event);
|
||||||
|
}
|
||||||
|
let gop = stream.queued_gops.front_mut().unwrap();
|
||||||
|
gop.data.push_back(GopItem::Event(event));
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// non-serialized events can be pushed directly
|
||||||
|
drop(state);
|
||||||
|
gst::Pad::event_default(pad, Some(&*obj), event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
|
||||||
|
let obj = self.obj();
|
||||||
|
if query.is_serialized() {
|
||||||
|
// TODO: serialized queries somehow?
|
||||||
|
gst::warning!(CAT, obj: pad, "Serialized queries are currently not supported");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gst::Pad::query_default(pad, Some(&*obj), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn src_query(&self, pad: &gst::Pad, query: &mut gst::QueryRef) -> bool {
|
||||||
|
let obj = self.obj();
|
||||||
|
match query.view_mut() {
|
||||||
|
gst::QueryViewMut::Latency(latency) => {
|
||||||
|
let mut upstream_query = gst::query::Latency::new();
|
||||||
|
let otherpad = {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
let Some(stream) = state.stream_from_src_pad(pad) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
stream.sinkpad.clone()
|
||||||
|
};
|
||||||
|
let ret = otherpad.peer_query(&mut upstream_query);
|
||||||
|
|
||||||
|
if ret {
|
||||||
|
let (live, mut min, mut max) = upstream_query.result();
|
||||||
|
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
min += settings.max_time.unwrap_or(settings.min_time);
|
||||||
|
max = max.opt_max(settings.max_time);
|
||||||
|
|
||||||
|
latency.set(live, min, max);
|
||||||
|
|
||||||
|
gst::debug!(
|
||||||
|
CAT,
|
||||||
|
obj: pad,
|
||||||
|
"Latency query response: live {} min {} max {}",
|
||||||
|
live,
|
||||||
|
min,
|
||||||
|
max.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
_ => gst::Pad::query_default(pad, Some(&*obj), query),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn iterate_internal_links(&self, pad: &gst::Pad) -> gst::Iterator<gst::Pad> {
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
let otherpad = match pad.direction() {
|
||||||
|
gst::PadDirection::Src => state
|
||||||
|
.stream_from_src_pad(pad)
|
||||||
|
.map(|stream| stream.sinkpad.clone()),
|
||||||
|
gst::PadDirection::Sink => state
|
||||||
|
.stream_from_sink_pad(pad)
|
||||||
|
.map(|stream| stream.srcpad.clone()),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
if let Some(otherpad) = otherpad {
|
||||||
|
gst::Iterator::from_vec(vec![otherpad])
|
||||||
|
} else {
|
||||||
|
gst::Iterator::from_vec(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for GopBuffer {
|
||||||
|
const NAME: &'static str = "GstGopBuffer";
|
||||||
|
type Type = super::GopBuffer;
|
||||||
|
type ParentType = gst::Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for GopBuffer {
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![
|
||||||
|
glib::ParamSpecUInt64::builder("minimum-duration")
|
||||||
|
.nick("Minimum Duration")
|
||||||
|
.blurb("The minimum duration to store")
|
||||||
|
.default_value(DEFAULT_MIN_TIME.nseconds())
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
glib::ParamSpecUInt64::builder("max-size-time")
|
||||||
|
.nick("Maximum Duration")
|
||||||
|
.blurb("The maximum duration to store (0=disable)")
|
||||||
|
.default_value(0)
|
||||||
|
.mutable_ready()
|
||||||
|
.build(),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
&PROPERTIES
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
match pspec.name() {
|
||||||
|
"minimum-duration" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
let min_time = value.get().expect("type checked upstream");
|
||||||
|
if settings.min_time != min_time {
|
||||||
|
settings.min_time = min_time;
|
||||||
|
drop(settings);
|
||||||
|
self.post_message(gst::message::Latency::builder().src(&*self.obj()).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"max-size-time" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
let max_time = value
|
||||||
|
.get::<Option<gst::ClockTime>>()
|
||||||
|
.expect("type checked upstream");
|
||||||
|
let max_time = if matches!(max_time, Some(gst::ClockTime::ZERO) | None) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
max_time
|
||||||
|
};
|
||||||
|
if settings.max_time != max_time {
|
||||||
|
settings.max_time = max_time;
|
||||||
|
drop(settings);
|
||||||
|
self.post_message(gst::message::Latency::builder().src(&*self.obj()).build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"minimum-duration" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.min_time.to_value()
|
||||||
|
}
|
||||||
|
"max-size-time" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.max_time.unwrap_or(gst::ClockTime::ZERO).to_value()
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
let obj = self.obj();
|
||||||
|
let class = obj.class();
|
||||||
|
let templ = class.pad_template("video_sink").unwrap();
|
||||||
|
let sinkpad = gst::Pad::builder_from_template(&templ)
|
||||||
|
.name("video_sink")
|
||||||
|
.chain_function(|pad, parent, buffer| {
|
||||||
|
GopBuffer::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| Err(gst::FlowError::Error),
|
||||||
|
|gopbuffer| gopbuffer.sink_chain(pad, buffer),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.event_function(|pad, parent, event| {
|
||||||
|
GopBuffer::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|gopbuffer| gopbuffer.sink_event(pad, event),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.query_function(|pad, parent, query| {
|
||||||
|
GopBuffer::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|gopbuffer| gopbuffer.sink_query(pad, query),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.iterate_internal_links_function(|pad, parent| {
|
||||||
|
GopBuffer::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| gst::Pad::iterate_internal_links_default(pad, parent),
|
||||||
|
|gopbuffer| gopbuffer.iterate_internal_links(pad),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.flags(gst::PadFlags::PROXY_CAPS)
|
||||||
|
.build();
|
||||||
|
obj.add_pad(&sinkpad).unwrap();
|
||||||
|
|
||||||
|
let templ = class.pad_template("video_src").unwrap();
|
||||||
|
let srcpad = gst::Pad::builder_from_template(&templ)
|
||||||
|
.name("video_src")
|
||||||
|
.query_function(|pad, parent, query| {
|
||||||
|
GopBuffer::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|gopbuffer| gopbuffer.src_query(pad, query),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.iterate_internal_links_function(|pad, parent| {
|
||||||
|
GopBuffer::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| gst::Pad::iterate_internal_links_default(pad, parent),
|
||||||
|
|gopbuffer| gopbuffer.iterate_internal_links(pad),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
obj.add_pad(&srcpad).unwrap();
|
||||||
|
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
state.streams.push(Stream {
|
||||||
|
sinkpad,
|
||||||
|
srcpad,
|
||||||
|
sink_segment: None,
|
||||||
|
delta_frames: DeltaFrames::IntraOnly,
|
||||||
|
queued_gops: VecDeque::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GstObjectImpl for GopBuffer {}
|
||||||
|
|
||||||
|
impl ElementImpl for GopBuffer {
|
||||||
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||||
|
gst::subclass::ElementMetadata::new(
|
||||||
|
"GopBuffer",
|
||||||
|
"Video",
|
||||||
|
"GOP Buffer",
|
||||||
|
"Matthew Waters <matthew@centricular.com>",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(&*ELEMENT_METADATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||||
|
// This element is designed to implement multiple streams but it has not been
|
||||||
|
// implemented.
|
||||||
|
//
|
||||||
|
// The things missing for multiple (audio or video) streams are:
|
||||||
|
// 1. More pad templates
|
||||||
|
// 2. Choosing a main stream to drive the timestamp logic between all input streams
|
||||||
|
// 3. Allowing either the main stream to cause other streams to push data
|
||||||
|
// regardless of it's GOP state, or allow each stream to be individually delimited
|
||||||
|
// by GOP but all still within the minimum duration.
|
||||||
|
let video_caps = [
|
||||||
|
gst::Structure::builder("video/x-h264")
|
||||||
|
.field("stream-format", gst::List::new(["avc", "avc3"]))
|
||||||
|
.field("alignment", "au")
|
||||||
|
.build(),
|
||||||
|
gst::Structure::builder("video/x-h265")
|
||||||
|
.field("stream-format", gst::List::new(["hvc1", "hev1"]))
|
||||||
|
.field("alignment", "au")
|
||||||
|
.build(),
|
||||||
|
gst::Structure::builder("video/x-vp8").build(),
|
||||||
|
gst::Structure::builder("video/x-vp9").build(),
|
||||||
|
gst::Structure::builder("video/x-av1")
|
||||||
|
.field("stream-format", "obu-stream")
|
||||||
|
.field("alignment", "tu")
|
||||||
|
.build(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect::<gst::Caps>();
|
||||||
|
|
||||||
|
let src_pad_template = gst::PadTemplate::new(
|
||||||
|
"video_src",
|
||||||
|
gst::PadDirection::Src,
|
||||||
|
gst::PadPresence::Always,
|
||||||
|
&video_caps,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let sink_pad_template = gst::PadTemplate::new(
|
||||||
|
"video_sink",
|
||||||
|
gst::PadDirection::Sink,
|
||||||
|
gst::PadPresence::Always,
|
||||||
|
&video_caps,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
vec![src_pad_template, sink_pad_template]
|
||||||
|
});
|
||||||
|
|
||||||
|
PAD_TEMPLATES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_state(
|
||||||
|
&self,
|
||||||
|
transition: gst::StateChange,
|
||||||
|
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
match transition {
|
||||||
|
gst::StateChange::NullToReady => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
if let Some(max_time) = settings.max_time {
|
||||||
|
if max_time < settings.min_time {
|
||||||
|
gst::element_imp_error!(
|
||||||
|
self,
|
||||||
|
gst::CoreError::StateChange,
|
||||||
|
["Configured maximum time is less than the minimum time"]
|
||||||
|
);
|
||||||
|
return Err(gst::StateChangeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parent_change_state(transition)?;
|
||||||
|
|
||||||
|
Ok(gst::StateChangeSuccess::Success)
|
||||||
|
}
|
||||||
|
}
|
27
generic/gopbuffer/src/gopbuffer/mod.rs
Normal file
27
generic/gopbuffer/src/gopbuffer/mod.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright (C) 2022 Matthew Waters <matthew@centricular.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub(crate) struct GopBuffer(ObjectSubclass<imp::GopBuffer>) @extends gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
gst::Element::register(
|
||||||
|
Some(plugin),
|
||||||
|
"gopbuffer",
|
||||||
|
gst::Rank::PRIMARY,
|
||||||
|
GopBuffer::static_type(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
34
generic/gopbuffer/src/lib.rs
Normal file
34
generic/gopbuffer/src/lib.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// Copyright (C) 2022 Matthew Waters <matthew@centricular.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
#![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plugin-gopbuffer:
|
||||||
|
*
|
||||||
|
* Since: plugins-rs-0.13.0
|
||||||
|
*/
|
||||||
|
use gst::glib;
|
||||||
|
|
||||||
|
mod gopbuffer;
|
||||||
|
|
||||||
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
gopbuffer::register(plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::plugin_define!(
|
||||||
|
gopbuffer,
|
||||||
|
env!("CARGO_PKG_DESCRIPTION"),
|
||||||
|
plugin_init,
|
||||||
|
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
||||||
|
// FIXME: MPL-2.0 is only allowed since 1.18.3 (as unknown) and 1.20 (as known)
|
||||||
|
"MPL",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_REPOSITORY"),
|
||||||
|
env!("BUILD_REL_DATE")
|
||||||
|
);
|
128
generic/gopbuffer/tests/tests.rs
Normal file
128
generic/gopbuffer/tests/tests.rs
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
fn init() {
|
||||||
|
use std::sync::Once;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| {
|
||||||
|
gst::init().unwrap();
|
||||||
|
gstgopbuffer::plugin_register_static().unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! check_buffer {
|
||||||
|
($buf1:expr, $buf2:expr) => {
|
||||||
|
assert_eq!($buf1.pts(), $buf2.pts());
|
||||||
|
assert_eq!($buf1.dts(), $buf2.dts());
|
||||||
|
assert_eq!($buf1.flags(), $buf2.flags());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_min_one_gop_held() {
|
||||||
|
const OFFSET: gst::ClockTime = gst::ClockTime::from_seconds(10);
|
||||||
|
init();
|
||||||
|
|
||||||
|
let mut h =
|
||||||
|
gst_check::Harness::with_padnames("gopbuffer", Some("video_sink"), Some("video_src"));
|
||||||
|
|
||||||
|
// 200ms min buffer time
|
||||||
|
let element = h.element().unwrap();
|
||||||
|
element.set_property("minimum-duration", gst::ClockTime::from_mseconds(200));
|
||||||
|
|
||||||
|
h.set_src_caps(
|
||||||
|
gst::Caps::builder("video/x-h264")
|
||||||
|
.field("width", 320i32)
|
||||||
|
.field("height", 240i32)
|
||||||
|
.field("framerate", gst::Fraction::new(10, 1))
|
||||||
|
.field("stream-format", "avc")
|
||||||
|
.field("alignment", "au")
|
||||||
|
.field("codec_data", gst::Buffer::with_size(1).unwrap())
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
let mut in_segment = gst::Segment::new();
|
||||||
|
in_segment.set_format(gst::Format::Time);
|
||||||
|
in_segment.set_base(10.seconds());
|
||||||
|
assert!(h.push_event(gst::event::Segment::builder(&in_segment).build()));
|
||||||
|
|
||||||
|
h.play();
|
||||||
|
|
||||||
|
// Push 10 buffers of 100ms each, 2nd and 5th buffer without DELTA_UNIT flag
|
||||||
|
let in_buffers: Vec<_> = (0..6)
|
||||||
|
.map(|i| {
|
||||||
|
let mut buffer = gst::Buffer::with_size(1).unwrap();
|
||||||
|
{
|
||||||
|
let buffer = buffer.get_mut().unwrap();
|
||||||
|
buffer.set_pts(OFFSET + gst::ClockTime::from_mseconds(i * 100));
|
||||||
|
buffer.set_dts(OFFSET + gst::ClockTime::from_mseconds(i * 100));
|
||||||
|
buffer.set_duration(gst::ClockTime::from_mseconds(100));
|
||||||
|
if i != 1 && i != 4 {
|
||||||
|
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(h.push(buffer.clone()), Ok(gst::FlowSuccess::Ok));
|
||||||
|
buffer
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// pull mandatory events
|
||||||
|
let ev = h.pull_event().unwrap();
|
||||||
|
assert_eq!(ev.type_(), gst::EventType::StreamStart);
|
||||||
|
let ev = h.pull_event().unwrap();
|
||||||
|
assert_eq!(ev.type_(), gst::EventType::Caps);
|
||||||
|
// GstHarness pushes its own segment event that we need to eat
|
||||||
|
let ev = h.pull_event().unwrap();
|
||||||
|
assert_eq!(ev.type_(), gst::EventType::Segment);
|
||||||
|
let ev = h.pull_event().unwrap();
|
||||||
|
let gst::event::EventView::Segment(recv_segment) = ev.view() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let recv_segment = recv_segment.segment();
|
||||||
|
assert_eq!(recv_segment, &in_segment);
|
||||||
|
|
||||||
|
// check that at least the first GOP has been output already as it exceeds the minimum-time
|
||||||
|
// value
|
||||||
|
let mut in_iter = in_buffers.iter();
|
||||||
|
|
||||||
|
// the first buffer is dropped because it was not preceded by a keyframe
|
||||||
|
let _buffer = in_iter.next().unwrap();
|
||||||
|
|
||||||
|
// a keyframe
|
||||||
|
let out = h.pull().unwrap();
|
||||||
|
let buffer = in_iter.next().unwrap();
|
||||||
|
check_buffer!(buffer, out);
|
||||||
|
|
||||||
|
// not a keyframe
|
||||||
|
let out = h.pull().unwrap();
|
||||||
|
let buffer = in_iter.next().unwrap();
|
||||||
|
check_buffer!(buffer, out);
|
||||||
|
|
||||||
|
// not a keyframe
|
||||||
|
let out = h.pull().unwrap();
|
||||||
|
let buffer = in_iter.next().unwrap();
|
||||||
|
check_buffer!(buffer, out);
|
||||||
|
|
||||||
|
// no more buffers
|
||||||
|
assert_eq!(h.buffers_in_queue(), 0);
|
||||||
|
|
||||||
|
// push eos to drain out the rest of the data
|
||||||
|
assert!(h.push_event(gst::event::Eos::new()));
|
||||||
|
for buffer in in_iter {
|
||||||
|
let out = h.pull().unwrap();
|
||||||
|
check_buffer!(buffer, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no more buffers
|
||||||
|
assert_eq!(h.buffers_in_queue(), 0);
|
||||||
|
|
||||||
|
let ev = h.pull_event().unwrap();
|
||||||
|
assert_eq!(ev.type_(), gst::EventType::Eos);
|
||||||
|
}
|
53
generic/inter/Cargo.toml
Normal file
53
generic/inter/Cargo.toml
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
[package]
|
||||||
|
name = "gst-plugin-inter"
|
||||||
|
version.workspace = true
|
||||||
|
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
|
||||||
|
license = "MPL-2.0"
|
||||||
|
description = "GStreamer Inter Plugin"
|
||||||
|
repository.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1"
|
||||||
|
gst = { workspace = true, features = ["v1_18"] }
|
||||||
|
gst-utils.workspace = true
|
||||||
|
gst-app.workspace = true
|
||||||
|
once_cell = "1.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "1"
|
||||||
|
gst-check.workspace = true
|
||||||
|
futures = "0.3"
|
||||||
|
tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread", "time"] }
|
||||||
|
tokio-stream = "0.1.11"
|
||||||
|
serial_test = "3"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "gstrsinter"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
static = []
|
||||||
|
capi = []
|
||||||
|
doc = ["gst/v1_18"]
|
||||||
|
|
||||||
|
[package.metadata.capi]
|
||||||
|
min_version = "0.8.0"
|
||||||
|
|
||||||
|
[package.metadata.capi.header]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[package.metadata.capi.library]
|
||||||
|
install_subdir = "gstreamer-1.0"
|
||||||
|
versioning = false
|
||||||
|
|
||||||
|
[package.metadata.capi.pkg_config]
|
||||||
|
requires_private = "gstreamer-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "plug-and-play"
|
5
generic/inter/build.rs
Normal file
5
generic/inter/build.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
gst_plugin_version_helper::info()
|
||||||
|
}
|
74
generic/inter/examples/basic.rs
Normal file
74
generic/inter/examples/basic.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use anyhow::Error;
|
||||||
|
use futures::prelude::*;
|
||||||
|
use futures::stream::select_all;
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
fn toplevel(obj: &gst::Object) -> gst::Object {
|
||||||
|
if let Some(parent) = obj.parent() {
|
||||||
|
toplevel(&parent)
|
||||||
|
} else {
|
||||||
|
obj.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Error> {
|
||||||
|
gst::init()?;
|
||||||
|
|
||||||
|
let src_pipeline = gst::parse::launch("videotestsrc is-live=true ! intersink")?;
|
||||||
|
let sink_pipeline = gst::parse::launch("intersrc ! videoconvert ! autovideosink")?;
|
||||||
|
|
||||||
|
let mut stream = select_all([
|
||||||
|
src_pipeline.bus().unwrap().stream(),
|
||||||
|
sink_pipeline.bus().unwrap().stream(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let base_time = gst::SystemClock::obtain().time().unwrap();
|
||||||
|
|
||||||
|
src_pipeline.set_clock(Some(&gst::SystemClock::obtain()))?;
|
||||||
|
src_pipeline.set_start_time(gst::ClockTime::NONE);
|
||||||
|
src_pipeline.set_base_time(base_time);
|
||||||
|
|
||||||
|
sink_pipeline.set_clock(Some(&gst::SystemClock::obtain()))?;
|
||||||
|
sink_pipeline.set_start_time(gst::ClockTime::NONE);
|
||||||
|
sink_pipeline.set_base_time(base_time);
|
||||||
|
|
||||||
|
src_pipeline.set_state(gst::State::Playing)?;
|
||||||
|
sink_pipeline.set_state(gst::State::Playing)?;
|
||||||
|
|
||||||
|
while let Some(msg) = stream.next().await {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
|
match msg.view() {
|
||||||
|
MessageView::Latency(..) => {
|
||||||
|
if let Some(o) = msg.src() {
|
||||||
|
if let Ok(pipeline) = toplevel(o).downcast::<gst::Pipeline>() {
|
||||||
|
eprintln!("Recalculating latency {:?}", pipeline);
|
||||||
|
let _ = pipeline.recalculate_latency();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MessageView::Eos(..) => {
|
||||||
|
eprintln!("Unexpected EOS");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
MessageView::Error(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Got error from {}: {} ({})",
|
||||||
|
msg.src()
|
||||||
|
.map(|s| String::from(s.path_string()))
|
||||||
|
.unwrap_or_else(|| "None".into()),
|
||||||
|
err.error(),
|
||||||
|
err.debug().unwrap_or_else(|| "".into()),
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
src_pipeline.set_state(gst::State::Null)?;
|
||||||
|
sink_pipeline.set_state(gst::State::Null)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
324
generic/inter/examples/plug-and-play.rs
Normal file
324
generic/inter/examples/plug-and-play.rs
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
use anyhow::Error;
|
||||||
|
use futures::prelude::*;
|
||||||
|
use gst::prelude::*;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use tokio::task;
|
||||||
|
|
||||||
|
struct Producer {
|
||||||
|
pipeline: gst::Pipeline,
|
||||||
|
sink: gst::Element,
|
||||||
|
overlay: gst::Element,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Consumer {
|
||||||
|
pipeline: gst::Pipeline,
|
||||||
|
src: gst::Element,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_sink_pipeline(producer_name: &str) -> Result<Producer, Error> {
|
||||||
|
let pipeline = gst::Pipeline::builder()
|
||||||
|
.name(format!("producer-{producer_name}"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let videotestsrc = gst::ElementFactory::make("videotestsrc")
|
||||||
|
.property_from_str("pattern", "ball")
|
||||||
|
.property("is-live", true)
|
||||||
|
.build()?;
|
||||||
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
||||||
|
.property(
|
||||||
|
"caps",
|
||||||
|
gst::Caps::builder("video/x-raw")
|
||||||
|
.field("framerate", gst::Fraction::new(50, 1))
|
||||||
|
.build(),
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
let queue = gst::ElementFactory::make("queue").build()?;
|
||||||
|
let overlay = gst::ElementFactory::make("textoverlay")
|
||||||
|
.property("font-desc", "Sans 30")
|
||||||
|
.property("text", format!("Producer: {producer_name}"))
|
||||||
|
.property_from_str("valignment", "top")
|
||||||
|
.build()?;
|
||||||
|
let timeoverlay = gst::ElementFactory::make("timeoverlay")
|
||||||
|
.property("font-desc", "Sans 30")
|
||||||
|
.property_from_str("valignment", "center")
|
||||||
|
.property_from_str("halignment", "center")
|
||||||
|
.build()?;
|
||||||
|
let sink = gst::ElementFactory::make("intersink")
|
||||||
|
.property("producer-name", producer_name)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
pipeline.add_many([
|
||||||
|
&videotestsrc,
|
||||||
|
&capsfilter,
|
||||||
|
&queue,
|
||||||
|
&overlay,
|
||||||
|
&timeoverlay,
|
||||||
|
&sink,
|
||||||
|
])?;
|
||||||
|
gst::Element::link_many([
|
||||||
|
&videotestsrc,
|
||||||
|
&capsfilter,
|
||||||
|
&queue,
|
||||||
|
&overlay,
|
||||||
|
&timeoverlay,
|
||||||
|
&sink,
|
||||||
|
])?;
|
||||||
|
|
||||||
|
Ok(Producer {
|
||||||
|
pipeline,
|
||||||
|
sink,
|
||||||
|
overlay,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_src_pipeline(producer_name: &str, consumer_name: &str) -> Result<Consumer, Error> {
|
||||||
|
let pipeline = gst::Pipeline::builder()
|
||||||
|
.name(format!("consumer-{consumer_name}"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let src = gst::ElementFactory::make("intersrc")
|
||||||
|
.property("producer-name", producer_name)
|
||||||
|
.build()?;
|
||||||
|
let queue = gst::ElementFactory::make("queue").build()?;
|
||||||
|
let vconv = gst::ElementFactory::make("videoconvert").build()?;
|
||||||
|
let overlay = gst::ElementFactory::make("textoverlay")
|
||||||
|
.property("font-desc", "Sans 30")
|
||||||
|
.property("text", format!("Consumer: {consumer_name}"))
|
||||||
|
.property_from_str("valignment", "bottom")
|
||||||
|
.build()?;
|
||||||
|
let vconv2 = gst::ElementFactory::make("videoconvert").build()?;
|
||||||
|
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
||||||
|
|
||||||
|
pipeline.add_many([&src, &queue, &vconv, &overlay, &vconv2, &sink])?;
|
||||||
|
gst::Element::link_many([&src, &queue, &vconv, &overlay, &vconv2, &sink])?;
|
||||||
|
|
||||||
|
Ok(Consumer { pipeline, src })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt_on() {
|
||||||
|
print!("$ ");
|
||||||
|
let _ = std::io::stdout().flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn monitor_pipeline(pipeline: &gst::Pipeline, base_time: gst::ClockTime) -> Result<(), Error> {
|
||||||
|
pipeline.set_clock(Some(&gst::SystemClock::obtain()))?;
|
||||||
|
pipeline.set_start_time(gst::ClockTime::NONE);
|
||||||
|
pipeline.set_base_time(base_time);
|
||||||
|
|
||||||
|
pipeline.set_state(gst::State::Playing)?;
|
||||||
|
|
||||||
|
let mut bus_stream = pipeline.bus().expect("Pipeline should have a bus").stream();
|
||||||
|
|
||||||
|
let pipeline_clone = pipeline.downgrade();
|
||||||
|
task::spawn(async move {
|
||||||
|
while let Some(msg) = bus_stream.next().await {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
|
if let Some(pipeline) = pipeline_clone.upgrade() {
|
||||||
|
match msg.view() {
|
||||||
|
MessageView::Latency(..) => {
|
||||||
|
let _ = pipeline.recalculate_latency();
|
||||||
|
}
|
||||||
|
MessageView::Eos(..) => {
|
||||||
|
println!(
|
||||||
|
"EOS from {}",
|
||||||
|
msg.src()
|
||||||
|
.map(|s| String::from(s.path_string()))
|
||||||
|
.unwrap_or_else(|| "None".into())
|
||||||
|
);
|
||||||
|
prompt_on();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
MessageView::Error(err) => {
|
||||||
|
let _ = pipeline.set_state(gst::State::Null);
|
||||||
|
println!(
|
||||||
|
"Got error from {}: {} ({})",
|
||||||
|
msg.src()
|
||||||
|
.map(|s| String::from(s.path_string()))
|
||||||
|
.unwrap_or_else(|| "None".into()),
|
||||||
|
err.error(),
|
||||||
|
err.debug().unwrap_or_else(|| "".into()),
|
||||||
|
);
|
||||||
|
prompt_on();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
MessageView::StateChanged(sc) => {
|
||||||
|
if msg.src() == Some(pipeline.upcast_ref()) {
|
||||||
|
pipeline.debug_to_dot_file(
|
||||||
|
gst::DebugGraphDetails::all(),
|
||||||
|
format!("{}-{:?}-{:?}", pipeline.name(), sc.old(), sc.current()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Error> {
|
||||||
|
gst::init()?;
|
||||||
|
|
||||||
|
println!("h for help");
|
||||||
|
|
||||||
|
let base_time = gst::SystemClock::obtain().time().unwrap();
|
||||||
|
|
||||||
|
let mut producers: HashMap<String, Producer> = HashMap::new();
|
||||||
|
let mut consumers: HashMap<String, Consumer> = HashMap::new();
|
||||||
|
|
||||||
|
let mut stdin = std::io::stdin().lock();
|
||||||
|
loop {
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
prompt_on();
|
||||||
|
|
||||||
|
match stdin.read_line(&mut buf)? {
|
||||||
|
0 => {
|
||||||
|
eprintln!("EOF!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let command: Vec<_> = buf.split_whitespace().collect();
|
||||||
|
|
||||||
|
match command.first() {
|
||||||
|
Some(&"ap") => {
|
||||||
|
if command.len() != 2 {
|
||||||
|
println!("ap <producer_name>: Add a producer");
|
||||||
|
} else {
|
||||||
|
let producer_name = command.get(1).unwrap().to_string();
|
||||||
|
|
||||||
|
if producers.contains_key(&producer_name) {
|
||||||
|
println!("Producer with name {producer_name} already exists!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let producer = create_sink_pipeline(&producer_name)?;
|
||||||
|
monitor_pipeline(&producer.pipeline, base_time)?;
|
||||||
|
|
||||||
|
println!("Added producer with name {producer_name}");
|
||||||
|
|
||||||
|
producers.insert(producer_name, producer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(&"ac") => {
|
||||||
|
if command.len() != 3 {
|
||||||
|
println!("ac <consumer_name> <producer_name>: Add a consumer");
|
||||||
|
} else {
|
||||||
|
let consumer_name = command.get(1).unwrap().to_string();
|
||||||
|
let producer_name = command.get(2).unwrap().to_string();
|
||||||
|
|
||||||
|
if consumers.contains_key(&consumer_name) {
|
||||||
|
println!("Consumer with name {consumer_name} already exists!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let consumer = create_src_pipeline(&producer_name, &consumer_name)?;
|
||||||
|
monitor_pipeline(&consumer.pipeline, base_time)?;
|
||||||
|
|
||||||
|
println!("Added consumer with name {consumer_name} and producer name {producer_name}");
|
||||||
|
|
||||||
|
consumers.insert(consumer_name, consumer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(&"rp") => {
|
||||||
|
if command.len() != 2 {
|
||||||
|
println!("rp <producer_name>: Remove a producer");
|
||||||
|
} else {
|
||||||
|
let producer_name = command.get(1).unwrap().to_string();
|
||||||
|
if let Some(producer) = producers.remove(&producer_name) {
|
||||||
|
let _ = producer.pipeline.set_state(gst::State::Null);
|
||||||
|
println!("Removed producer with name {producer_name}");
|
||||||
|
} else {
|
||||||
|
println!("No producer with name {producer_name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(&"rc") => {
|
||||||
|
if command.len() != 2 {
|
||||||
|
println!("rc <consumer_name>: Remove a consumer");
|
||||||
|
} else {
|
||||||
|
let consumer_name = command.get(1).unwrap().to_string();
|
||||||
|
if let Some(consumer) = consumers.remove(&consumer_name) {
|
||||||
|
let _ = consumer.pipeline.set_state(gst::State::Null);
|
||||||
|
println!("Removed consumer with name {consumer_name}");
|
||||||
|
} else {
|
||||||
|
println!("No consumer with name {consumer_name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(&"cnp") => {
|
||||||
|
if command.len() != 3 {
|
||||||
|
println!("cnp <old_producer_name> <new_producer_name>: Change the name of a producer");
|
||||||
|
} else {
|
||||||
|
let old_producer_name = command.get(1).unwrap().to_string();
|
||||||
|
let producer_name = command.get(2).unwrap().to_string();
|
||||||
|
|
||||||
|
if producers.contains_key(&producer_name) {
|
||||||
|
println!("Producer with name {producer_name} already exists!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(producer) = producers.remove(&old_producer_name) {
|
||||||
|
producer.sink.set_property("producer-name", &producer_name);
|
||||||
|
producer
|
||||||
|
.overlay
|
||||||
|
.set_property("text", format!("Producer: {producer_name}"));
|
||||||
|
println!(
|
||||||
|
"Changed producer name {old_producer_name} -> {producer_name}"
|
||||||
|
);
|
||||||
|
producers.insert(producer_name, producer);
|
||||||
|
} else {
|
||||||
|
println!("No producer with name {old_producer_name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(&"cpn") => {
|
||||||
|
if command.len() != 3 {
|
||||||
|
println!("cpn <consumer_name> <new_producer_name>: Change the producer name for a consumer");
|
||||||
|
} else {
|
||||||
|
let consumer_name = command.get(1).unwrap().to_string();
|
||||||
|
let producer_name = command.get(2).unwrap().to_string();
|
||||||
|
|
||||||
|
if let Some(consumer) = consumers.get_mut(&consumer_name) {
|
||||||
|
consumer.src.set_property("producer-name", &producer_name);
|
||||||
|
println!("Changed producer name for consumer {consumer_name} to {producer_name}");
|
||||||
|
} else {
|
||||||
|
println!("No consumer with name {consumer_name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(&"h") => {
|
||||||
|
println!("h: show this help");
|
||||||
|
println!("ap <producer_name>: Add a producer");
|
||||||
|
println!("ac <consumer_name> <producer_name>: Add a consumer");
|
||||||
|
println!("rp <producer_name>: Remove a producer");
|
||||||
|
println!("rc <consumer_name>: Remove a consumer");
|
||||||
|
println!("cnp <old_producer_name> <new_producer_name>: Change the name of a producer");
|
||||||
|
println!("cpn <consumer_name> <new_producer_name>: Change the producer name for a consumer");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
println!("Unknown command");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, producer) in producers {
|
||||||
|
let _ = producer.pipeline.set_state(gst::State::Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (_, consumer) in consumers {
|
||||||
|
let _ = consumer.pipeline.set_state(gst::State::Null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
44
generic/inter/src/lib.rs
Normal file
44
generic/inter/src/lib.rs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (C) 2023 Mathieu Duponchelle <mathieu@centricular.com>
|
||||||
|
//
|
||||||
|
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||||
|
#![allow(unused_doc_comments)]
|
||||||
|
|
||||||
|
//! GStreamer elements for connecting pipelines in the same process
|
||||||
|
|
||||||
|
mod sink;
|
||||||
|
mod src;
|
||||||
|
mod streamproducer;
|
||||||
|
/**
|
||||||
|
* plugin-rsinter:
|
||||||
|
* @title: Rust inter elements
|
||||||
|
* @short_description: A set of elements for transferring data between pipelines
|
||||||
|
*
|
||||||
|
* This plugin exposes two elements, `intersink` and `intersrc`, that can be
|
||||||
|
* used to transfer data from one pipeline to multiple others in the same
|
||||||
|
* process.
|
||||||
|
*
|
||||||
|
* The elements are implemented using the `StreamProducer` API from
|
||||||
|
* gstreamer-utils.
|
||||||
|
*
|
||||||
|
* Since: plugins-rs-0.11.0
|
||||||
|
*/
|
||||||
|
use gst::glib;
|
||||||
|
|
||||||
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
sink::register(plugin)?;
|
||||||
|
src::register(plugin)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::plugin_define!(
|
||||||
|
rsinter,
|
||||||
|
env!("CARGO_PKG_DESCRIPTION"),
|
||||||
|
plugin_init,
|
||||||
|
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
||||||
|
"MPL-2.0",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_REPOSITORY"),
|
||||||
|
env!("BUILD_REL_DATE")
|
||||||
|
);
|
217
generic/inter/src/sink/imp.rs
Normal file
217
generic/inter/src/sink/imp.rs
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::streamproducer::InterStreamProducer;
|
||||||
|
use anyhow::Error;
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
use gst::subclass::prelude::*;
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
const DEFAULT_PRODUCER_NAME: &str = "default";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Settings {
|
||||||
|
producer_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Settings {
|
||||||
|
producer_name: DEFAULT_PRODUCER_NAME.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
appsink: gst_app::AppSink,
|
||||||
|
sinkpad: gst::GhostPad,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Locking order is field order */
|
||||||
|
pub struct InterSink {
|
||||||
|
settings: Mutex<Settings>,
|
||||||
|
state: Mutex<State>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterSink {
|
||||||
|
fn prepare(&self) -> Result<(), Error> {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
InterStreamProducer::acquire(&settings.producer_name, &state.appsink)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unprepare(&self) {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
InterStreamProducer::release(&settings.producer_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
|
gst::DebugCategory::new(
|
||||||
|
"intersink",
|
||||||
|
gst::DebugColorFlags::empty(),
|
||||||
|
Some("Inter Sink"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for InterSink {
|
||||||
|
const NAME: &'static str = "GstInterSink";
|
||||||
|
type Type = super::InterSink;
|
||||||
|
type ParentType = gst::Bin;
|
||||||
|
|
||||||
|
fn with_class(klass: &Self::Class) -> Self {
|
||||||
|
let templ = klass.pad_template("sink").unwrap();
|
||||||
|
let sinkpad = gst::GhostPad::from_template(&templ);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
settings: Mutex::new(Default::default()),
|
||||||
|
state: Mutex::new(State {
|
||||||
|
appsink: gst_app::AppSink::builder().name("appsink").build(),
|
||||||
|
sinkpad: sinkpad.upcast(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for InterSink {
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![glib::ParamSpecString::builder("producer-name")
|
||||||
|
.nick("Producer Name")
|
||||||
|
.blurb("Producer Name to use")
|
||||||
|
.doc_show_default()
|
||||||
|
.mutable_playing()
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
PROPERTIES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
match pspec.name() {
|
||||||
|
"producer-name" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
let old_producer_name = settings.producer_name.clone();
|
||||||
|
settings.producer_name = value
|
||||||
|
.get::<String>()
|
||||||
|
.unwrap_or_else(|_| DEFAULT_PRODUCER_NAME.to_string());
|
||||||
|
|
||||||
|
if let Some(appsink) = InterStreamProducer::release(&old_producer_name) {
|
||||||
|
if let Err(err) =
|
||||||
|
InterStreamProducer::acquire(&settings.producer_name, &appsink)
|
||||||
|
{
|
||||||
|
drop(settings);
|
||||||
|
gst::error!(CAT, imp: self, "{err}");
|
||||||
|
self.post_error_message(gst::error_msg!(
|
||||||
|
gst::StreamError::Failed,
|
||||||
|
["{err}"]
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
drop(settings);
|
||||||
|
// This is required because StreamProducer obtains the latency
|
||||||
|
// it needs to forward from Latency events, and we need to let the
|
||||||
|
// application know it should recalculate latency to get the event
|
||||||
|
// to travel upstream again
|
||||||
|
self.post_message(gst::message::Latency::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"producer-name" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.producer_name.to_value()
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
obj.set_suppressed_flags(gst::ElementFlags::SINK | gst::ElementFlags::SOURCE);
|
||||||
|
obj.set_element_flags(gst::ElementFlags::SINK);
|
||||||
|
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
obj.add(&state.appsink).unwrap();
|
||||||
|
obj.add_pad(&state.sinkpad).unwrap();
|
||||||
|
state
|
||||||
|
.sinkpad
|
||||||
|
.set_target(Some(&state.appsink.static_pad("sink").unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GstObjectImpl for InterSink {}
|
||||||
|
|
||||||
|
impl ElementImpl for InterSink {
|
||||||
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||||
|
gst::subclass::ElementMetadata::new(
|
||||||
|
"Inter Sink",
|
||||||
|
"Generic/Sink",
|
||||||
|
"Inter Sink",
|
||||||
|
"Mathieu Duponchelle <mathieu@centricular.com>",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(&*ELEMENT_METADATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||||
|
let caps = gst::Caps::new_any();
|
||||||
|
|
||||||
|
let sink_pad_template = gst::PadTemplate::new(
|
||||||
|
"sink",
|
||||||
|
gst::PadDirection::Sink,
|
||||||
|
gst::PadPresence::Always,
|
||||||
|
&caps,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
vec![sink_pad_template]
|
||||||
|
});
|
||||||
|
|
||||||
|
PAD_TEMPLATES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_state(
|
||||||
|
&self,
|
||||||
|
transition: gst::StateChange,
|
||||||
|
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||||
|
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||||
|
|
||||||
|
if transition == gst::StateChange::ReadyToPaused {
|
||||||
|
if let Err(err) = self.prepare() {
|
||||||
|
gst::element_error!(
|
||||||
|
self.obj(),
|
||||||
|
gst::StreamError::Failed,
|
||||||
|
["Failed to prepare: {}", err]
|
||||||
|
);
|
||||||
|
return Err(gst::StateChangeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = self.parent_change_state(transition)?;
|
||||||
|
|
||||||
|
if transition == gst::StateChange::PausedToReady {
|
||||||
|
self.unprepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinImpl for InterSink {}
|
35
generic/inter/src/sink/mod.rs
Normal file
35
generic/inter/src/sink/mod.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use glib::prelude::*;
|
||||||
|
use gst::glib;
|
||||||
|
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:element-intersink
|
||||||
|
*
|
||||||
|
* #intersink is an element that can be used to produce data for
|
||||||
|
* multiple #intersrc elements to consume.
|
||||||
|
*
|
||||||
|
* You can access the underlying appsink element through the static name
|
||||||
|
* "appsink".
|
||||||
|
*
|
||||||
|
* #intersink should not reside in the same pipeline as the #intersrc
|
||||||
|
* that consumes from it, here is an example of how to use those elements
|
||||||
|
* in separate pipelines:
|
||||||
|
*
|
||||||
|
* {{ generic/inter/examples/basic.rs }}
|
||||||
|
*/
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct InterSink(ObjectSubclass<imp::InterSink>) @extends gst::Bin, gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
gst::Element::register(
|
||||||
|
Some(plugin),
|
||||||
|
"intersink",
|
||||||
|
gst::Rank::NONE,
|
||||||
|
InterSink::static_type(),
|
||||||
|
)
|
||||||
|
}
|
203
generic/inter/src/src/imp.rs
Normal file
203
generic/inter/src/src/imp.rs
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use crate::streamproducer::InterStreamProducer;
|
||||||
|
use anyhow::Error;
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
use gst::subclass::prelude::*;
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
const DEFAULT_PRODUCER_NAME: &str = "default";
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Settings {
|
||||||
|
producer_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Settings {
|
||||||
|
producer_name: DEFAULT_PRODUCER_NAME.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
srcpad: gst::GhostPad,
|
||||||
|
appsrc: gst_app::AppSrc,
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Locking order is field order */
|
||||||
|
pub struct InterSrc {
|
||||||
|
settings: Mutex<Settings>,
|
||||||
|
state: Mutex<State>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterSrc {
|
||||||
|
fn prepare(&self) -> Result<(), Error> {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
InterStreamProducer::subscribe(&settings.producer_name, &state.appsrc);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unprepare(&self) {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
InterStreamProducer::unsubscribe(&settings.producer_name, &state.appsrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
|
gst::DebugCategory::new("intersrc", gst::DebugColorFlags::empty(), Some("Inter Src"))
|
||||||
|
});
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for InterSrc {
|
||||||
|
const NAME: &'static str = "GstInterSrc";
|
||||||
|
|
||||||
|
type Type = super::InterSrc;
|
||||||
|
type ParentType = gst::Bin;
|
||||||
|
|
||||||
|
fn with_class(klass: &Self::Class) -> Self {
|
||||||
|
let templ = klass.pad_template("src").unwrap();
|
||||||
|
let srcpad = gst::GhostPad::from_template(&templ);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
settings: Mutex::new(Default::default()),
|
||||||
|
state: Mutex::new(State {
|
||||||
|
srcpad: srcpad.upcast(),
|
||||||
|
appsrc: gst_app::AppSrc::builder().name("appsrc").build(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for InterSrc {
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![glib::ParamSpecString::builder("producer-name")
|
||||||
|
.nick("Producer Name")
|
||||||
|
.blurb("Producer Name to consume from")
|
||||||
|
.doc_show_default()
|
||||||
|
.mutable_playing()
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
PROPERTIES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
match pspec.name() {
|
||||||
|
"producer-name" => {
|
||||||
|
let mut settings = self.settings.lock().unwrap();
|
||||||
|
let old_producer_name = settings.producer_name.clone();
|
||||||
|
settings.producer_name = value
|
||||||
|
.get::<String>()
|
||||||
|
.unwrap_or_else(|_| DEFAULT_PRODUCER_NAME.to_string());
|
||||||
|
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
if InterStreamProducer::unsubscribe(&old_producer_name, &state.appsrc) {
|
||||||
|
InterStreamProducer::subscribe(&settings.producer_name, &state.appsrc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"producer-name" => {
|
||||||
|
let settings = self.settings.lock().unwrap();
|
||||||
|
settings.producer_name.to_value()
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
obj.set_suppressed_flags(gst::ElementFlags::SINK | gst::ElementFlags::SOURCE);
|
||||||
|
obj.set_element_flags(gst::ElementFlags::SOURCE);
|
||||||
|
|
||||||
|
let state = self.state.lock().unwrap();
|
||||||
|
gst_utils::StreamProducer::configure_consumer(&state.appsrc);
|
||||||
|
obj.add(&state.appsrc).unwrap();
|
||||||
|
obj.add_pad(&state.srcpad).unwrap();
|
||||||
|
state
|
||||||
|
.srcpad
|
||||||
|
.set_target(Some(&state.appsrc.static_pad("src").unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GstObjectImpl for InterSrc {}
|
||||||
|
|
||||||
|
impl ElementImpl for InterSrc {
|
||||||
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||||
|
gst::subclass::ElementMetadata::new(
|
||||||
|
"Inter Src",
|
||||||
|
"Generic/Src",
|
||||||
|
"Inter Src",
|
||||||
|
"Mathieu Duponchelle <mathieu@centricular.com>",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(&*ELEMENT_METADATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||||
|
let caps = gst::Caps::new_any();
|
||||||
|
|
||||||
|
let src_pad_template = gst::PadTemplate::new(
|
||||||
|
"src",
|
||||||
|
gst::PadDirection::Src,
|
||||||
|
gst::PadPresence::Always,
|
||||||
|
&caps,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
vec![src_pad_template]
|
||||||
|
});
|
||||||
|
|
||||||
|
PAD_TEMPLATES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_state(
|
||||||
|
&self,
|
||||||
|
transition: gst::StateChange,
|
||||||
|
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||||
|
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||||
|
|
||||||
|
if transition == gst::StateChange::ReadyToPaused {
|
||||||
|
if let Err(err) = self.prepare() {
|
||||||
|
gst::element_error!(
|
||||||
|
self.obj(),
|
||||||
|
gst::StreamError::Failed,
|
||||||
|
["Failed to prepare: {}", err]
|
||||||
|
);
|
||||||
|
return Err(gst::StateChangeError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let ret = self.parent_change_state(transition)?;
|
||||||
|
|
||||||
|
if transition == gst::StateChange::PausedToReady {
|
||||||
|
self.unprepare();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinImpl for InterSrc {}
|
34
generic/inter/src/src/mod.rs
Normal file
34
generic/inter/src/src/mod.rs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use glib::prelude::*;
|
||||||
|
use gst::glib;
|
||||||
|
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:element-intersrc
|
||||||
|
*
|
||||||
|
* #intersrc is an element that can be used to consume data from an #intersink.
|
||||||
|
*
|
||||||
|
* You can access the underlying appsrc element through the static name
|
||||||
|
* "appsrc".
|
||||||
|
*
|
||||||
|
* #intersrc should not reside in the same pipeline as the #intersink
|
||||||
|
* that it consumes from, here is an example of how to use those elements
|
||||||
|
* in separate pipelines:
|
||||||
|
*
|
||||||
|
* {{ generic/inter/examples/basic.rs }}
|
||||||
|
*/
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct InterSrc(ObjectSubclass<imp::InterSrc>) @extends gst::Bin, gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
gst::Element::register(
|
||||||
|
Some(plugin),
|
||||||
|
"intersrc",
|
||||||
|
gst::Rank::NONE,
|
||||||
|
InterSrc::static_type(),
|
||||||
|
)
|
||||||
|
}
|
159
generic/inter/src/streamproducer/mod.rs
Normal file
159
generic/inter/src/streamproducer/mod.rs
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
use gst::prelude::*;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Error};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
pub enum InterStreamProducer {
|
||||||
|
Pending {
|
||||||
|
consumers: HashSet<gst_app::AppSrc>,
|
||||||
|
},
|
||||||
|
Active {
|
||||||
|
producer: gst_utils::StreamProducer,
|
||||||
|
links: HashMap<gst_app::AppSrc, gst_utils::ConsumptionLink>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
static PRODUCERS: Lazy<Mutex<HashMap<String, InterStreamProducer>>> =
|
||||||
|
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||||
|
|
||||||
|
fn toplevel(obj: &gst::Object) -> gst::Object {
|
||||||
|
if let Some(parent) = obj.parent() {
|
||||||
|
toplevel(&parent)
|
||||||
|
} else {
|
||||||
|
obj.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ensure_different_toplevel(producer: &gst_app::AppSink, consumer: &gst_app::AppSrc) {
|
||||||
|
let top_a = toplevel(producer.upcast_ref());
|
||||||
|
let top_b = toplevel(consumer.upcast_ref());
|
||||||
|
|
||||||
|
if top_a == top_b {
|
||||||
|
gst::glib::g_critical!(
|
||||||
|
"gstrsinter",
|
||||||
|
"Intersink with appsink {} should not share the same toplevel bin \
|
||||||
|
as intersrc with appsrc {}, this results in loops in latency calculation",
|
||||||
|
producer.name(),
|
||||||
|
consumer.name()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterStreamProducer {
|
||||||
|
pub fn acquire(
|
||||||
|
name: &str,
|
||||||
|
appsink: &gst_app::AppSink,
|
||||||
|
) -> Result<gst_utils::StreamProducer, Error> {
|
||||||
|
let mut producers = PRODUCERS.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(producer) = producers.remove(name) {
|
||||||
|
match producer {
|
||||||
|
InterStreamProducer::Pending { consumers } => {
|
||||||
|
let producer = gst_utils::StreamProducer::from(appsink);
|
||||||
|
let mut links = HashMap::new();
|
||||||
|
|
||||||
|
for consumer in consumers {
|
||||||
|
ensure_different_toplevel(appsink, &consumer);
|
||||||
|
|
||||||
|
let link = producer
|
||||||
|
.add_consumer(&consumer)
|
||||||
|
.expect("consumer should not have already been added");
|
||||||
|
links.insert(consumer, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
producers.insert(
|
||||||
|
name.to_string(),
|
||||||
|
InterStreamProducer::Active {
|
||||||
|
producer: producer.clone(),
|
||||||
|
links,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(producer)
|
||||||
|
}
|
||||||
|
InterStreamProducer::Active { .. } => {
|
||||||
|
producers.insert(name.to_string(), producer);
|
||||||
|
|
||||||
|
Err(anyhow!(
|
||||||
|
"An active producer already exists with name {}",
|
||||||
|
name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let producer = gst_utils::StreamProducer::from(appsink);
|
||||||
|
|
||||||
|
producers.insert(
|
||||||
|
name.to_string(),
|
||||||
|
InterStreamProducer::Active {
|
||||||
|
producer: producer.clone(),
|
||||||
|
links: HashMap::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(producer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(name: &str) -> Option<gst_app::AppSink> {
|
||||||
|
let mut producers = PRODUCERS.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(producer) = producers.remove(name) {
|
||||||
|
match producer {
|
||||||
|
InterStreamProducer::Pending { .. } => None,
|
||||||
|
InterStreamProducer::Active { links, producer } => {
|
||||||
|
producers.insert(
|
||||||
|
name.to_string(),
|
||||||
|
InterStreamProducer::Pending {
|
||||||
|
consumers: links.into_keys().collect(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(producer.appsink().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subscribe(name: &str, consumer: &gst_app::AppSrc) {
|
||||||
|
let mut producers = PRODUCERS.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(producer) = producers.get_mut(name) {
|
||||||
|
match producer {
|
||||||
|
InterStreamProducer::Pending { consumers } => {
|
||||||
|
consumers.insert(consumer.clone());
|
||||||
|
}
|
||||||
|
InterStreamProducer::Active { producer, links } => {
|
||||||
|
ensure_different_toplevel(producer.appsink(), consumer);
|
||||||
|
|
||||||
|
let link = producer
|
||||||
|
.add_consumer(consumer)
|
||||||
|
.expect("consumer should not already have been added");
|
||||||
|
links.insert(consumer.clone(), link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let producer = InterStreamProducer::Pending {
|
||||||
|
consumers: [consumer.clone()].into(),
|
||||||
|
};
|
||||||
|
producers.insert(name.to_string(), producer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsubscribe(name: &str, consumer: &gst_app::AppSrc) -> bool {
|
||||||
|
let mut producers = PRODUCERS.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(producer) = producers.get_mut(name) {
|
||||||
|
match producer {
|
||||||
|
InterStreamProducer::Pending { consumers } => consumers.remove(consumer),
|
||||||
|
InterStreamProducer::Active { links, .. } => links.remove(consumer).is_some(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
generic/inter/tests/inter.rs
Normal file
138
generic/inter/tests/inter.rs
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use gst::prelude::*;
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
fn init() {
|
||||||
|
use std::sync::Once;
|
||||||
|
static INIT: Once = Once::new();
|
||||||
|
|
||||||
|
INIT.call_once(|| {
|
||||||
|
gst::init().unwrap();
|
||||||
|
gstrsinter::plugin_register_static().unwrap();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_consumer(producer_name: &str) -> gst_check::Harness {
|
||||||
|
let mut hc = gst_check::Harness::new("intersrc");
|
||||||
|
|
||||||
|
hc.element()
|
||||||
|
.unwrap()
|
||||||
|
.set_property("producer-name", producer_name);
|
||||||
|
hc.play();
|
||||||
|
|
||||||
|
hc
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_producer(producer_name: &str) -> (gst::Pad, gst::Element) {
|
||||||
|
let element = gst::ElementFactory::make("intersink").build().unwrap();
|
||||||
|
|
||||||
|
element.set_property("producer-name", producer_name);
|
||||||
|
element.set_state(gst::State::Playing).unwrap();
|
||||||
|
|
||||||
|
let sinkpad = element.static_pad("sink").unwrap();
|
||||||
|
let srcpad = gst::Pad::new(gst::PadDirection::Src);
|
||||||
|
srcpad.set_active(true).unwrap();
|
||||||
|
srcpad.link(&sinkpad).unwrap();
|
||||||
|
|
||||||
|
srcpad.push_event(gst::event::StreamStart::builder("foo").build());
|
||||||
|
srcpad
|
||||||
|
.push_event(gst::event::Caps::builder(&gst::Caps::builder("video/x-raw").build()).build());
|
||||||
|
srcpad.push_event(
|
||||||
|
gst::event::Segment::builder(&gst::FormattedSegment::<gst::format::Time>::new()).build(),
|
||||||
|
);
|
||||||
|
|
||||||
|
(srcpad, element)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_one(srcpad: &gst::Pad, pts: gst::ClockTime) {
|
||||||
|
let mut inbuf = gst::Buffer::with_size(1).unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let buf = inbuf.get_mut().unwrap();
|
||||||
|
buf.set_pts(pts);
|
||||||
|
}
|
||||||
|
|
||||||
|
srcpad.push(inbuf).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_forward_one_buffer() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
let mut hc = start_consumer("p1");
|
||||||
|
let (srcpad, element) = start_producer("p1");
|
||||||
|
|
||||||
|
push_one(&srcpad, gst::ClockTime::from_nseconds(1));
|
||||||
|
|
||||||
|
let outbuf = hc.pull().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(outbuf.pts(), Some(gst::ClockTime::from_nseconds(1)));
|
||||||
|
|
||||||
|
element.set_state(gst::State::Null).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_change_name_of_producer() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
let mut hc1 = start_consumer("p1");
|
||||||
|
let mut hc2 = start_consumer("p2");
|
||||||
|
let (srcpad, element) = start_producer("p1");
|
||||||
|
|
||||||
|
/* Once this returns, the buffer should have been dispatched only to hc1 */
|
||||||
|
push_one(&srcpad, gst::ClockTime::from_nseconds(1));
|
||||||
|
let outbuf = hc1.pull().unwrap();
|
||||||
|
assert_eq!(outbuf.pts(), Some(gst::ClockTime::from_nseconds(1)));
|
||||||
|
|
||||||
|
element.set_property("producer-name", "p2");
|
||||||
|
|
||||||
|
/* This should only get dispatched to hc2, and it should be its first buffer */
|
||||||
|
push_one(&srcpad, gst::ClockTime::from_nseconds(2));
|
||||||
|
let outbuf = hc2.pull().unwrap();
|
||||||
|
assert_eq!(outbuf.pts(), Some(gst::ClockTime::from_nseconds(2)));
|
||||||
|
|
||||||
|
element.set_property("producer-name", "p1");
|
||||||
|
|
||||||
|
/* Back to hc1, which should not see the buffer we pushed in the previous step */
|
||||||
|
push_one(&srcpad, gst::ClockTime::from_nseconds(3));
|
||||||
|
let outbuf = hc1.pull().unwrap();
|
||||||
|
assert_eq!(outbuf.pts(), Some(gst::ClockTime::from_nseconds(3)));
|
||||||
|
|
||||||
|
element.set_state(gst::State::Null).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[serial]
|
||||||
|
fn test_change_producer_name() {
|
||||||
|
init();
|
||||||
|
|
||||||
|
let mut hc = start_consumer("p1");
|
||||||
|
let (srcpad1, element1) = start_producer("p1");
|
||||||
|
let (srcpad2, element2) = start_producer("p2");
|
||||||
|
|
||||||
|
/* This buffer should be dispatched to no consumer */
|
||||||
|
push_one(&srcpad2, gst::ClockTime::from_nseconds(1));
|
||||||
|
|
||||||
|
/* This one should be dispatched to hc, and it should be its first buffer */
|
||||||
|
push_one(&srcpad1, gst::ClockTime::from_nseconds(2));
|
||||||
|
let outbuf = hc.pull().unwrap();
|
||||||
|
assert_eq!(outbuf.pts(), Some(gst::ClockTime::from_nseconds(2)));
|
||||||
|
|
||||||
|
hc.element().unwrap().set_property("producer-name", "p2");
|
||||||
|
|
||||||
|
/* This buffer should be dispatched to no consumer */
|
||||||
|
push_one(&srcpad1, gst::ClockTime::from_nseconds(3));
|
||||||
|
|
||||||
|
/* This one should be dispatched to hc, and it should be its next buffer */
|
||||||
|
push_one(&srcpad2, gst::ClockTime::from_nseconds(4));
|
||||||
|
let outbuf = hc.pull().unwrap();
|
||||||
|
assert_eq!(outbuf.pts(), Some(gst::ClockTime::from_nseconds(4)));
|
||||||
|
|
||||||
|
element1.set_state(gst::State::Null).unwrap();
|
||||||
|
element2.set_state(gst::State::Null).unwrap();
|
||||||
|
}
|
43
generic/originalbuffer/Cargo.toml
Normal file
43
generic/originalbuffer/Cargo.toml
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
[package]
|
||||||
|
name = "gst-plugin-originalbuffer"
|
||||||
|
version.workspace = true
|
||||||
|
authors = ["Olivier Crête <olivier.crete@collabora.com>"]
|
||||||
|
repository.workspace = true
|
||||||
|
license = "MPL-2.0"
|
||||||
|
description = "GStreamer Origin buffer meta Plugin"
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
glib.workspace = true
|
||||||
|
gst.workspace = true
|
||||||
|
gst-video.workspace = true
|
||||||
|
atomic_refcell = "0.1"
|
||||||
|
once_cell.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "gstoriginalbuffer"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
static = []
|
||||||
|
capi = []
|
||||||
|
doc = ["gst/v1_16"]
|
||||||
|
|
||||||
|
[package.metadata.capi]
|
||||||
|
min_version = "0.9.21"
|
||||||
|
|
||||||
|
[package.metadata.capi.header]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[package.metadata.capi.library]
|
||||||
|
install_subdir = "gstreamer-1.0"
|
||||||
|
versioning = false
|
||||||
|
import_library = false
|
||||||
|
|
||||||
|
[package.metadata.capi.pkg_config]
|
||||||
|
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
3
generic/originalbuffer/build.rs
Normal file
3
generic/originalbuffer/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() {
|
||||||
|
gst_plugin_version_helper::info()
|
||||||
|
}
|
38
generic/originalbuffer/src/lib.rs
Normal file
38
generic/originalbuffer/src/lib.rs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright (C) 2024 Collabora Ltd
|
||||||
|
// @author: Olivier Crête <olivier.crete@collabora.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
#![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* plugin-originalbuffer:
|
||||||
|
*
|
||||||
|
* Since: plugins-rs-0.12 */
|
||||||
|
use gst::glib;
|
||||||
|
|
||||||
|
mod originalbuffermeta;
|
||||||
|
mod originalbufferrestore;
|
||||||
|
mod originalbuffersave;
|
||||||
|
|
||||||
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
originalbuffersave::register(plugin)?;
|
||||||
|
originalbufferrestore::register(plugin)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::plugin_define!(
|
||||||
|
originalbuffer,
|
||||||
|
env!("CARGO_PKG_DESCRIPTION"),
|
||||||
|
plugin_init,
|
||||||
|
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
||||||
|
"MPL",
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_NAME"),
|
||||||
|
env!("CARGO_PKG_REPOSITORY"),
|
||||||
|
env!("BUILD_REL_DATE")
|
||||||
|
);
|
199
generic/originalbuffer/src/originalbuffermeta.rs
Normal file
199
generic/originalbuffer/src/originalbuffermeta.rs
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
// Copyright (C) 2024 Collabora Ltd
|
||||||
|
// @author: Olivier Crête <olivier.crete@collabora.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use gst::prelude::*;
|
||||||
|
use std::fmt;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub struct OriginalBufferMeta(imp::OriginalBufferMeta);
|
||||||
|
|
||||||
|
unsafe impl Send for OriginalBufferMeta {}
|
||||||
|
unsafe impl Sync for OriginalBufferMeta {}
|
||||||
|
|
||||||
|
impl OriginalBufferMeta {
|
||||||
|
pub fn add(
|
||||||
|
buffer: &mut gst::BufferRef,
|
||||||
|
original: gst::Buffer,
|
||||||
|
caps: Option<gst::Caps>,
|
||||||
|
) -> gst::MetaRefMut<'_, Self, gst::meta::Standalone> {
|
||||||
|
unsafe {
|
||||||
|
// Manually dropping because gst_buffer_add_meta() takes ownership of the
|
||||||
|
// content of the struct
|
||||||
|
let mut params =
|
||||||
|
mem::ManuallyDrop::new(imp::OriginalBufferMetaParams { original, caps });
|
||||||
|
|
||||||
|
let meta = gst::ffi::gst_buffer_add_meta(
|
||||||
|
buffer.as_mut_ptr(),
|
||||||
|
imp::original_buffer_meta_get_info(),
|
||||||
|
&mut *params as *mut imp::OriginalBufferMetaParams as gst::glib::ffi::gpointer,
|
||||||
|
) as *mut imp::OriginalBufferMeta;
|
||||||
|
|
||||||
|
Self::from_mut_ptr(buffer, meta)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace(&mut self, original: gst::Buffer, caps: Option<gst::Caps>) {
|
||||||
|
self.0.original = Some(original);
|
||||||
|
self.0.caps = caps;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn original(&self) -> &gst::Buffer {
|
||||||
|
self.0.original.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn caps(&self) -> &gst::Caps {
|
||||||
|
self.0.caps.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl MetaAPI for OriginalBufferMeta {
|
||||||
|
type GstType = imp::OriginalBufferMeta;
|
||||||
|
|
||||||
|
fn meta_api() -> gst::glib::Type {
|
||||||
|
imp::original_buffer_meta_api_get_type()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for OriginalBufferMeta {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("OriginalBufferMeta")
|
||||||
|
.field("buffer", &self.original())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use gst::glib::translate::*;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::mem;
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
pub(super) struct OriginalBufferMetaParams {
|
||||||
|
pub original: gst::Buffer,
|
||||||
|
pub caps: Option<gst::Caps>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct OriginalBufferMeta {
|
||||||
|
parent: gst::ffi::GstMeta,
|
||||||
|
pub(super) original: Option<gst::Buffer>,
|
||||||
|
pub(super) caps: Option<gst::Caps>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn original_buffer_meta_api_get_type() -> glib::Type {
|
||||||
|
static TYPE: Lazy<glib::Type> = Lazy::new(|| unsafe {
|
||||||
|
let t = from_glib(gst::ffi::gst_meta_api_type_register(
|
||||||
|
b"GstOriginalBufferMetaAPI\0".as_ptr() as *const _,
|
||||||
|
[ptr::null::<std::os::raw::c_char>()].as_ptr() as *mut *const _,
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_ne!(t, glib::Type::INVALID);
|
||||||
|
|
||||||
|
t
|
||||||
|
});
|
||||||
|
|
||||||
|
*TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn original_buffer_meta_init(
|
||||||
|
meta: *mut gst::ffi::GstMeta,
|
||||||
|
params: glib::ffi::gpointer,
|
||||||
|
_buffer: *mut gst::ffi::GstBuffer,
|
||||||
|
) -> glib::ffi::gboolean {
|
||||||
|
assert!(!params.is_null());
|
||||||
|
let meta = &mut *(meta as *mut OriginalBufferMeta);
|
||||||
|
let params = ptr::read(params as *const OriginalBufferMetaParams);
|
||||||
|
|
||||||
|
let OriginalBufferMetaParams { original, caps } = params;
|
||||||
|
|
||||||
|
ptr::write(&mut meta.original, Some(original));
|
||||||
|
ptr::write(&mut meta.caps, caps);
|
||||||
|
|
||||||
|
true.into_glib()
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn original_buffer_meta_free(
|
||||||
|
meta: *mut gst::ffi::GstMeta,
|
||||||
|
_buffer: *mut gst::ffi::GstBuffer,
|
||||||
|
) {
|
||||||
|
let meta = &mut *(meta as *mut OriginalBufferMeta);
|
||||||
|
meta.original = None;
|
||||||
|
meta.caps = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn original_buffer_meta_transform(
|
||||||
|
dest: *mut gst::ffi::GstBuffer,
|
||||||
|
meta: *mut gst::ffi::GstMeta,
|
||||||
|
_buffer: *mut gst::ffi::GstBuffer,
|
||||||
|
_type_: glib::ffi::GQuark,
|
||||||
|
_data: glib::ffi::gpointer,
|
||||||
|
) -> glib::ffi::gboolean {
|
||||||
|
let dest = gst::BufferRef::from_mut_ptr(dest);
|
||||||
|
let meta = &*(meta as *const OriginalBufferMeta);
|
||||||
|
|
||||||
|
if dest.meta::<super::OriginalBufferMeta>().is_some() {
|
||||||
|
return true.into_glib();
|
||||||
|
}
|
||||||
|
// We don't store a ref in the meta if it's self-refencing, but we add it
|
||||||
|
// when copying the meta to another buffer.
|
||||||
|
super::OriginalBufferMeta::add(
|
||||||
|
dest,
|
||||||
|
meta.original.as_ref().unwrap().clone(),
|
||||||
|
meta.caps.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
|
true.into_glib()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn original_buffer_meta_get_info() -> *const gst::ffi::GstMetaInfo {
|
||||||
|
struct MetaInfo(ptr::NonNull<gst::ffi::GstMetaInfo>);
|
||||||
|
unsafe impl Send for MetaInfo {}
|
||||||
|
unsafe impl Sync for MetaInfo {}
|
||||||
|
|
||||||
|
static META_INFO: Lazy<MetaInfo> = Lazy::new(|| unsafe {
|
||||||
|
MetaInfo(
|
||||||
|
ptr::NonNull::new(gst::ffi::gst_meta_register(
|
||||||
|
original_buffer_meta_api_get_type().into_glib(),
|
||||||
|
b"OriginalBufferMeta\0".as_ptr() as *const _,
|
||||||
|
mem::size_of::<OriginalBufferMeta>(),
|
||||||
|
Some(original_buffer_meta_init),
|
||||||
|
Some(original_buffer_meta_free),
|
||||||
|
Some(original_buffer_meta_transform),
|
||||||
|
) as *mut gst::ffi::GstMetaInfo)
|
||||||
|
.expect("Failed to register meta API"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
META_INFO.0.as_ptr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test() {
|
||||||
|
gst::init().unwrap();
|
||||||
|
let mut b = gst::Buffer::with_size(10).unwrap();
|
||||||
|
let caps = gst::Caps::new_empty_simple("video/x-raw");
|
||||||
|
let copy = b.copy();
|
||||||
|
let m = OriginalBufferMeta::add(b.make_mut(), copy, Some(caps.clone()));
|
||||||
|
assert_eq!(m.caps(), caps.as_ref());
|
||||||
|
assert_eq!(m.original().clone(), b);
|
||||||
|
let b2: gst::Buffer = b.copy_deep().unwrap();
|
||||||
|
let m = b.meta::<OriginalBufferMeta>().unwrap();
|
||||||
|
assert_eq!(m.caps(), caps.as_ref());
|
||||||
|
assert_eq!(m.original(), &b);
|
||||||
|
let m = b2.meta::<OriginalBufferMeta>().unwrap();
|
||||||
|
assert_eq!(m.caps(), caps.as_ref());
|
||||||
|
assert_eq!(m.original(), &b);
|
||||||
|
let b3: gst::Buffer = b2.copy_deep().unwrap();
|
||||||
|
drop(b2);
|
||||||
|
let m = b3.meta::<OriginalBufferMeta>().unwrap();
|
||||||
|
assert_eq!(m.caps(), caps.as_ref());
|
||||||
|
assert_eq!(m.original(), &b);
|
||||||
|
}
|
315
generic/originalbuffer/src/originalbufferrestore/imp.rs
Normal file
315
generic/originalbuffer/src/originalbufferrestore/imp.rs
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
// Copyright (C) 2024 Collabora Ltd
|
||||||
|
// @author: Olivier Crête <olivier.crete@collabora.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use gst::glib;
|
||||||
|
use gst::subclass::prelude::*;
|
||||||
|
use gst_video::prelude::*;
|
||||||
|
|
||||||
|
use atomic_refcell::AtomicRefCell;
|
||||||
|
|
||||||
|
use crate::originalbuffermeta;
|
||||||
|
use crate::originalbuffermeta::OriginalBufferMeta;
|
||||||
|
|
||||||
|
struct CapsState {
|
||||||
|
caps: gst::Caps,
|
||||||
|
vinfo: Option<gst_video::VideoInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CapsState {
|
||||||
|
fn default() -> Self {
|
||||||
|
CapsState {
|
||||||
|
caps: gst::Caps::new_empty(),
|
||||||
|
vinfo: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct State {
|
||||||
|
sinkpad_caps: CapsState,
|
||||||
|
meta_caps: CapsState,
|
||||||
|
sinkpad_segment: Option<gst::Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct OriginalBufferRestore {
|
||||||
|
state: AtomicRefCell<State>,
|
||||||
|
src_pad: gst::Pad,
|
||||||
|
sink_pad: gst::Pad,
|
||||||
|
}
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
#[cfg(unused_code)]
|
||||||
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
|
gst::DebugCategory::new(
|
||||||
|
"originalbufferrestore",
|
||||||
|
gst::DebugColorFlags::empty(),
|
||||||
|
Some("Restore Original buffer as meta"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for OriginalBufferRestore {
|
||||||
|
const NAME: &'static str = "GstOriginalBufferRestore";
|
||||||
|
type Type = super::OriginalBufferRestore;
|
||||||
|
type ParentType = gst::Element;
|
||||||
|
|
||||||
|
fn with_class(klass: &Self::Class) -> Self {
|
||||||
|
let sink_templ = klass.pad_template("sink").unwrap();
|
||||||
|
let src_templ = klass.pad_template("src").unwrap();
|
||||||
|
|
||||||
|
let sink_pad = gst::Pad::builder_from_template(&sink_templ)
|
||||||
|
.chain_function(|pad, parent, buffer| {
|
||||||
|
OriginalBufferRestore::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| Err(gst::FlowError::Error),
|
||||||
|
|obj| obj.sink_chain(pad, buffer),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.event_function(|pad, parent, event| {
|
||||||
|
OriginalBufferRestore::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|obj| obj.sink_event(pad, parent, event),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.query_function(|pad, parent, query| {
|
||||||
|
OriginalBufferRestore::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|obj| obj.sink_query(pad, parent, query),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let src_pad = gst::Pad::builder_from_template(&src_templ)
|
||||||
|
.event_function(|pad, parent, event| {
|
||||||
|
OriginalBufferRestore::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|obj| obj.src_event(pad, parent, event),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
src_pad,
|
||||||
|
sink_pad,
|
||||||
|
state: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for OriginalBufferRestore {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
let obj = self.obj();
|
||||||
|
obj.add_pad(&self.sink_pad).unwrap();
|
||||||
|
obj.add_pad(&self.src_pad).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GstObjectImpl for OriginalBufferRestore {}
|
||||||
|
|
||||||
|
impl ElementImpl for OriginalBufferRestore {
|
||||||
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||||
|
gst::subclass::ElementMetadata::new(
|
||||||
|
"Original Buffer Restore",
|
||||||
|
"Generic",
|
||||||
|
"Restores a reference to the buffer in a meta",
|
||||||
|
"Olivier Crête <olivier.crete@collabora.com>",
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(&*ELEMENT_METADATA)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
|
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||||
|
let caps = gst::Caps::new_any();
|
||||||
|
let src_pad_template = gst::PadTemplate::new(
|
||||||
|
"src",
|
||||||
|
gst::PadDirection::Src,
|
||||||
|
gst::PadPresence::Always,
|
||||||
|
&caps,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let sink_pad_template = gst::PadTemplate::new(
|
||||||
|
"sink",
|
||||||
|
gst::PadDirection::Sink,
|
||||||
|
gst::PadPresence::Always,
|
||||||
|
&caps,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
vec![src_pad_template, sink_pad_template]
|
||||||
|
});
|
||||||
|
|
||||||
|
PAD_TEMPLATES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_state(
|
||||||
|
&self,
|
||||||
|
transition: gst::StateChange,
|
||||||
|
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||||
|
let ret = self.parent_change_state(transition)?;
|
||||||
|
if transition == gst::StateChange::PausedToReady {
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
*state = State::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OriginalBufferRestore {
|
||||||
|
fn sink_event(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
parent: Option<&impl IsA<gst::Object>>,
|
||||||
|
event: gst::Event,
|
||||||
|
) -> bool {
|
||||||
|
match event.view() {
|
||||||
|
gst::EventView::Caps(e) => {
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
|
||||||
|
let caps = e.caps_owned();
|
||||||
|
let vinfo = gst_video::VideoInfo::from_caps(&caps).ok();
|
||||||
|
state.sinkpad_caps = CapsState { caps, vinfo };
|
||||||
|
true
|
||||||
|
}
|
||||||
|
gst::EventView::Segment(_) => {
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
state.sinkpad_segment = Some(event);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => gst::Pad::event_default(pad, parent, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn src_event(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
parent: Option<&impl IsA<gst::Object>>,
|
||||||
|
event: gst::Event,
|
||||||
|
) -> bool {
|
||||||
|
if event.type_() == gst::EventType::Reconfigure
|
||||||
|
|| event.has_name("gst-original-buffer-forward-upstream-event")
|
||||||
|
{
|
||||||
|
let s = gst::Structure::builder("gst-original-buffer-forward-upstream-event")
|
||||||
|
.field("event", event)
|
||||||
|
.build();
|
||||||
|
let event = gst::event::CustomUpstream::new(s);
|
||||||
|
self.sink_pad.push_event(event)
|
||||||
|
} else {
|
||||||
|
gst::Pad::event_default(pad, parent, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_query(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
parent: Option<&impl IsA<gst::Object>>,
|
||||||
|
query: &mut gst::QueryRef,
|
||||||
|
) -> bool {
|
||||||
|
if let gst::QueryViewMut::Custom(_) = query.view_mut() {
|
||||||
|
let s = query.structure_mut();
|
||||||
|
if s.has_name("gst-original-buffer-forward-query") {
|
||||||
|
if let Ok(mut q) = s.get::<gst::Query>("query") {
|
||||||
|
s.remove_field("query");
|
||||||
|
assert!(q.is_writable());
|
||||||
|
let res = self.src_pad.peer_query(q.get_mut().unwrap());
|
||||||
|
|
||||||
|
s.set("query", q);
|
||||||
|
s.set("result", res);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::Pad::query_default(pad, parent, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_chain(
|
||||||
|
&self,
|
||||||
|
_pad: &gst::Pad,
|
||||||
|
inbuf: gst::Buffer,
|
||||||
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
|
let Some(ometa) = inbuf.meta::<OriginalBufferMeta>() else {
|
||||||
|
//gst::element_warning!(self, gst::StreamError::Failed, ["Buffer {} is missing the GstOriginalBufferMeta, put originalbuffersave upstream in your pipeline", buffer]);
|
||||||
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
|
};
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
let meta_caps = &mut state.meta_caps;
|
||||||
|
if &meta_caps.caps != ometa.caps() {
|
||||||
|
if !self.src_pad.push_event(gst::event::Caps::new(ometa.caps())) {
|
||||||
|
return Err(gst::FlowError::NotNegotiated);
|
||||||
|
}
|
||||||
|
meta_caps.caps = ometa.caps().clone();
|
||||||
|
meta_caps.vinfo = gst_video::VideoInfo::from_caps(&meta_caps.caps).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut outbuf = ometa.original().copy();
|
||||||
|
|
||||||
|
inbuf
|
||||||
|
.copy_into(
|
||||||
|
outbuf.make_mut(),
|
||||||
|
gst::BufferCopyFlags::TIMESTAMPS | gst::BufferCopyFlags::FLAGS,
|
||||||
|
..,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for meta in inbuf.iter_meta::<gst::Meta>() {
|
||||||
|
if meta.api() == originalbuffermeta::OriginalBufferMeta::meta_api() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.has_tag::<gst::meta::tags::Memory>()
|
||||||
|
|| meta.has_tag::<gst::meta::tags::MemoryReference>()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.has_tag::<gst_video::video_meta::tags::Size>() {
|
||||||
|
if let (Some(ref meta_vinfo), Some(ref sink_vinfo)) =
|
||||||
|
(&state.meta_caps.vinfo, &state.sinkpad_caps.vinfo)
|
||||||
|
{
|
||||||
|
if (meta_vinfo.width() != sink_vinfo.width()
|
||||||
|
|| meta_vinfo.height() != sink_vinfo.height())
|
||||||
|
&& meta
|
||||||
|
.transform(
|
||||||
|
outbuf.make_mut(),
|
||||||
|
&gst_video::video_meta::VideoMetaTransformScale::new(
|
||||||
|
sink_vinfo, meta_vinfo,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = meta.transform(
|
||||||
|
outbuf.make_mut(),
|
||||||
|
&gst::meta::MetaTransformCopy::new(false, ..),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(event) = state.sinkpad_segment.take() {
|
||||||
|
if !self.src_pad.push_event(event) {
|
||||||
|
return Err(gst::FlowError::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src_pad.push(outbuf)
|
||||||
|
}
|
||||||
|
}
|
31
generic/originalbuffer/src/originalbufferrestore/mod.rs
Normal file
31
generic/originalbuffer/src/originalbufferrestore/mod.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (C) 2024 Collabora Ltd
|
||||||
|
// @author: Olivier Crête <olivier.crete@collabora.com>
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||||
|
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||||
|
// <https://mozilla.org/MPL/2.0/>.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SECTION:element-originalbufferrestore
|
||||||
|
*
|
||||||
|
* See originalbuffersave for details
|
||||||
|
*/
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct OriginalBufferRestore(ObjectSubclass<imp::OriginalBufferRestore>) @extends gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
gst::Element::register(
|
||||||
|
Some(plugin),
|
||||||
|
"originalbufferrestore",
|
||||||
|
gst::Rank::NONE,
|
||||||
|
OriginalBufferRestore::static_type(),
|
||||||
|
)
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue