mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-06-27 10:30:34 +00:00
Compare commits
598 commits
0.11.3+fix
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
960529d90d | ||
|
bbf131086a | ||
|
7caf6b2073 | ||
|
505fab2e1c | ||
|
a10577b42c | ||
|
0ecbd3f953 | ||
|
3b7b2cd37b | ||
|
0bd98e2c34 | ||
|
e00ebca63f | ||
|
2b35f009fb | ||
|
cf7172248c | ||
|
bc5ed023e4 | ||
|
dbad98132f | ||
|
39b61195ad | ||
|
10a31a397e | ||
|
d036abb7d2 | ||
|
df4a4fb2ef | ||
|
2d1f556794 | ||
|
84a9f9c61f | ||
|
47d62b6d78 | ||
|
90e926def4 | ||
|
345edeb947 | ||
|
a7764ff033 | ||
|
9b323a6519 | ||
|
23d998a1db | ||
|
8f96509f03 | ||
|
59bada0a9f | ||
|
743ab29ba8 | ||
|
970d1c9afd | ||
|
5aedcab32f | ||
|
4677948a82 | ||
|
d357a63bf9 | ||
|
5cd9e34265 | ||
|
bbe38b9599 | ||
|
b4b56eb282 | ||
|
343680ffea | ||
|
477855789d | ||
|
93c9821cba | ||
|
0ca4a3778a | ||
|
69c3c2ae46 | ||
|
cd47bf2f04 | ||
|
6538803cf6 | ||
|
4c9ed330c8 | ||
|
7f16fd7736 | ||
|
3e4330686f | ||
|
3b6832724f | ||
|
968e0fddb9 | ||
|
39f466f2c6 | ||
|
4eed615871 | ||
|
3d4d785a2a | ||
|
51f6d3986f | ||
|
00aaecad07 | ||
|
c42040fbb8 | ||
|
9945b702b8 | ||
|
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 | ||
|
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 |
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
|
||||||
|
|
|
@ -20,6 +20,8 @@ variables:
|
||||||
# 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"
|
||||||
|
@ -50,6 +52,7 @@ trigger:
|
||||||
stage: 'trigger'
|
stage: 'trigger'
|
||||||
variables:
|
variables:
|
||||||
GIT_STRATEGY: none
|
GIT_STRATEGY: none
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
script:
|
script:
|
||||||
- echo "Trigger job done, now running the pipeline."
|
- echo "Trigger job done, now running the pipeline."
|
||||||
rules:
|
rules:
|
||||||
|
@ -73,13 +76,6 @@ trigger:
|
||||||
before_script:
|
before_script:
|
||||||
- source ./ci/env.sh
|
- source ./ci/env.sh
|
||||||
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
||||||
# If cargo exists assume we probably will want to update
|
|
||||||
# the lockfile
|
|
||||||
- |
|
|
||||||
if command -v cargo; then
|
|
||||||
cargo generate-lockfile
|
|
||||||
cargo update
|
|
||||||
fi
|
|
||||||
|
|
||||||
.debian:12-stable:
|
.debian:12-stable:
|
||||||
extends: .debian:12
|
extends: .debian:12
|
||||||
|
@ -103,11 +99,11 @@ trigger:
|
||||||
- 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:
|
||||||
|
@ -140,7 +136,7 @@ meson shared:
|
||||||
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
|
||||||
|
@ -294,6 +290,7 @@ test windows stable:
|
||||||
rustfmt:
|
rustfmt:
|
||||||
extends: '.debian:12-stable'
|
extends: '.debian:12-stable'
|
||||||
stage: "lint"
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
needs: []
|
needs: []
|
||||||
script:
|
script:
|
||||||
- cargo fmt --version
|
- cargo fmt --version
|
||||||
|
@ -302,6 +299,7 @@ rustfmt:
|
||||||
typos:
|
typos:
|
||||||
extends: '.debian:12-stable'
|
extends: '.debian:12-stable'
|
||||||
stage: "lint"
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
needs: []
|
needs: []
|
||||||
script:
|
script:
|
||||||
- typos
|
- typos
|
||||||
|
@ -309,6 +307,7 @@ typos:
|
||||||
gstwebrtc-api lint:
|
gstwebrtc-api lint:
|
||||||
image: node:lts
|
image: node:lts
|
||||||
stage: "lint"
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
needs: []
|
needs: []
|
||||||
script:
|
script:
|
||||||
- cd net/webrtc/gstwebrtc-api
|
- cd net/webrtc/gstwebrtc-api
|
||||||
|
@ -318,10 +317,12 @@ gstwebrtc-api lint:
|
||||||
check commits:
|
check commits:
|
||||||
extends: '.debian:12-stable'
|
extends: '.debian:12-stable'
|
||||||
stage: "lint"
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
needs: []
|
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-for-symlinks.sh
|
||||||
|
- ci/check-meson-version.sh
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
extends: '.debian:12-stable'
|
extends: '.debian:12-stable'
|
||||||
|
@ -333,7 +334,9 @@ clippy:
|
||||||
# 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 clippy --locked --color=always --all --all-features --all-targets -- -D warnings -A unknown-lints
|
- 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: .debian:12-stable
|
extends: .debian:12-stable
|
||||||
|
@ -344,7 +347,8 @@ deny:
|
||||||
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: '.debian:12-stable'
|
extends: '.debian:12-stable'
|
||||||
|
@ -356,7 +360,8 @@ outdated:
|
||||||
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
|
||||||
|
- cargo outdated --color=always --root-deps-only --exit-code 1 -v
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
@ -372,7 +377,7 @@ coverage:
|
||||||
# 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
|
||||||
|
@ -387,3 +392,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
|
||||||
|
|
251
CHANGELOG.md
251
CHANGELOG.md
|
@ -5,6 +5,241 @@ 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),
|
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).
|
specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-version-field).
|
||||||
|
|
||||||
|
## [0.12.7] - 2024-06-19
|
||||||
|
### Fixed
|
||||||
|
- aws, spotifyaudiosrc, reqwesthttpsrc, webrtchttp: Fix race condition when unlocking
|
||||||
|
- rtp: Allow any payload type for the AV1 RTP payloader/depayloader
|
||||||
|
- rtp: Various fixes to the AV1 RTP payloader/depayloader to work correctly
|
||||||
|
with Chrome and Pion
|
||||||
|
- meson: Various fixes to the meson-based build system around cargo
|
||||||
|
- webrtcsink: Use correct property names for configuring `av1enc`
|
||||||
|
- webrtcsink: Avoid lock poisoning when setting encoder properties
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- ndi: Support for NDI SDK v6
|
||||||
|
- webrtcsink: Support for AV1 via `nvav1enc`, `av1enc` or `rav1enc`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Update to async-tungstenite 0.26
|
||||||
|
|
||||||
|
## [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
|
## [0.10.11] - 2023-07-20
|
||||||
### Fixed
|
### Fixed
|
||||||
- fallbackswitch: Fix pad health calculation and notifies.
|
- fallbackswitch: Fix pad health calculation and notifies.
|
||||||
|
@ -213,8 +448,20 @@ specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-v
|
||||||
- webrtcsink: Make the `turn-server` property a `turn-servers` list
|
- webrtcsink: Make the `turn-server` property a `turn-servers` list
|
||||||
- webrtcsink: Move from async-std to tokio
|
- webrtcsink: Move from async-std to tokio
|
||||||
|
|
||||||
[Unreleased]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.11...HEAD
|
[Unreleased]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.7...HEAD
|
||||||
[0.10.11]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.9...0.10.11
|
[0.12.7]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.12.6...0.12.7
|
||||||
|
[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.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.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.8]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.7...0.10.8
|
||||||
|
|
7946
Cargo.lock
generated
Normal file
7946
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
46
Cargo.toml
46
Cargo.toml
|
@ -12,8 +12,11 @@ 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",
|
||||||
|
@ -26,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",
|
||||||
|
@ -63,7 +68,10 @@ 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",
|
"mux/mp4",
|
||||||
|
@ -74,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",
|
||||||
|
@ -107,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.71"
|
||||||
|
|
||||||
|
[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.
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-audiofx"
|
name = "gst-plugin-audiofx"
|
||||||
version = "0.11.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.70"
|
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"
|
||||||
|
@ -21,6 +21,7 @@ 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"
|
||||||
|
@ -28,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 = []
|
||||||
|
|
|
@ -36,7 +36,7 @@ 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={uri} ! audioconvert ! audio/x-raw,channels=1 !
|
"uridecodebin uri={uri} ! audioconvert ! audio/x-raw,channels=1 !
|
||||||
hrtfrender hrir-file={hrir} name=hrtf ! audioresample ! autoaudiosink"
|
hrtfrender hrir-file={hrir} name=hrtf ! audioresample ! autoaudiosink"
|
||||||
))?
|
))?
|
||||||
|
|
|
@ -11,15 +11,15 @@ use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst_audio::subclass::prelude::*;
|
use gst_audio::subclass::prelude::*;
|
||||||
|
|
||||||
|
use std::cmp;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::{cmp, u64};
|
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
use num_traits::cast::{FromPrimitive, ToPrimitive};
|
use num_traits::cast::{FromPrimitive, ToPrimitive};
|
||||||
use num_traits::float::Float;
|
use num_traits::float::Float;
|
||||||
|
|
||||||
use gst::glib::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",
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,10 @@ use gst::subclass::prelude::*;
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::u64;
|
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use atomic_refcell::AtomicRefCell;
|
use atomic_refcell::AtomicRefCell;
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ use nnnoiseless::DenoiseState;
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use atomic_refcell::AtomicRefCell;
|
use atomic_refcell::AtomicRefCell;
|
||||||
|
|
||||||
|
@ -129,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)
|
||||||
|
};
|
||||||
|
|
||||||
self.process(state, &settings, in_data, out_data);
|
gst_audio::AudioLevelMeta::add(buffer, level, has_voice);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.obj().src_pad().push(buffer)
|
self.obj().src_pad().push(buffer)
|
||||||
|
@ -160,10 +163,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)
|
||||||
|
};
|
||||||
|
|
||||||
self.process(state, &settings, in_data, out_data);
|
gst_audio::AudioLevelMeta::add(buffer, level, has_voice);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(GenerateOutputSuccess::Buffer(buffer))
|
Ok(GenerateOutputSuccess::Buffer(buffer))
|
||||||
|
@ -175,9 +181,10 @@ impl AudioRNNoise {
|
||||||
settings: &Settings,
|
settings: &Settings,
|
||||||
input_plane: &[f32],
|
input_plane: &[f32],
|
||||||
output_plane: &mut [f32],
|
output_plane: &mut [f32],
|
||||||
) {
|
) -> (u8, bool) {
|
||||||
let channels = state.in_info.channels() as usize;
|
let channels = state.in_info.channels() as usize;
|
||||||
let size = FRAME_SIZE * channels;
|
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 (out_frame, in_frame) in output_plane.chunks_mut(size).zip(input_plane.chunks(size)) {
|
||||||
for (index, item) in in_frame.iter().enumerate() {
|
for (index, item) in in_frame.iter().enumerate() {
|
||||||
|
@ -207,11 +214,15 @@ impl AudioRNNoise {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
gst::debug!(CAT, imp: self, "Voice activity: {}", vad);
|
gst::trace!(CAT, imp: self, "Voice activity: {}", vad);
|
||||||
|
|
||||||
if vad < settings.vad_threshold {
|
if vad < settings.vad_threshold {
|
||||||
out_frame.fill(0.0);
|
out_frame.fill(0.0);
|
||||||
} else {
|
} 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() {
|
for (index, item) in out_frame.iter_mut().enumerate() {
|
||||||
let channel_index = index % channels;
|
let channel_index = index % channels;
|
||||||
let channel_denoiser = &state.denoisers[channel_index];
|
let channel_denoiser = &state.denoisers[channel_index];
|
||||||
|
@ -220,6 +231,19 @@ impl AudioRNNoise {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,11 +12,10 @@ use gst::subclass::prelude::*;
|
||||||
use gst_audio::subclass::prelude::*;
|
use gst_audio::subclass::prelude::*;
|
||||||
use gst_base::prelude::*;
|
use gst_base::prelude::*;
|
||||||
|
|
||||||
use std::i32;
|
|
||||||
use std::sync::atomic;
|
use std::sync::atomic;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ use byte_slice_cast::*;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||||
|
|
||||||
use gst::glib::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(
|
||||||
"hrtfrender",
|
"hrtfrender",
|
||||||
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ fn run_test(
|
||||||
};
|
};
|
||||||
|
|
||||||
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,7 +49,7 @@ 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",
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,7 @@ fn basic_two_channels() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn silence() {
|
fn silence() {
|
||||||
run_test("wave=silence", None, 1000, 1024, 1, std::f64::NEG_INFINITY);
|
run_test("wave=silence", None, 1000, 1024, 1, f64::NEG_INFINITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -228,7 +228,7 @@ fn below_threshold() {
|
||||||
1000,
|
1000,
|
||||||
1024,
|
1024,
|
||||||
1,
|
1,
|
||||||
std::f64::NEG_INFINITY,
|
f64::NEG_INFINITY,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
static CONFIG: Lazy<glib::Bytes> = Lazy::new(|| {
|
static CONFIG: Lazy<glib::Bytes> = Lazy::new(|| {
|
||||||
let buff = include_bytes!("test.hrir");
|
let buff = include_bytes!("test.hrir");
|
||||||
|
|
|
@ -1,23 +1,23 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-claxon"
|
name = "gst-plugin-claxon"
|
||||||
version = "0.11.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.70"
|
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 = []
|
||||||
|
|
|
@ -19,7 +19,7 @@ use atomic_refcell::AtomicRefCell;
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
use gst::glib::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(
|
||||||
|
|
|
@ -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,22 +1,23 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-csound"
|
name = "gst-plugin-csound"
|
||||||
version = "0.11.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.70"
|
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"
|
||||||
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"
|
||||||
|
@ -28,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 = []
|
||||||
|
|
|
@ -75,9 +75,9 @@ 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)
|
||||||
|
|
|
@ -17,13 +17,12 @@ use gst_base::subclass::prelude::*;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::{f64, i32};
|
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
use csound::{Csound, MessageType};
|
use csound::{Csound, MessageType};
|
||||||
|
|
||||||
use gst::glib::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(
|
||||||
|
|
|
@ -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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-lewton"
|
name = "gst-plugin-lewton"
|
||||||
version = "0.11.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.70"
|
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.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"
|
||||||
|
@ -24,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 = []
|
||||||
|
|
|
@ -18,7 +18,7 @@ use atomic_refcell::AtomicRefCell;
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
struct State {
|
struct State {
|
||||||
header_bufs: (
|
header_bufs: (
|
||||||
|
|
|
@ -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,21 +1,22 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-spotify"
|
name = "gst-plugin-spotify"
|
||||||
version = "0.11.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.70"
|
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
|
||||||
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"
|
||||||
|
@ -23,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 = []
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -30,13 +30,13 @@ impl Settings {
|
||||||
pub fn properties() -> Vec<glib::ParamSpec> {
|
pub fn properties() -> Vec<glib::ParamSpec> {
|
||||||
vec![glib::ParamSpecString::builder("username")
|
vec![glib::ParamSpecString::builder("username")
|
||||||
.nick("Username")
|
.nick("Username")
|
||||||
.blurb("Spotify device username from https://www.spotify.com/us/account/set-device-password/")
|
.blurb("Spotify username, Facebook accounts need a device username from https://www.spotify.com/us/account/set-device-password/")
|
||||||
.default_value(Some(""))
|
.default_value(Some(""))
|
||||||
.mutable_ready()
|
.mutable_ready()
|
||||||
.build(),
|
.build(),
|
||||||
glib::ParamSpecString::builder("password")
|
glib::ParamSpecString::builder("password")
|
||||||
.nick("Password")
|
.nick("Password")
|
||||||
.blurb("Spotify device password from https://www.spotify.com/us/account/set-device-password/")
|
.blurb("Spotify password, Facebook accounts need a device password from https://www.spotify.com/us/account/set-device-password/")
|
||||||
.default_value(Some(""))
|
.default_value(Some(""))
|
||||||
.mutable_ready()
|
.mutable_ready()
|
||||||
.build(),
|
.build(),
|
||||||
|
@ -109,7 +109,7 @@ impl Settings {
|
||||||
cat: &gst::DebugCategory,
|
cat: &gst::DebugCategory,
|
||||||
) -> anyhow::Result<Session>
|
) -> anyhow::Result<Session>
|
||||||
where
|
where
|
||||||
T: glib::IsA<glib::Object>,
|
T: IsA<glib::Object>,
|
||||||
{
|
{
|
||||||
let credentials_cache = if self.cache_credentials.is_empty() {
|
let credentials_cache = if self.cache_credentials.is_empty() {
|
||||||
None
|
None
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use std::sync::{mpsc, Arc, Mutex, MutexGuard};
|
use std::sync::{mpsc, Arc, Mutex};
|
||||||
|
|
||||||
use futures::future::{AbortHandle, Abortable, Aborted};
|
use futures::future::{AbortHandle, Abortable, Aborted};
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::{runtime, task::JoinHandle};
|
use tokio::{runtime, task::JoinHandle};
|
||||||
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
@ -67,15 +67,39 @@ struct Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SpotifyAudioSrc {
|
enum SetupThread {
|
||||||
setup_thread: Mutex<Option<SetupThread>>,
|
#[default]
|
||||||
state: Arc<Mutex<Option<State>>>,
|
None,
|
||||||
settings: Mutex<Settings>,
|
Pending {
|
||||||
|
thread_handle: Option<std::thread::JoinHandle<Result<anyhow::Result<()>, Aborted>>>,
|
||||||
|
abort_handle: AbortHandle,
|
||||||
|
},
|
||||||
|
Cancelled,
|
||||||
|
Done,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SetupThread {
|
impl SetupThread {
|
||||||
thread_handle: std::thread::JoinHandle<Result<anyhow::Result<()>, Aborted>>,
|
fn abort(&mut self) {
|
||||||
abort_handle: AbortHandle,
|
// Cancel setup thread if it is pending and not done yet
|
||||||
|
if matches!(self, SetupThread::None | SetupThread::Done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let SetupThread::Pending {
|
||||||
|
ref abort_handle, ..
|
||||||
|
} = *self
|
||||||
|
{
|
||||||
|
abort_handle.abort();
|
||||||
|
}
|
||||||
|
*self = SetupThread::Cancelled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SpotifyAudioSrc {
|
||||||
|
setup_thread: Mutex<SetupThread>,
|
||||||
|
state: Arc<Mutex<Option<State>>>,
|
||||||
|
settings: Mutex<Settings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
@ -172,21 +196,18 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let setup_thread = self.setup_thread.lock().unwrap();
|
// If not started yet and not cancelled, start the setup
|
||||||
if setup_thread.is_some() {
|
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||||
// already starting
|
assert!(!matches!(&*setup_thread, SetupThread::Cancelled));
|
||||||
return Ok(());
|
if matches!(&*setup_thread, SetupThread::None) {
|
||||||
|
self.start_setup(&mut setup_thread);
|
||||||
}
|
}
|
||||||
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();
|
||||||
|
@ -199,9 +220,17 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unlock(&self) -> Result<(), gst::ErrorMessage> {
|
fn unlock(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
self.cancel_setup();
|
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||||
|
setup_thread.abort();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
self.parent_unlock()
|
fn unlock_stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
|
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||||
|
if matches!(&*setup_thread, SetupThread::Cancelled) {
|
||||||
|
*setup_thread = SetupThread::None;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,30 +245,47 @@ impl PushSrcImpl for SpotifyAudioSrc {
|
||||||
};
|
};
|
||||||
|
|
||||||
if !state_set {
|
if !state_set {
|
||||||
let setup_thread = self.setup_thread.lock().unwrap();
|
// If not started yet and not cancelled, start the setup
|
||||||
if setup_thread.is_none() {
|
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||||
// unlock() could potentially cancel the setup, and create() can be called after unlock() without going through start() again.
|
if matches!(&*setup_thread, SetupThread::Cancelled) {
|
||||||
self.start_setup(setup_thread);
|
return Err(gst::FlowError::Flushing);
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(&*setup_thread, SetupThread::None) {
|
||||||
|
self.start_setup(&mut setup_thread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
// wait for the setup to be completed
|
// wait for the setup to be completed
|
||||||
let mut setup_thread = self.setup_thread.lock().unwrap();
|
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||||
if let Some(setup) = setup_thread.take() {
|
if let SetupThread::Pending {
|
||||||
let res = setup.thread_handle.join().unwrap();
|
ref mut thread_handle,
|
||||||
|
..
|
||||||
|
} = *setup_thread
|
||||||
|
{
|
||||||
|
let thread_handle = thread_handle.take().expect("Waiting multiple times");
|
||||||
|
drop(setup_thread);
|
||||||
|
let res = thread_handle.join().unwrap();
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(_aborted) => {
|
Err(_aborted) => {
|
||||||
gst::debug!(CAT, imp: self, "setup has been cancelled");
|
gst::debug!(CAT, imp: self, "setup has been cancelled");
|
||||||
|
setup_thread = self.setup_thread.lock().unwrap();
|
||||||
|
*setup_thread = SetupThread::Cancelled;
|
||||||
return Err(gst::FlowError::Flushing);
|
return Err(gst::FlowError::Flushing);
|
||||||
}
|
}
|
||||||
Ok(Err(err)) => {
|
Ok(Err(err)) => {
|
||||||
gst::error!(CAT, imp: self, "failed to start: {err:?}");
|
gst::error!(CAT, imp: self, "failed to start: {err:?}");
|
||||||
gst::element_imp_error!(self, gst::ResourceError::Settings, ["{err:?}"]);
|
gst::element_imp_error!(self, gst::ResourceError::Settings, ["{err:?}"]);
|
||||||
|
setup_thread = self.setup_thread.lock().unwrap();
|
||||||
|
*setup_thread = SetupThread::None;
|
||||||
return Err(gst::FlowError::Error);
|
return Err(gst::FlowError::Error);
|
||||||
}
|
}
|
||||||
Ok(Ok(_)) => {}
|
Ok(Ok(_)) => {
|
||||||
|
setup_thread = self.setup_thread.lock().unwrap();
|
||||||
|
*setup_thread = SetupThread::Done;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,7 +377,9 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpotifyAudioSrc {
|
impl SpotifyAudioSrc {
|
||||||
fn start_setup(&self, mut setup_thread: MutexGuard<Option<SetupThread>>) {
|
fn start_setup(&self, setup_thread: &mut SetupThread) {
|
||||||
|
assert!(matches!(setup_thread, SetupThread::None));
|
||||||
|
|
||||||
let self_ = self.to_owned();
|
let self_ = self.to_owned();
|
||||||
|
|
||||||
// run the runtime from another thread to prevent the "start a runtime from within a runtime" panic
|
// run the runtime from another thread to prevent the "start a runtime from within a runtime" panic
|
||||||
|
@ -344,10 +392,10 @@ impl SpotifyAudioSrc {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
setup_thread.replace(SetupThread {
|
*setup_thread = SetupThread::Pending {
|
||||||
thread_handle,
|
thread_handle: Some(thread_handle),
|
||||||
abort_handle,
|
abort_handle,
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn setup(&self) -> anyhow::Result<()> {
|
async fn setup(&self) -> anyhow::Result<()> {
|
||||||
|
@ -420,12 +468,4 @@ impl SpotifyAudioSrc {
|
||||||
|
|
||||||
Ok(())
|
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"spotifyaudiosrc",
|
"spotifyaudiosrc",
|
||||||
gst::Rank::Primary,
|
gst::Rank::PRIMARY,
|
||||||
SpotifyAudioSrc::static_type(),
|
SpotifyAudioSrc::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,9 @@ if __name__ == "__main__":
|
||||||
pkg_config_path.append(str(opts.root_dir / 'meson-uninstalled'))
|
pkg_config_path.append(str(opts.root_dir / 'meson-uninstalled'))
|
||||||
env['PKG_CONFIG_PATH'] = os.pathsep.join(pkg_config_path)
|
env['PKG_CONFIG_PATH'] = os.pathsep.join(pkg_config_path)
|
||||||
|
|
||||||
|
if 'NASM' in env:
|
||||||
|
env['PATH'] = os.pathsep.join([os.path.dirname(env['NASM']), env['PATH']])
|
||||||
|
|
||||||
rustc_target = None
|
rustc_target = None
|
||||||
if 'RUSTC' in env:
|
if 'RUSTC' in env:
|
||||||
rustc_cmdline = shlex.split(env['RUSTC'])
|
rustc_cmdline = shlex.split(env['RUSTC'])
|
||||||
|
|
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(".")
|
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
|
|
@ -36,6 +36,7 @@ function Run-Tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
$env:G_DEBUG="fatal_warnings"
|
$env:G_DEBUG="fatal_warnings"
|
||||||
|
$env:RUST_BACKTRACE="1"
|
||||||
cargo test --no-fail-fast --color=always --workspace $local_exclude --all-targets $Features
|
cargo test --no-fail-fast --color=always --workspace $local_exclude --all-targets $Features
|
||||||
|
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
|
|
|
@ -22,6 +22,8 @@ RS_PREFIXED = [
|
||||||
'png',
|
'png',
|
||||||
'tracers',
|
'tracers',
|
||||||
'rtp',
|
'rtp',
|
||||||
|
'rtsp',
|
||||||
|
'inter',
|
||||||
]
|
]
|
||||||
|
|
||||||
OVERRIDE = {
|
OVERRIDE = {
|
||||||
|
|
208
deny.toml
208
deny.toml
|
@ -10,8 +10,6 @@ ignore = [
|
||||||
"RUSTSEC-2021-0060",
|
"RUSTSEC-2021-0060",
|
||||||
"RUSTSEC-2021-0061",
|
"RUSTSEC-2021-0061",
|
||||||
"RUSTSEC-2021-0145",
|
"RUSTSEC-2021-0145",
|
||||||
# https://github.com/chronotope/chrono/issues/499
|
|
||||||
"RUSTSEC-2020-0071",
|
|
||||||
# sodiumoxide is deprecated
|
# sodiumoxide is deprecated
|
||||||
"RUSTSEC-2021-0137",
|
"RUSTSEC-2021-0137",
|
||||||
]
|
]
|
||||||
|
@ -19,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
|
||||||
|
|
||||||
|
@ -40,22 +32,22 @@ 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 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]]
|
||||||
|
@ -78,22 +70,15 @@ version = "0.9"
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
version = "0.11"
|
version = "0.11"
|
||||||
|
|
||||||
# ignore duplicated wasi dependency because various crates depends on an old version
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "wasi"
|
name = "zerocopy"
|
||||||
version = "0.10"
|
version = "0.6"
|
||||||
|
|
||||||
# ignore duplicated spin dependency because various crates depend on an old version
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "spin"
|
name = "multimap"
|
||||||
version = "0.5"
|
version = "0.8"
|
||||||
|
|
||||||
# cookie_store depends on older idna
|
|
||||||
# https://github.com/pfernie/cookie_store/commit/b9c710f45550c5c8997f18a83e6fcc5998cf1726
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "idna"
|
name = "nix"
|
||||||
version = "0.2"
|
version = "0.23"
|
||||||
|
|
||||||
# field-offset and nix depend on an older memoffset
|
# field-offset and nix depend on an older memoffset
|
||||||
# https://github.com/Diggsey/rust-field-offset/pull/23
|
# https://github.com/Diggsey/rust-field-offset/pull/23
|
||||||
|
@ -106,38 +91,28 @@ version = "0.6"
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1"
|
version = "0.1"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3"
|
||||||
|
|
||||||
# Various crates depend on an older version of base64
|
# Various crates depend on an older version of base64
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13"
|
version = "0.13"
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21"
|
||||||
|
|
||||||
# Various crates depend on an older version of socket2
|
# Various crates depend on an older version of socket2
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.4"
|
version = "0.4"
|
||||||
|
|
||||||
# Various crates depend on an older version of syn
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
# Various crates depend on an older version of bitflags
|
# Various crates depend on an older version of bitflags
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|
||||||
# cargo-lock depends on an old version of the toml crate
|
|
||||||
# https://github.com/rustsec/rustsec/pull/805
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "toml"
|
|
||||||
version = "0.5"
|
|
||||||
|
|
||||||
# Various crates depend on an older version of redox_syscall
|
|
||||||
[[bans.skip]]
|
|
||||||
name = "redox_syscall"
|
|
||||||
version = "0.2"
|
|
||||||
|
|
||||||
# tracing-subscriber depends on an older version of regex-syntax
|
# tracing-subscriber depends on an older version of regex-syntax
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
|
@ -157,28 +132,137 @@ version = "1.0"
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12"
|
version = "0.12"
|
||||||
|
|
||||||
# av1-grain depends on an old version of itertools
|
# various livekit dependencies depend on an old version of itertools and sync_wrapper
|
||||||
# https://github.com/rust-av/av1-grain/pull/12
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10"
|
version = "0.11"
|
||||||
|
|
||||||
# rav1e depends on an old version of num-derive
|
|
||||||
# https://github.com/xiph/rav1e/pull/3237
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "num-derive"
|
name = "sync_wrapper"
|
||||||
version = "0.3"
|
version = "0.1"
|
||||||
|
|
||||||
|
# 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
|
# matchers depends on an old version of regex-automata
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.1"
|
version = "0.1"
|
||||||
|
|
||||||
# old livekit-api depends on an old version of tokio-tungstenite/tungstenite
|
# Various crates depend on old versions of the windows crates
|
||||||
# https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1293
|
[[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]]
|
[[bans.skip]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
version = "0.19"
|
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"
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
# various crates depend on an older version of system-deps
|
||||||
|
[[bans.skip]]
|
||||||
|
name = "system-deps"
|
||||||
|
version = "6"
|
||||||
|
|
||||||
[sources]
|
[sources]
|
||||||
unknown-registry = "deny"
|
unknown-registry = "deny"
|
||||||
|
|
|
@ -29,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',
|
||||||
|
@ -36,6 +37,7 @@ RENAMES = {
|
||||||
'rswebrtc': 'webrtc',
|
'rswebrtc': 'webrtc',
|
||||||
'rspng': 'png',
|
'rspng': 'png',
|
||||||
'rsvideofx': 'videofx',
|
'rsvideofx': 'videofx',
|
||||||
|
'rsinter': 'inter',
|
||||||
'textahead': 'ahead',
|
'textahead': 'ahead',
|
||||||
'textwrap': 'wrap',
|
'textwrap': 'wrap',
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.')
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,17 +1,18 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-file"
|
name = "gst-plugin-file"
|
||||||
version = "0.11.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.70"
|
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.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstrsfile"
|
name = "gstrsfile"
|
||||||
|
@ -19,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 = []
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -54,7 +55,7 @@ pub struct FileSink {
|
||||||
state: Mutex<State>,
|
state: Mutex<State>,
|
||||||
}
|
}
|
||||||
|
|
||||||
use gst::glib::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(
|
||||||
"rsfilesink",
|
"rsfilesink",
|
||||||
|
@ -111,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(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ pub struct FileSrc {
|
||||||
state: Mutex<State>,
|
state: Mutex<State>,
|
||||||
}
|
}
|
||||||
|
|
||||||
use gst::glib::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(
|
||||||
"rsfilesrc",
|
"rsfilesrc",
|
||||||
|
|
|
@ -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)
|
||||||
|
.is_some_and(|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(),
|
||||||
|
)
|
||||||
|
}
|
205
generic/originalbuffer/src/originalbuffersave/imp.rs
Normal file
205
generic/originalbuffer/src/originalbuffersave/imp.rs
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
// 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::prelude::*;
|
||||||
|
use gst::subclass::prelude::*;
|
||||||
|
|
||||||
|
use crate::originalbuffermeta::OriginalBufferMeta;
|
||||||
|
|
||||||
|
pub struct OriginalBufferSave {
|
||||||
|
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(
|
||||||
|
"originalbuffersave",
|
||||||
|
gst::DebugColorFlags::empty(),
|
||||||
|
Some("Save Original buffer as meta"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for OriginalBufferSave {
|
||||||
|
const NAME: &'static str = "GstOriginalBufferSave";
|
||||||
|
type Type = super::OriginalBufferSave;
|
||||||
|
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| {
|
||||||
|
OriginalBufferSave::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| Err(gst::FlowError::Error),
|
||||||
|
|obj| obj.sink_chain(pad, buffer),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.query_function(|pad, parent, query| {
|
||||||
|
OriginalBufferSave::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|obj| obj.sink_query(pad, parent, query),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.flags(gst::PadFlags::PROXY_CAPS | gst::PadFlags::PROXY_ALLOCATION)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let src_pad = gst::Pad::builder_from_template(&src_templ)
|
||||||
|
.event_function(|pad, parent, event| {
|
||||||
|
OriginalBufferSave::catch_panic_pad_function(
|
||||||
|
parent,
|
||||||
|
|| false,
|
||||||
|
|obj| obj.src_event(pad, parent, event),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Self { src_pad, sink_pad }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for OriginalBufferSave {
|
||||||
|
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 OriginalBufferSave {}
|
||||||
|
|
||||||
|
impl ElementImpl for OriginalBufferSave {
|
||||||
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
|
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||||
|
gst::subclass::ElementMetadata::new(
|
||||||
|
"Original Buffer Save",
|
||||||
|
"Generic",
|
||||||
|
"Saves 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OriginalBufferSave {
|
||||||
|
fn forward_query(&self, query: gst::Query) -> Option<gst::Query> {
|
||||||
|
let mut s = gst::Structure::new_empty("gst-original-buffer-forward-query");
|
||||||
|
s.set("query", query);
|
||||||
|
|
||||||
|
let mut query = gst::query::Custom::new(s);
|
||||||
|
if self.src_pad.peer_query(&mut query) {
|
||||||
|
let s = query.structure_mut();
|
||||||
|
if let (Ok(true), Ok(q)) = (s.get("result"), s.get::<gst::Query>("query")) {
|
||||||
|
Some(q)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_chain(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
inbuf: gst::Buffer,
|
||||||
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
|
let mut buf = inbuf.copy();
|
||||||
|
let caps = pad.current_caps();
|
||||||
|
|
||||||
|
if let Some(mut meta) = buf.make_mut().meta_mut::<OriginalBufferMeta>() {
|
||||||
|
meta.replace(inbuf, caps);
|
||||||
|
} else {
|
||||||
|
OriginalBufferMeta::add(buf.make_mut(), inbuf, caps);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.src_pad.push(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sink_query(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
parent: Option<&impl IsA<gst::Object>>,
|
||||||
|
query: &mut gst::QueryRef,
|
||||||
|
) -> bool {
|
||||||
|
let ret = gst::Pad::query_default(pad, parent, query);
|
||||||
|
if !ret {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let gst::QueryViewMut::Caps(q) = query.view_mut() {
|
||||||
|
if let Some(caps) = q.result_owned() {
|
||||||
|
let forwarding_q = gst::query::Caps::new(Some(&caps)).into();
|
||||||
|
|
||||||
|
if let Some(forwarding_q) = self.forward_query(forwarding_q) {
|
||||||
|
if let gst::QueryView::Caps(c) = forwarding_q.view() {
|
||||||
|
let res = c
|
||||||
|
.result_owned()
|
||||||
|
.map(|c| c.intersect_with_mode(&caps, gst::CapsIntersectMode::First));
|
||||||
|
q.set_result(&res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should also do allocation queries, but that requires supporting the same
|
||||||
|
// intersection semantics as gsttee, which should be in a helper function.
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn src_event(
|
||||||
|
&self,
|
||||||
|
pad: &gst::Pad,
|
||||||
|
parent: Option<&impl IsA<gst::Object>>,
|
||||||
|
event: gst::Event,
|
||||||
|
) -> bool {
|
||||||
|
let event = if event.has_name("gst-original-buffer-forward-upstream-event") {
|
||||||
|
event.structure().unwrap().get("event").unwrap()
|
||||||
|
} else {
|
||||||
|
event
|
||||||
|
};
|
||||||
|
|
||||||
|
gst::Pad::event_default(pad, parent, event)
|
||||||
|
}
|
||||||
|
}
|
41
generic/originalbuffer/src/originalbuffersave/mod.rs
Normal file
41
generic/originalbuffer/src/originalbuffersave/mod.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// 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-originalbuffersave
|
||||||
|
*
|
||||||
|
* GStreamer elements to store the original buffer and restore it later
|
||||||
|
*
|
||||||
|
* In many analysis scenario (for example machine learning), it is desirable to
|
||||||
|
* use a pre-processed buffer, for example by lowering the resolution, but we may
|
||||||
|
* want to take the output of this analysis, and apply it to the original buffer.
|
||||||
|
*
|
||||||
|
* These elements do just this, the typical usage would be a pipeline like:
|
||||||
|
*
|
||||||
|
* `... ! originalbuffersave ! videoconvertscale ! video/x-raw, width=100, height=100 ! analysiselement ! originalbufferrestore ! ...`
|
||||||
|
*
|
||||||
|
* The originalbufferrestore element will "restore" the buffer that was entered to the "save" element, but will keep any metadata that was added later.
|
||||||
|
*/
|
||||||
|
use gst::glib;
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct OriginalBufferSave(ObjectSubclass<imp::OriginalBufferSave>) @extends gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
gst::Element::register(
|
||||||
|
Some(plugin),
|
||||||
|
"originalbuffersave",
|
||||||
|
gst::Rank::NONE,
|
||||||
|
OriginalBufferSave::static_type(),
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,16 +1,16 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-sodium"
|
name = "gst-plugin-sodium"
|
||||||
version = "0.11.0-alpha.1"
|
version.workspace = true
|
||||||
authors = ["Jordan Petridis <jordan@centricular.com>"]
|
authors = ["Jordan Petridis <jordan@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository.workspace = true
|
||||||
description = "GStreamer plugin for libsodium-based file encryption and decryption"
|
description = "GStreamer plugin for libsodium-based file encryption and decryption"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.70"
|
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
|
||||||
sodiumoxide = "0.2.1"
|
sodiumoxide = "0.2.1"
|
||||||
once_cell = "1.3.0"
|
once_cell = "1.3.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
@ -24,14 +24,8 @@ serde_json = { version = "1.0", optional = true }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
gst-check.workspace = true
|
||||||
[dev-dependencies.gst-check]
|
gst-app.workspace = true
|
||||||
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
|
||||||
package="gstreamer-check"
|
|
||||||
|
|
||||||
[dev-dependencies.gst-app]
|
|
||||||
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
|
||||||
package="gstreamer-app"
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstsodium"
|
name = "gstsodium"
|
||||||
|
@ -54,7 +48,7 @@ path = "examples/decrypt_example.rs"
|
||||||
required-features = ["serde", "serde_json", "clap"]
|
required-features = ["serde", "serde_json", "clap"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -29,7 +29,7 @@ use sodiumoxide::crypto::box_;
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use gst::glib::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(
|
||||||
"sodiumdecrypter",
|
"sodiumdecrypter",
|
||||||
|
@ -500,7 +500,7 @@ impl Decrypter {
|
||||||
gst::debug!(CAT, obj: pad, "Stream Block index: {}", chunk_index);
|
gst::debug!(CAT, obj: pad, "Stream Block index: {}", chunk_index);
|
||||||
|
|
||||||
let pull_offset = offset - (chunk_index * block_size as u64);
|
let pull_offset = offset - (chunk_index * block_size as u64);
|
||||||
assert!(pull_offset <= std::u32::MAX as u64);
|
assert!(pull_offset <= u32::MAX as u64);
|
||||||
let pull_offset = pull_offset as u32;
|
let pull_offset = pull_offset as u32;
|
||||||
|
|
||||||
let pulled_buffer =
|
let pulled_buffer =
|
||||||
|
|
|
@ -35,7 +35,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"sodiumdecrypter",
|
"sodiumdecrypter",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
Decrypter::static_type(),
|
Decrypter::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ type BufferVec = SmallVec<[gst::Buffer; 16]>;
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use gst::glib::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(
|
||||||
"sodiumencrypter",
|
"sodiumencrypter",
|
||||||
|
|
|
@ -35,7 +35,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"sodiumencrypter",
|
"sodiumencrypter",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
Encrypter::static_type(),
|
Encrypter::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ fn typefind_register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
TypeFind::register(
|
TypeFind::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"sodium_encrypted_typefind",
|
"sodium_encrypted_typefind",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
None,
|
None,
|
||||||
Some(&Caps::builder("application/x-sodium-encrypted").build()),
|
Some(&Caps::builder("application/x-sodium-encrypted").build()),
|
||||||
|typefind| {
|
|typefind| {
|
||||||
|
|
|
@ -31,7 +31,7 @@ use std::path::PathBuf;
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
static SENDER_PUBLIC: Lazy<glib::Bytes> = Lazy::new(|| {
|
static SENDER_PUBLIC: Lazy<glib::Bytes> = Lazy::new(|| {
|
||||||
let public = [
|
let public = [
|
||||||
66, 248, 199, 74, 216, 55, 228, 116, 52, 17, 147, 56, 65, 130, 134, 148, 157, 153, 235,
|
66, 248, 199, 74, 216, 55, 228, 116, 52, 17, 147, 56, 65, 130, 134, 148, 157, 153, 235,
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,29 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-threadshare"
|
name = "gst-plugin-threadshare"
|
||||||
version = "0.11.0-alpha.1"
|
version.workspace = true
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
license = "LGPL-2.1-or-later"
|
license = "LGPL-2.1-or-later"
|
||||||
description = "GStreamer Threadshare Plugin"
|
description = "GStreamer Threadshare Plugin"
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository.workspace = true
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.70"
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-task = "4.3.0"
|
async-task = "4.3.0"
|
||||||
concurrent-queue = "2"
|
cfg-if = "1"
|
||||||
flume = "0.10.13"
|
concurrent-queue = "2.2.0"
|
||||||
futures = "0.3.21"
|
flume = "0.11"
|
||||||
libc = "0.2"
|
futures = "0.3.28"
|
||||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
gio.workspace = true
|
||||||
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
|
||||||
gst-net = { package = "gstreamer-net", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-net.workspace = true
|
||||||
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-rtp.workspace = true
|
||||||
once_cell = "1"
|
once_cell.workspace = true
|
||||||
pin-project-lite = "0.2.0"
|
pin-project-lite = "0.2.0"
|
||||||
polling = "2.2.0"
|
polling = "3.1.0"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
|
rustix = { version = "0.38.2", default-features = false, features = ["std", "fs", "net"] }
|
||||||
slab = "0.4.7"
|
slab = "0.4.7"
|
||||||
socket2 = {features = ["all"], version = "0.5"}
|
socket2 = {features = ["all"], version = "0.5"}
|
||||||
waker-fn = "1.1"
|
waker-fn = "1.1"
|
||||||
|
@ -34,8 +35,8 @@ clap = { version = "4", features = ["derive"], optional = true }
|
||||||
winapi = { version = "0.3.9", features = ["winsock2", "processthreadsapi"] }
|
winapi = { version = "0.3.9", features = ["winsock2", "processthreadsapi"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-check.workspace = true
|
||||||
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-app.workspace = true
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstthreadshare"
|
name = "gstthreadshare"
|
||||||
|
@ -59,7 +60,7 @@ name = "ts-standalone"
|
||||||
path = "examples/standalone/main.rs"
|
path = "examples/standalone/main.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper.workspace = true
|
||||||
cc = "1.0.38"
|
cc = "1.0.38"
|
||||||
pkg-config = "0.3.15"
|
pkg-config = "0.3.15"
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
@ -84,13 +84,16 @@ fn main() {
|
||||||
.property("signal-handoffs", true)
|
.property("signal-handoffs", true)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
sink.connect(
|
sink.connect_closure(
|
||||||
"handoff",
|
"handoff",
|
||||||
true,
|
true,
|
||||||
glib::clone!(@strong counter => move |_| {
|
glib::closure!(
|
||||||
let _ = counter.fetch_add(1, Ordering::SeqCst);
|
#[strong]
|
||||||
None
|
counter,
|
||||||
}),
|
move |_fakesink: &gst::Element, _buffer: &gst::Buffer, _pad: &gst::Pad| {
|
||||||
|
let _ = counter.fetch_add(1, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
let (source, context) = match source.as_str() {
|
let (source, context) = match source.as_str() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
mod args;
|
mod args;
|
||||||
use args::*;
|
use args::*;
|
||||||
|
|
|
@ -14,7 +14,7 @@ use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst::EventView;
|
use gst::EventView;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use gstthreadshare::runtime::executor::block_on_or_add_sub_task;
|
use gstthreadshare::runtime::executor::block_on_or_add_sub_task;
|
||||||
use gstthreadshare::runtime::{prelude::*, PadSink};
|
use gstthreadshare::runtime::{prelude::*, PadSink};
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
super::ASYNC_MUTEX_ELEMENT_NAME,
|
super::ASYNC_MUTEX_ELEMENT_NAME,
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
AsyncMutexSink::static_type(),
|
AsyncMutexSink::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ pub const ASYNC_MUTEX_ELEMENT_NAME: &str = "ts-standalone-async-mutex-sink";
|
||||||
pub const SYNC_MUTEX_ELEMENT_NAME: &str = "ts-standalone-sync-mutex-sink";
|
pub const SYNC_MUTEX_ELEMENT_NAME: &str = "ts-standalone-sync-mutex-sink";
|
||||||
pub const TASK_ELEMENT_NAME: &str = "ts-standalone-task-sink";
|
pub const TASK_ELEMENT_NAME: &str = "ts-standalone-task-sink";
|
||||||
|
|
||||||
use gst::glib::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(
|
||||||
"ts-standalone-sink",
|
"ts-standalone-sink",
|
||||||
|
|
|
@ -14,7 +14,7 @@ use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst::EventView;
|
use gst::EventView;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use gstthreadshare::runtime::{prelude::*, PadSink};
|
use gstthreadshare::runtime::{prelude::*, PadSink};
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
super::SYNC_MUTEX_ELEMENT_NAME,
|
super::SYNC_MUTEX_ELEMENT_NAME,
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
DirectSink::static_type(),
|
DirectSink::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
use gst::EventView;
|
use gst::EventView;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use gstthreadshare::runtime::prelude::*;
|
use gstthreadshare::runtime::prelude::*;
|
||||||
use gstthreadshare::runtime::{Context, PadSink, Task};
|
use gstthreadshare::runtime::{Context, PadSink, Task};
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
super::TASK_ELEMENT_NAME,
|
super::TASK_ELEMENT_NAME,
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
TaskSink::static_type(),
|
TaskSink::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"ts-standalone-src",
|
"ts-standalone-src",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
TestSrc::static_type(),
|
TestSrc::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::net;
|
use std::net;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
|
|
|
@ -26,11 +26,10 @@ use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::u32;
|
|
||||||
|
|
||||||
use crate::runtime::prelude::*;
|
use crate::runtime::prelude::*;
|
||||||
use crate::runtime::{Context, PadSrc, Task, TaskState};
|
use crate::runtime::{Context, PadSrc, Task, TaskState};
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"ts-appsrc",
|
"ts-appsrc",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
AppSrc::static_type(),
|
AppSrc::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::prelude::*;
|
use gst::subclass::prelude::*;
|
||||||
|
|
||||||
use gst::glib::once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"ts-audiotestsrc",
|
"ts-audiotestsrc",
|
||||||
gst::Rank::None,
|
gst::Rank::NONE,
|
||||||
AudioTestSrc::static_type(),
|
AudioTestSrc::static_type(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue