mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-09-26 14:00:14 +00:00
Compare commits
605 commits
Author | SHA1 | Date | |
---|---|---|---|
|
192844c173 | ||
|
7b51a7c77b | ||
|
b5bf829876 | ||
|
90f9406377 | ||
|
73544718b4 | ||
|
ce92502d61 | ||
|
bd37999166 | ||
|
6a87d4a66a | ||
|
6849e4b4c3 | ||
|
f14efbd70c | ||
|
224215a844 | ||
|
700004b8ce | ||
|
6035bffbab | ||
|
d16b28fc2f | ||
|
f62c8658e4 | ||
|
88a52d9ea1 | ||
|
96ec17b1e9 | ||
|
1b578b6113 | ||
|
7eaf47d7b5 | ||
|
5c39500308 | ||
|
299601f7e3 | ||
|
9bb441880d | ||
|
c818778ad7 | ||
|
628f040e2d | ||
|
b9d34e1c21 | ||
|
ed6aac91bd | ||
|
497f15acd3 | ||
|
be7b3e3522 | ||
|
2f9f70bd67 | ||
|
78c490ef17 | ||
|
e8797c95e7 | ||
|
a8234a67d2 | ||
|
9b5b1d4650 | ||
|
4ee374e60c | ||
|
c5dfc87953 | ||
|
57aa8e09ea | ||
|
4cf22e91cf | ||
|
7959e37204 | ||
|
ebe7f5f663 | ||
|
63935bb680 | ||
|
c463c07871 | ||
|
09bc0a2836 | ||
|
44479cf42a | ||
|
ec66403c24 | ||
|
c7694a4a91 | ||
|
b829c41cdc | ||
|
75ed9b668f | ||
|
ae120b300f | ||
|
6c5ceca804 | ||
|
6674f8d23a | ||
|
326e5861f3 | ||
|
57f407fa89 | ||
|
c9412e663b | ||
|
99f598a45a | ||
|
ce9c1729b8 | ||
|
a145ce6ab1 | ||
|
30d247fd5c | ||
|
2061a4e310 | ||
|
80d1066a20 | ||
|
4d6c30cdb6 | ||
|
4306c9a2c9 | ||
|
bc96a99576 | ||
|
0bd9fbd615 | ||
|
f8d1d813c5 | ||
|
73d9793f5b | ||
|
3dab797c32 | ||
|
7be6a9fef4 | ||
|
2ae1e4a511 | ||
|
658b8c2231 | ||
|
0f5c0e935c | ||
|
c8853734c0 | ||
|
b290d52639 | ||
|
0ed46425f6 | ||
|
3521eb920c | ||
|
efb8d85bd0 | ||
|
7dc7f3c0cd | ||
|
48436458bb | ||
|
9b76ef35cf | ||
|
c55daa88ca | ||
|
a5c044dda4 | ||
|
e45dbc8fd9 | ||
|
f4d3f01d25 | ||
|
cae7285013 | ||
|
94313e67c5 | ||
|
a871f71515 | ||
|
426d95bc6a | ||
|
207694ca6c | ||
|
adf3e9236a | ||
|
7a5096b1e4 | ||
|
cfb0fe6a17 | ||
|
f3d7e18bcd | ||
|
cd9f38135f | ||
|
53ede03b3b | ||
|
45ec7cedd9 | ||
|
84ea10dc73 | ||
|
a978f6d942 | ||
|
87c16d8f9f | ||
|
5ea912d702 | ||
|
0be8b364f8 | ||
|
c21da79eac | ||
|
8527c0e39e | ||
|
e4e5cfd63b | ||
|
f99c519a00 | ||
|
6bab9de772 | ||
|
6a8e8055b9 | ||
|
1970a043d4 | ||
|
7c87874c28 | ||
|
b59a92b29e | ||
|
e1a387229c | ||
|
3888f65e4d | ||
|
50b941ecfc | ||
|
60302732f3 | ||
|
259066e5b1 | ||
|
c593ae5cfc | ||
|
ea16222625 | ||
|
d8b7356f3d | ||
|
bc96d439d0 | ||
|
e6ed67cbc5 | ||
|
8a3ea1192d | ||
|
b045708353 | ||
|
c545154472 | ||
|
b20ea25147 | ||
|
4ebec84f5e | ||
|
10aff0d66e | ||
|
9d3ec9da53 | ||
|
413a6baa8c | ||
|
9e2c6268cb | ||
|
4cda565a39 | ||
|
805cd6c591 | ||
|
a0e58ec359 | ||
|
9f151466b7 | ||
|
1b537c17c8 | ||
|
c3619b45aa | ||
|
f59029b57c | ||
|
b468280353 | ||
|
0ef80c4fe7 | ||
|
455996c60b | ||
|
83fe420466 | ||
|
5af4a262b8 | ||
|
b8dbfc66ca | ||
|
b15e0e1633 | ||
|
a430291725 | ||
|
0ee36ea4b5 | ||
|
a7a0bf226d | ||
|
19ea814a09 | ||
|
2a9d0d035f | ||
|
1e293e5cb8 | ||
|
fe1fe5b114 | ||
|
238768f525 | ||
|
2f99c4c560 | ||
|
9fca740851 | ||
|
9490735655 | ||
|
81b20b9329 | ||
|
ba4bd5c631 | ||
|
4b79dddc14 | ||
|
01b32ce143 | ||
|
873aeff133 | ||
|
87cc9fe6e4 | ||
|
bac0828260 | ||
|
200d8b1c0c | ||
|
dc04a53207 | ||
|
0bb334e14c | ||
|
46226106b4 | ||
|
b7b5352353 | ||
|
88a6977777 | ||
|
cb560e59a3 | ||
|
241338f43c | ||
|
5c8a989029 | ||
|
57050f66c6 | ||
|
63654c67da | ||
|
70a15e8dbe | ||
|
953e3747f2 | ||
|
e117010bc0 | ||
|
694d1fd39b | ||
|
db03c8edd1 | ||
|
ea25c9262b | ||
|
0d872ae6f8 | ||
|
7433ea79c9 | ||
|
46be4a0b1e | ||
|
43c82da25a | ||
|
da1f53f4c7 | ||
|
0524435190 | ||
|
917c458a86 | ||
|
5eaa0ca46d | ||
|
5400979e28 | ||
|
c43c08804a | ||
|
a7ebe45ff3 | ||
|
2b53c55ee6 | ||
|
04c840a1d9 | ||
|
6111663e26 | ||
|
7cea7ba6f1 | ||
|
e2e38d9494 | ||
|
676e41064b | ||
|
4524af89ee | ||
|
e5830c2ea9 | ||
|
d7fe0709a5 | ||
|
983e8b3308 | ||
|
6aff1773bd | ||
|
ebc06257b5 | ||
|
86d02890ca | ||
|
29c82cd54d | ||
|
c05563d22e | ||
|
9e80250b49 | ||
|
0b027c853b | ||
|
3246f4fb5b | ||
|
82f6accc31 | ||
|
ffad1188b9 | ||
|
353e3d1611 | ||
|
b5cb4ae831 | ||
|
14576fdf26 | ||
|
70045a36fb | ||
|
28451435a4 | ||
|
fcc7ab6b88 | ||
|
18a02f6d34 | ||
|
c5111ddcc2 | ||
|
d5917be045 | ||
|
4e6ddf6663 | ||
|
ab0a29b765 | ||
|
8062a8748f | ||
|
28fe70f479 | ||
|
c8b98dde8c | ||
|
b47aba1837 | ||
|
28931e2f09 | ||
|
1649e268c5 | ||
|
d575cd1f95 | ||
|
ac275fe10e | ||
|
b10f395c2c | ||
|
22796cee0c | ||
|
7f9dd58718 | ||
|
798ee29b98 | ||
|
040a194700 | ||
|
b69fee9abe | ||
|
a87a844604 | ||
|
5d19b26974 | ||
|
2613c57739 | ||
|
f82b9cc197 | ||
|
12c9ada9e0 | ||
|
a784ea2d0b | ||
|
90e6108ed7 | ||
|
77b2800caf | ||
|
c4636fc0cd | ||
|
6a1441203d | ||
|
811e564ff9 | ||
|
0eacca7102 | ||
|
aab36d9745 | ||
|
d96dbef08c | ||
|
7ec3c8713a | ||
|
36792404a9 | ||
|
ebc18ea0b8 | ||
|
9cf270611f | ||
|
79846af1e6 | ||
|
90bb9182c2 | ||
|
a3b3017d75 | ||
|
e16832a2a8 | ||
|
a165f1aa96 | ||
|
e192eac554 | ||
|
a31940a916 | ||
|
230c906626 | ||
|
fde0c061c2 | ||
|
a51b5bdfd3 | ||
|
8230a7ccba | ||
|
2dff5b8ae2 | ||
|
14ffbfbe83 | ||
|
771dbb4ed5 | ||
|
aaea288abf | ||
|
cfc1aed3c3 | ||
|
ba91648bd5 | ||
|
95c00c4a5c | ||
|
193bcbf055 | ||
|
ce5dca918d | ||
|
e6ce8e4f71 | ||
|
a83680ffeb | ||
|
e13e9a7a7f | ||
|
446bb7ec3e | ||
|
047f4a3f75 | ||
|
c376bfac4d | ||
|
6cea21617c | ||
|
c5357064cf | ||
|
b5c7c402b9 | ||
|
e35782a3a4 | ||
|
92064a0c41 | ||
|
c66fc90566 | ||
|
0b4c602c6f | ||
|
3b3c3baee5 | ||
|
5fe76aa785 | ||
|
5f01bcf8f4 | ||
|
60e8c44abb | ||
|
08fa853c7e | ||
|
52c764b986 | ||
|
b5f4246445 | ||
|
ea002e2e11 | ||
|
dae3d30fae | ||
|
52bd716a80 | ||
|
26611a66bd | ||
|
003ebbdf1c | ||
|
0533160d94 | ||
|
dff595193d | ||
|
86cf7a7d81 | ||
|
38a9b7a242 | ||
|
2fb93e1c12 | ||
|
8b9862052b | ||
|
5c88bd0b5f | ||
|
96de51a7b7 | ||
|
cd30854c2b | ||
|
33e3e25b49 | ||
|
248b6d2f31 | ||
|
2139f368e9 | ||
|
e72a3bfc8d | ||
|
34fee6b691 | ||
|
fddeacc358 | ||
|
5d8652e872 | ||
|
3daab0112d | ||
|
0ec7b2608c | ||
|
84ca72a833 | ||
|
ceed45cfd7 | ||
|
a29d7c0e19 | ||
|
f055c113ac | ||
|
9bcf48050b | ||
|
a649e7dead | ||
|
f255b82b55 | ||
|
7f234c88ac | ||
|
59420b1590 | ||
|
a05e3fed14 | ||
|
ca8309a5dd | ||
|
897c7dfd39 | ||
|
c82ba6ffe0 | ||
|
e56061c25e | ||
|
4c3de8b80e | ||
|
db2028c4c5 | ||
|
9ab8dee59c | ||
|
6371b82c48 | ||
|
9617731206 | ||
|
53f1ab938e | ||
|
6cb371d3e5 | ||
|
1946973c25 | ||
|
c4413f1db7 | ||
|
3ac254d34c | ||
|
30d8a7893b | ||
|
4c6bb9eefa | ||
|
39b472ce8b | ||
|
9bc2a3dbf5 | ||
|
9419730ea4 | ||
|
ec3a3610d3 | ||
|
6403d3c0ee | ||
|
b8fff2d6fc | ||
|
7cabb4f22c | ||
|
bf568714b6 | ||
|
73180e530b | ||
|
5fdd56747f | ||
|
0f3d2d6d09 | ||
|
ce98a4755e | ||
|
1ec4560b62 | ||
|
5741b6a52e | ||
|
5ee8ee8545 | ||
|
754b6487d4 | ||
|
41519511aa | ||
|
3051401aa4 | ||
|
4b3d9f586e | ||
|
f471501df5 | ||
|
eb6d3a6c6c | ||
|
86d470e82c | ||
|
e84af103a1 | ||
|
339bec6aef | ||
|
b901322c46 | ||
|
4a015d94af | ||
|
fc4a0d29c6 | ||
|
130dc49b22 | ||
|
bd4122e334 | ||
|
bd9b1d6e38 | ||
|
a26fcaf0ad | ||
|
16acea71d4 | ||
|
d5ba6c1336 | ||
|
4d19d7b0b6 | ||
|
414019af21 | ||
|
a41dc25eba | ||
|
a04ed127af | ||
|
5312131069 | ||
|
62f58620b7 | ||
|
4c8d16d09e | ||
|
547cfb44e2 | ||
|
3f16233a01 | ||
|
27a0bc5af0 | ||
|
abdd4df415 | ||
|
b158ca83f9 | ||
|
a2387d1f84 | ||
|
8df470b85c | ||
|
b42b01ba86 | ||
|
3a5f69b64c | ||
|
a6470f13c9 | ||
|
7bde0285ff | ||
|
d7494bf1db | ||
|
fa3ce573d7 | ||
|
44602238d9 | ||
|
c0696d872d | ||
|
635b31614c | ||
|
72a5b1bdb9 | ||
|
3e5316c869 | ||
|
f8effdda61 | ||
|
6eb01dc916 | ||
|
61d559521b | ||
|
e8387bf4cf | ||
|
4957921cfa | ||
|
2a00236a1f | ||
|
8e3994f641 | ||
|
c071d8cba7 | ||
|
a60cb26c27 | ||
|
51075c71f6 | ||
|
b80a723de8 | ||
|
b93113c4c6 | ||
|
3988df8463 | ||
|
7167fb78ce | ||
|
3228c36ef7 | ||
|
54979d859d | ||
|
ba202a5f87 | ||
|
0306dd6b53 | ||
|
5e32d2efbf | ||
|
c7662ce15a | ||
|
4b87796c92 | ||
|
83a562e227 | ||
|
f4486f5d61 | ||
|
4976e4ac4b | ||
|
13835a9f03 | ||
|
db16dca822 | ||
|
277cb517cd | ||
|
76b8281709 | ||
|
a7be931474 | ||
|
faf03c73ca | ||
|
9c6e48119c | ||
|
7a7b2c7b21 | ||
|
af100377ed | ||
|
e95e62d871 | ||
|
03417068dc | ||
|
63852b3b19 | ||
|
783b95884d | ||
|
03b614372d | ||
|
ab153de47f | ||
|
bac88e88b8 | ||
|
00e42854fa | ||
|
e4d7748241 | ||
|
7a79fc89d6 | ||
|
cdd548acba | ||
|
1e2e57836f | ||
|
c0f9551fc4 | ||
|
16e1f92489 | ||
|
0d7555bed0 | ||
|
d96d164c8d | ||
|
f8bb992aaf | ||
|
a53069208b | ||
|
2abf75122d | ||
|
8fba09e1ed | ||
|
75f4c66f14 | ||
|
92327be3b1 | ||
|
63c79d377f | ||
|
29ffd10b35 | ||
|
8b6c99a84e | ||
|
88c21505d2 | ||
|
a4247d5199 | ||
|
214f61abc5 | ||
|
19cf78d85f | ||
|
2fe62d3107 | ||
|
387c51f860 | ||
|
89ab9d09c8 | ||
|
f9fa7f55fc | ||
|
b156ba2c59 | ||
|
7cf66dbc61 | ||
|
9eacba1569 | ||
|
c2cda2c067 | ||
|
4ec5f99142 | ||
|
7ac1a2b753 | ||
|
8cf099f0ab | ||
|
6a703508bc | ||
|
2740c38cdd | ||
|
617652dce0 | ||
|
80abcf6ca3 | ||
|
67b8c29274 | ||
|
bc979b7ce9 | ||
|
354f072ff3 | ||
|
f806967a2e | ||
|
c6015043f5 | ||
|
a913a895c0 | ||
|
90aad36c34 | ||
|
42fe22301b | ||
|
296a12d1ea | ||
|
96c7eb9563 | ||
|
eff6b4c952 | ||
|
12bed29738 | ||
|
af57f6f17b | ||
|
1bc197db79 | ||
|
a66fc95d2a | ||
|
7f7c7a4165 | ||
|
7c67375d99 | ||
|
b59680f437 | ||
|
e2c1dad0d5 | ||
|
49bf604276 | ||
|
750206067c | ||
|
1df5b0d028 | ||
|
827cb31bac | ||
|
d72884685a | ||
|
df67a2d860 | ||
|
b89b135c93 | ||
|
2becc79dfb | ||
|
5f8aaed96b | ||
|
093bc9b9cc | ||
|
a5fa1dab79 | ||
|
f75aa5f1f8 | ||
|
130805fc50 | ||
|
5f16254059 | ||
|
2290262c2e | ||
|
fadca54d51 | ||
|
53173eb46c | ||
|
71b77c513d | ||
|
3ffba2453d | ||
|
041a1f2a8e | ||
|
b6919d7c99 | ||
|
a13dcad033 | ||
|
6ab34e1656 | ||
|
4d7809424f | ||
|
2f75087830 | ||
|
adea2428af | ||
|
4f8862e15b | ||
|
074a2b1578 | ||
|
4384934b32 | ||
|
9453d63631 | ||
|
7cb5473ba6 | ||
|
13f0483a44 | ||
|
ccf3b57a8b | ||
|
33d4969493 | ||
|
13f4085456 | ||
|
9d61e39ed2 | ||
|
3d82f9cb65 | ||
|
3699da7314 | ||
|
5c331e7e77 | ||
|
40578ae7e9 | ||
|
5a24f2d9db | ||
|
0585476687 | ||
|
b1577af7cf | ||
|
5dd1decd6a | ||
|
86eb6c2bd8 | ||
|
145664ec0d | ||
|
e026d922e4 | ||
|
5c156737a4 | ||
|
e108a908dc | ||
|
98a8b75646 | ||
|
68e0ae9a9c | ||
|
6fc969932b | ||
|
e75391139c | ||
|
3cb2454fd3 | ||
|
341ac517d1 | ||
|
2dbd99bd18 | ||
|
03d046daba | ||
|
a11e5cfd75 | ||
|
4d787df819 | ||
|
ca2fd54230 | ||
|
fdbef66c24 | ||
|
ce0ddc7be0 | ||
|
8b0398aa32 | ||
|
a88b06f73e | ||
|
a252de69b1 | ||
|
e584fdb17b | ||
|
ca9d822042 | ||
|
98db1b546e | ||
|
9647ce8895 | ||
|
b34718697c | ||
|
8eae37c525 | ||
|
68db910bc8 | ||
|
6cb19c1f18 | ||
|
85e427345e | ||
|
0ab72911ee | ||
|
954d88154c | ||
|
81a5f25f81 | ||
|
a7131fc051 | ||
|
bc81e5a6a2 | ||
|
6d3c9d931b | ||
|
d2ef4023f7 | ||
|
7344e4bab2 | ||
|
273f084c06 | ||
|
368e1cacb6 | ||
|
fe038b7a14 | ||
|
43b5cffc85 | ||
|
ce0fab9cf9 | ||
|
b243ba1577 | ||
|
01d3cef73e | ||
|
c4a968a403 | ||
|
0bd6e07346 | ||
|
4b112a9bb8 | ||
|
37785b222a | ||
|
32a02b51e1 | ||
|
0b922b0e89 | ||
|
0056c62cec | ||
|
58475f4a25 | ||
|
8b6bf18bb0 | ||
|
b6e253acbf | ||
|
de01403658 | ||
|
b3980b389d | ||
|
862f4d014c | ||
|
cd8e8cea5a | ||
|
12b4a9a03d | ||
|
70b42a4846 | ||
|
f0bb4e5bef | ||
|
6ffb1db482 | ||
|
2ac85cd8de | ||
|
60ad9b5038 | ||
|
6f06a26327 | ||
|
7d9c12f8d1 | ||
|
13140d8b8a |
1004 changed files with 45711 additions and 24274 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
||||||
target/
|
target/
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
|
||||||
|
|
215
.gitlab-ci.yml
215
.gitlab-ci.yml
|
@ -11,13 +11,16 @@
|
||||||
# - setting it to the current date and the version suffix to 0
|
# - setting it to the current date and the version suffix to 0
|
||||||
# - incrementing the version suffix
|
# - incrementing the version suffix
|
||||||
#
|
#
|
||||||
|
# Same for GST_RS_IMG_WINDOWS_TAG. There's a separate tag for it to cater for
|
||||||
|
# image-only updates that only affect Windows or only Linux.
|
||||||
|
#
|
||||||
# After each update commit your changes and push to your personal repo.
|
# After each update commit your changes and push to your personal repo.
|
||||||
# After review and ci approval merge the branch as usual.
|
# After review and ci approval merge the branch as usual.
|
||||||
#
|
#
|
||||||
# Updating the nightly image should be done by simply running a scheduled ci
|
# Updating the nightly image should be done by simply running a scheduled ci
|
||||||
# pipeline on the upstream repo with the $UPDATE_NIGHTLY variable defined.
|
# pipeline on the upstream repo with the $UPDATE_NIGHTLY variable defined.
|
||||||
|
|
||||||
.templates_sha: &templates_sha 567700e483aabed992d0a4fea84994a0472deff6
|
.templates_sha: &templates_sha 6a40df92957c8ce9ee741aaccc5daaaf70545b1e
|
||||||
|
|
||||||
include:
|
include:
|
||||||
- project: 'freedesktop/ci-templates'
|
- project: 'freedesktop/ci-templates'
|
||||||
|
@ -39,25 +42,32 @@ workflow:
|
||||||
|
|
||||||
default:
|
default:
|
||||||
interruptible: true
|
interruptible: true
|
||||||
|
# Auto-retry jobs in case of infra failures
|
||||||
|
retry:
|
||||||
|
max: 1
|
||||||
|
when:
|
||||||
|
- 'runner_system_failure'
|
||||||
|
- 'stuck_or_timeout_failure'
|
||||||
|
- 'scheduler_failure'
|
||||||
|
- 'api_failure'
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
FDO_UPSTREAM_REPO: gstreamer/gstreamer-rs
|
FDO_UPSTREAM_REPO: gstreamer/gstreamer-rs
|
||||||
|
|
||||||
# DIY CI-templates like setup for windows
|
# DIY CI-templates like setup for windows
|
||||||
WINDOWS_RUST_MINIMUM_IMAGE: "$CI_REGISTRY_IMAGE/windows:$GST_RS_IMG_TAG-main-$GST_RS_MSRV"
|
WINDOWS_RUST_MINIMUM_IMAGE: "$CI_REGISTRY_IMAGE/windows:$GST_RS_IMG_WINDOWS_TAG-main-$GST_RS_MSRV"
|
||||||
WINDOWS_RUST_MINIMUM_UPSTREAM_IMAGE: "$CI_REGISTRY/$FDO_UPSTREAM_REPO/windows:$GST_RS_IMG_TAG-main-$GST_RS_MSRV"
|
WINDOWS_RUST_MINIMUM_UPSTREAM_IMAGE: "$CI_REGISTRY/$FDO_UPSTREAM_REPO/windows:$GST_RS_IMG_WINDOWS_TAG-main-$GST_RS_MSRV"
|
||||||
WINDOWS_RUST_STABLE_IMAGE: "$CI_REGISTRY_IMAGE/windows:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
WINDOWS_RUST_STABLE_IMAGE: "$CI_REGISTRY_IMAGE/windows:$GST_RS_IMG_WINDOWS_TAG-main-$GST_RS_STABLE"
|
||||||
WINDOWS_RUST_STABLE_UPSTREAM_IMAGE: "$CI_REGISTRY/$FDO_UPSTREAM_REPO/windows:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
WINDOWS_RUST_STABLE_UPSTREAM_IMAGE: "$CI_REGISTRY/$FDO_UPSTREAM_REPO/windows:$GST_RS_IMG_WINDOWS_TAG-main-$GST_RS_STABLE"
|
||||||
|
|
||||||
RUST_DOCS_FLAGS: "--extern-html-root-url=muldiv=https://docs.rs/muldiv/1.0.0/muldiv/ -Z unstable-options"
|
RUST_DOCS_FLAGS: "--cfg docsrs --extern-html-root-url=muldiv=https://docs.rs/muldiv/1.0.0/muldiv/ -Z unstable-options --generate-link-to-definition"
|
||||||
NAMESPACE: gstreamer
|
NAMESPACE: gstreamer
|
||||||
# format is <branch>=<name>
|
# format is <branch>=<name>
|
||||||
# the name is used in the URL
|
# the name is used in the URL
|
||||||
# latest release must be at the top
|
# latest release must be at the top
|
||||||
# (only relevant on main branch)
|
# (only relevant on main branch)
|
||||||
RELEASES:
|
RELEASES:
|
||||||
0.19=0.19
|
0.23=0.23
|
||||||
0.18=0.18
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- "trigger"
|
- "trigger"
|
||||||
|
@ -74,6 +84,8 @@ trigger:
|
||||||
stage: 'trigger'
|
stage: 'trigger'
|
||||||
variables:
|
variables:
|
||||||
GIT_STRATEGY: none
|
GIT_STRATEGY: none
|
||||||
|
GIT_SUBMODULE_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:
|
||||||
|
@ -89,41 +101,34 @@ trigger:
|
||||||
when: 'manual'
|
when: 'manual'
|
||||||
allow_failure: false
|
allow_failure: false
|
||||||
|
|
||||||
.debian:11:
|
.debian:12:
|
||||||
needs: []
|
needs: []
|
||||||
variables:
|
variables:
|
||||||
FDO_DISTRIBUTION_VERSION: 'bullseye-slim'
|
FDO_DISTRIBUTION_VERSION: 'bookworm-slim'
|
||||||
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.toml
|
||||||
# If cargo exists assume we probably will want to update
|
|
||||||
# the lockfile
|
|
||||||
- |
|
|
||||||
if command -v cargo; then
|
|
||||||
cargo generate-lockfile --color=always
|
|
||||||
cargo update --color=always
|
|
||||||
fi
|
|
||||||
|
|
||||||
.debian:11-base:
|
.debian:12-base:
|
||||||
extends: .debian:11
|
extends: .debian:12
|
||||||
variables:
|
variables:
|
||||||
FDO_DISTRIBUTION_TAG: 'base-$GST_RS_IMG_TAG'
|
FDO_DISTRIBUTION_TAG: 'base-$GST_RS_IMG_TAG'
|
||||||
|
|
||||||
.debian:11-stable:
|
.debian:12-stable:
|
||||||
extends: .debian:11
|
extends: .debian:12
|
||||||
variables:
|
variables:
|
||||||
RUST_IMAGE_FULL: "1"
|
RUST_IMAGE_FULL: "1"
|
||||||
FDO_DISTRIBUTION_TAG: '$GST_RS_STABLE-$GST_RS_IMG_TAG'
|
FDO_DISTRIBUTION_TAG: '$GST_RS_STABLE-$GST_RS_IMG_TAG'
|
||||||
FDO_DISTRIBUTION_EXEC: 'bash ci/install-rust.sh $GST_RS_STABLE $RUST_IMAGE_FULL'
|
FDO_DISTRIBUTION_EXEC: 'bash ci/install-rust.sh $GST_RS_STABLE $RUST_IMAGE_FULL'
|
||||||
|
|
||||||
.debian:11-msrv:
|
.debian:12-msrv:
|
||||||
extends: .debian:11
|
extends: .debian:12
|
||||||
variables:
|
variables:
|
||||||
FDO_DISTRIBUTION_TAG: '$GST_RS_MSRV-$GST_RS_IMG_TAG'
|
FDO_DISTRIBUTION_TAG: '$GST_RS_MSRV-$GST_RS_IMG_TAG'
|
||||||
FDO_DISTRIBUTION_EXEC: 'bash ci/install-rust.sh $GST_RS_MSRV $RUST_IMAGE_FULL'
|
FDO_DISTRIBUTION_EXEC: 'bash ci/install-rust.sh $GST_RS_MSRV $RUST_IMAGE_FULL'
|
||||||
|
|
||||||
.debian:11-nightly:
|
.debian:12-nightly:
|
||||||
extends: .debian:11
|
extends: .debian:12
|
||||||
variables:
|
variables:
|
||||||
FDO_DISTRIBUTION_TAG: 'nightly-$GST_RS_IMG_TAG'
|
FDO_DISTRIBUTION_TAG: 'nightly-$GST_RS_IMG_TAG'
|
||||||
FDO_DISTRIBUTION_EXEC: 'bash ci/install-rust.sh nightly $RUST_IMAGE_FULL'
|
FDO_DISTRIBUTION_EXEC: 'bash ci/install-rust.sh nightly $RUST_IMAGE_FULL'
|
||||||
|
@ -134,48 +139,56 @@ trigger:
|
||||||
stage: container-base
|
stage: container-base
|
||||||
variables:
|
variables:
|
||||||
FDO_DISTRIBUTION_PACKAGES: >-
|
FDO_DISTRIBUTION_PACKAGES: >-
|
||||||
build-essential curl python3-setuptools liborc-0.4-dev libglib2.0-dev
|
build-essential curl python3-setuptools libglib2.0-dev libxml2-dev
|
||||||
libxml2-dev libgtk-3-dev libegl1-mesa libgles2-mesa libgl1-mesa-dri
|
libdrm-dev libegl1-mesa-dev libgl1-mesa-dev libgbm-dev libgles2-mesa-dev
|
||||||
libgl1-mesa-glx libwayland-egl1-mesa xz-utils libssl-dev git wget
|
libgl1-mesa-dri libegl-dev libgl1-mesa-glx libwayland-egl1-mesa xz-utils
|
||||||
ca-certificates ninja-build python3-pip flex bison libglib2.0-dev
|
libssl-dev git wget ca-certificates ninja-build python3-pip flex bison
|
||||||
libx11-dev libx11-xcb-dev libsoup2.4-dev libvorbis-dev libogg-dev
|
libglib2.0-dev libx11-dev libx11-xcb-dev libsoup2.4-dev libvorbis-dev
|
||||||
libtheora-dev libmatroska-dev libvpx-dev libopus-dev libgraphene-1.0-dev
|
libogg-dev libtheora-dev libmatroska-dev libvpx-dev libopus-dev
|
||||||
libjpeg-dev libwayland-dev python3-gi libavcodec-dev libavformat-dev
|
libgraphene-1.0-dev libjpeg-dev libwayland-dev wayland-protocols
|
||||||
libavutil-dev libavfilter-dev libswscale-dev yasm libx264-dev
|
python3-gi libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev
|
||||||
|
libswscale-dev yasm libx264-dev libfontconfig-dev libfreetype-dev
|
||||||
|
libxkbcommon-dev libxi-dev libxcb-render0-dev libxcb-shm0-dev
|
||||||
|
libxcb1-dev libxext-dev libxrender-dev libxrandr-dev libxcursor-dev
|
||||||
|
libxdamage-dev libxfixes-dev libxinerama-dev libgudev-1.0-dev
|
||||||
|
libpango1.0-dev libcairo2-dev libjson-glib-dev libgdk-pixbuf-2.0-dev
|
||||||
|
libtiff-dev libpng-dev libjpeg-dev libepoxy-dev libsass-dev sassc
|
||||||
|
libcsound64-dev llvm clang nasm libsodium-dev libwebp-dev
|
||||||
|
libflac-dev libmysofa-dev libgtk-4-dev
|
||||||
FDO_DISTRIBUTION_EXEC: >-
|
FDO_DISTRIBUTION_EXEC: >-
|
||||||
bash ci/install-gst.sh &&
|
bash ci/install-gst.sh &&
|
||||||
bash ci/install-gtk4.sh &&
|
bash ci/install-dav1d.sh &&
|
||||||
pip3 install git+http://gitlab.freedesktop.org/freedesktop/ci-templates
|
pip3 install --break-system-packages git+http://gitlab.freedesktop.org/freedesktop/ci-templates
|
||||||
|
|
||||||
.build-final-image:
|
.build-final-image:
|
||||||
extends:
|
extends:
|
||||||
- .fdo.container-build@debian
|
- .fdo.container-build@debian
|
||||||
stage: container-final
|
stage: container-final
|
||||||
variables:
|
variables:
|
||||||
FDO_BASE_IMAGE: '$CI_REGISTRY_IMAGE/debian/bullseye-slim:base-$GST_RS_IMG_TAG'
|
FDO_BASE_IMAGE: '$CI_REGISTRY_IMAGE/debian/bookworm-slim:base-$GST_RS_IMG_TAG'
|
||||||
|
|
||||||
build-base:
|
build-base:
|
||||||
extends:
|
extends:
|
||||||
- .build-base-image
|
- .build-base-image
|
||||||
- .debian:11-base
|
- .debian:12-base
|
||||||
|
|
||||||
build-stable:
|
build-stable:
|
||||||
needs: ["build-base"]
|
needs: ["build-base"]
|
||||||
extends:
|
extends:
|
||||||
- .build-final-image
|
- .build-final-image
|
||||||
- .debian:11-stable
|
- .debian:12-stable
|
||||||
|
|
||||||
build-msrv:
|
build-msrv:
|
||||||
needs: ["build-base"]
|
needs: ["build-base"]
|
||||||
extends:
|
extends:
|
||||||
- .build-final-image
|
- .build-final-image
|
||||||
- .debian:11-msrv
|
- .debian:12-msrv
|
||||||
|
|
||||||
build-nightly:
|
build-nightly:
|
||||||
needs: ["build-base"]
|
needs: ["build-base"]
|
||||||
extends:
|
extends:
|
||||||
- .build-final-image
|
- .build-final-image
|
||||||
- .debian:11-nightly
|
- .debian:12-nightly
|
||||||
|
|
||||||
update-nightly:
|
update-nightly:
|
||||||
extends: build-nightly
|
extends: build-nightly
|
||||||
|
@ -192,79 +205,19 @@ update-nightly:
|
||||||
|
|
||||||
.img-stable:
|
.img-stable:
|
||||||
extends:
|
extends:
|
||||||
- .debian:11-stable
|
- .debian:12-stable
|
||||||
- .dist-debian-container
|
- .dist-debian-container
|
||||||
|
|
||||||
.img-msrv:
|
.img-msrv:
|
||||||
extends:
|
extends:
|
||||||
- .debian:11-msrv
|
- .debian:12-msrv
|
||||||
- .dist-debian-container
|
- .dist-debian-container
|
||||||
|
|
||||||
.img-nightly:
|
.img-nightly:
|
||||||
extends:
|
extends:
|
||||||
- .debian:11-nightly
|
- .debian:12-nightly
|
||||||
- .dist-debian-container
|
- .dist-debian-container
|
||||||
|
|
||||||
# GST_PLUGINS_RS_TOKEN is a variable of type 'Var' defined in gstreamer-rs CI
|
|
||||||
# settings and containing a gst-plugins-rs pipeline trigger token
|
|
||||||
.plugins-update:
|
|
||||||
stage: deploy
|
|
||||||
script:
|
|
||||||
- |
|
|
||||||
# FDO_DISTRIBUTION_IMAGE still has indirections
|
|
||||||
- echo $FDO_DISTRIBUTION_IMAGE
|
|
||||||
- DISTRO_IMAGE=$(eval echo ${FDO_DISTRIBUTION_IMAGE})
|
|
||||||
- echo $DISTRO_IMAGE
|
|
||||||
# retrieve the infos from the registry
|
|
||||||
- JSON_IMAGE=$(skopeo inspect docker://$DISTRO_IMAGE)
|
|
||||||
- IMAGE_PIPELINE_ID=$(echo $JSON_IMAGE | jq -r '.Labels["fdo.pipeline_id"]')
|
|
||||||
- echo $IMAGE_PIPELINE_ID
|
|
||||||
- echo $CI_PIPELINE_ID
|
|
||||||
- |
|
|
||||||
if [[ x"$IMAGE_PIPELINE_ID" == x"$CI_PIPELINE_ID" ]]; then
|
|
||||||
echo "Image has been updated, notify gst-plugins-rs"
|
|
||||||
curl -X POST -F "token=$GST_PLUGINS_RS_TOKEN" -F "ref=main" -F "variables[UPDATE_IMG]=$UPDATE_IMG" https://gitlab.freedesktop.org/api/v4/projects/1400/trigger/pipeline
|
|
||||||
else
|
|
||||||
echo "Image has not been updated, ignore"
|
|
||||||
fi
|
|
||||||
rules:
|
|
||||||
- if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "gstreamer/gstreamer-rs"'
|
|
||||||
|
|
||||||
# Those jobs need to use another image as ours doesn't have 'skopeo'
|
|
||||||
# and it's not easily installable in Debian stable for now.
|
|
||||||
plugins-update-stable:
|
|
||||||
extends:
|
|
||||||
- .plugins-update
|
|
||||||
- .img-stable
|
|
||||||
image: quay.io/freedesktop.org/ci-templates:container-build-base-2021-07-29.0
|
|
||||||
needs:
|
|
||||||
- job: 'build-stable'
|
|
||||||
artifacts: false
|
|
||||||
variables:
|
|
||||||
UPDATE_IMG: "stable"
|
|
||||||
|
|
||||||
plugins-update-msrv:
|
|
||||||
extends:
|
|
||||||
- .plugins-update
|
|
||||||
- .img-msrv
|
|
||||||
image: quay.io/freedesktop.org/ci-templates:container-build-base-2021-07-29.0
|
|
||||||
needs:
|
|
||||||
- job: 'build-msrv'
|
|
||||||
artifacts: false
|
|
||||||
variables:
|
|
||||||
UPDATE_IMG: "msrv"
|
|
||||||
|
|
||||||
plugins-update-nightly:
|
|
||||||
extends:
|
|
||||||
- .plugins-update
|
|
||||||
- .img-nightly
|
|
||||||
image: quay.io/freedesktop.org/ci-templates:container-build-base-2021-07-29.0
|
|
||||||
needs:
|
|
||||||
- job: 'build-nightly'
|
|
||||||
artifacts: false
|
|
||||||
variables:
|
|
||||||
UPDATE_IMG: "nightly"
|
|
||||||
|
|
||||||
.cargo_test_var: &cargo_test
|
.cargo_test_var: &cargo_test
|
||||||
- ./ci/run-cargo-test.sh
|
- ./ci/run-cargo-test.sh
|
||||||
|
|
||||||
|
@ -371,6 +324,9 @@ test nightly sys:
|
||||||
rustfmt:
|
rustfmt:
|
||||||
extends: .img-stable
|
extends: .img-stable
|
||||||
stage: "lint"
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: "none"
|
||||||
script:
|
script:
|
||||||
- cargo fmt --version
|
- cargo fmt --version
|
||||||
- cargo fmt -- --color=always --check
|
- cargo fmt -- --color=always --check
|
||||||
|
@ -381,12 +337,27 @@ rustfmt:
|
||||||
check commits:
|
check commits:
|
||||||
extends: .img-stable
|
extends: .img-stable
|
||||||
stage: "lint"
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: "none"
|
||||||
script:
|
script:
|
||||||
- ci-fairy check-commits --textwidth 0 --no-signed-off-by
|
- ci-fairy check-commits --textwidth 0 --no-signed-off-by
|
||||||
needs:
|
needs:
|
||||||
- job: 'build-stable'
|
- job: 'build-stable'
|
||||||
artifacts: false
|
artifacts: false
|
||||||
|
|
||||||
|
typos:
|
||||||
|
extends: .img-stable
|
||||||
|
stage: "lint"
|
||||||
|
tags: [ 'placeholder-job' ]
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: "none"
|
||||||
|
script:
|
||||||
|
- typos
|
||||||
|
needs:
|
||||||
|
- job: 'build-stable'
|
||||||
|
artifacts: false
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
extends: .img-stable
|
extends: .img-stable
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
|
@ -409,13 +380,16 @@ deny:
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||||
script:
|
script:
|
||||||
- cargo deny --color=always check
|
- cargo update --color=always
|
||||||
|
- cargo deny --color=always --workspace --all-features check all
|
||||||
|
|
||||||
gir-checks:
|
gir-checks:
|
||||||
variables:
|
variables:
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
extends: .img-stable
|
extends: .img-stable
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
|
tags:
|
||||||
|
- "gstreamer"
|
||||||
needs:
|
needs:
|
||||||
- job: 'build-stable'
|
- job: 'build-stable'
|
||||||
artifacts: false
|
artifacts: false
|
||||||
|
@ -426,13 +400,17 @@ gir-checks:
|
||||||
outdated:
|
outdated:
|
||||||
extends: .img-stable
|
extends: .img-stable
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
|
variables:
|
||||||
|
GIT_SUBMODULE_STRATEGY: "none"
|
||||||
needs:
|
needs:
|
||||||
- job: 'build-stable'
|
- job: 'build-stable'
|
||||||
artifacts: false
|
artifacts: false
|
||||||
rules:
|
rules:
|
||||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||||
script:
|
script:
|
||||||
- cargo outdated --color=always --root-deps-only --exit-code 1 -v
|
- cargo update --color=always
|
||||||
|
# Ignore derive_more until we can depend on Rust 1.75 or newer
|
||||||
|
- cargo outdated --color=always --root-deps-only --exit-code 1 -v --ignore derive_more
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
@ -451,25 +429,26 @@ coverage:
|
||||||
LLVM_PROFILE_FILE: "gstreamer-rs-%p-%m.profraw"
|
LLVM_PROFILE_FILE: "gstreamer-rs-%p-%m.profraw"
|
||||||
script:
|
script:
|
||||||
- *cargo_test
|
- *cargo_test
|
||||||
# generate html report
|
# generate html and cobertura report for gitlab integration
|
||||||
- grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing --ignore "*target*" --ignore "*/sys/*" --ignore "examples/*" --ignore "tutorials/*" --ignore "*/build.rs" -o ./coverage/
|
- mkdir -p coverage
|
||||||
# generate cobertura report for gitlab integration
|
- grcov . --binary-path ./target/debug/ -s . -t html,cobertura --branch --ignore-not-existing --ignore "*target*" --ignore "*/sys/*" --ignore "examples/*" --ignore "tutorials/*" --ignore "*/build.rs" -o ./coverage/
|
||||||
- grcov . --binary-path ./target/debug/ -s . -t cobertura --branch --ignore-not-existing --ignore "*target*" --ignore "*/sys/*" --ignore "examples/*" --ignore "tutorials/*" --ignore "*/build.rs" -o coverage.xml
|
|
||||||
# output coverage summary for gitlab parsing.
|
# output coverage summary for gitlab parsing.
|
||||||
# TODO: use grcov once https://github.com/mozilla/grcov/issues/556 is fixed
|
# TODO: use grcov once https://github.com/mozilla/grcov/issues/556 is fixed
|
||||||
- grep % coverage/index.html | head -1 ; true
|
- grep % coverage/html/index.html | head -1 ; true
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- 'coverage'
|
- 'coverage'
|
||||||
reports:
|
reports:
|
||||||
coverage_report:
|
coverage_report:
|
||||||
coverage_format: cobertura
|
coverage_format: cobertura
|
||||||
path: coverage.xml
|
path: "coverage/cobertura.xml"
|
||||||
|
|
||||||
doc-stripping:
|
doc-stripping:
|
||||||
variables:
|
variables:
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
extends: .img-nightly
|
extends: .img-nightly
|
||||||
|
tags:
|
||||||
|
- "gstreamer"
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
needs:
|
needs:
|
||||||
- job: 'build-nightly'
|
- job: 'build-nightly'
|
||||||
|
@ -484,6 +463,8 @@ regen-check:
|
||||||
variables:
|
variables:
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
extends: .img-nightly
|
extends: .img-nightly
|
||||||
|
tags:
|
||||||
|
- "gstreamer"
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
needs:
|
needs:
|
||||||
- job: 'build-nightly'
|
- job: 'build-nightly'
|
||||||
|
@ -498,6 +479,8 @@ docs:
|
||||||
GIT_SUBMODULE_STRATEGY: recursive
|
GIT_SUBMODULE_STRATEGY: recursive
|
||||||
extends: .img-nightly
|
extends: .img-nightly
|
||||||
stage: 'extras'
|
stage: 'extras'
|
||||||
|
tags:
|
||||||
|
- "gstreamer"
|
||||||
needs:
|
needs:
|
||||||
- job: 'build-nightly'
|
- job: 'build-nightly'
|
||||||
artifacts: false
|
artifacts: false
|
||||||
|
@ -509,8 +492,9 @@ docs:
|
||||||
- PATH=~/.cargo/bin/:$PATH ./generator.py --gir-files-directories gir-files gst-gir-files --embed-docs --no-fmt
|
- PATH=~/.cargo/bin/:$PATH ./generator.py --gir-files-directories gir-files gst-gir-files --embed-docs --no-fmt
|
||||||
- |
|
- |
|
||||||
RUSTDOCFLAGS="$RUST_DOCS_FLAGS"
|
RUSTDOCFLAGS="$RUST_DOCS_FLAGS"
|
||||||
|
RUSTFLAGS="--cfg docsrs"
|
||||||
eval $(./gir-rustdoc.py pre-docs)
|
eval $(./gir-rustdoc.py pre-docs)
|
||||||
cargo +nightly doc --workspace --exclude examples --exclude tutorials --color=always --features=dox --no-deps
|
cargo +nightly doc --workspace --exclude examples --exclude tutorials --all-features --color=always --no-deps
|
||||||
- mv target/doc docs
|
- mv target/doc docs
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
|
@ -546,6 +530,7 @@ pages:
|
||||||
|
|
||||||
.windows rust docker build:
|
.windows rust docker build:
|
||||||
stage: 'container-final'
|
stage: 'container-final'
|
||||||
|
timeout: '2h'
|
||||||
needs: []
|
needs: []
|
||||||
variables:
|
variables:
|
||||||
# Unlike the buildah/linux jobs, this file
|
# Unlike the buildah/linux jobs, this file
|
||||||
|
@ -555,11 +540,11 @@ pages:
|
||||||
# We also don't need a CONTEXT_DIR var as its also
|
# We also don't need a CONTEXT_DIR var as its also
|
||||||
# hardcoded to be windows-docker/
|
# hardcoded to be windows-docker/
|
||||||
DOCKERFILE: 'ci/windows-docker/Dockerfile'
|
DOCKERFILE: 'ci/windows-docker/Dockerfile'
|
||||||
GST_UPSTREAM_BRANCH: 'main'
|
|
||||||
tags:
|
tags:
|
||||||
- 'windows'
|
- 'windows'
|
||||||
- 'shell'
|
- 'shell'
|
||||||
- '2022'
|
- '2022'
|
||||||
|
- "gstreamer-windows"
|
||||||
script:
|
script:
|
||||||
# We need to pass an array and to resolve the env vars, so we can't use a variable:
|
# We need to pass an array and to resolve the env vars, so we can't use a variable:
|
||||||
- $DOCKER_BUILD_ARGS = @("--build-arg", "DEFAULT_BRANCH=$GST_UPSTREAM_BRANCH", "--build-arg", "RUST_VERSION=$RUST_VERSION")
|
- $DOCKER_BUILD_ARGS = @("--build-arg", "DEFAULT_BRANCH=$GST_UPSTREAM_BRANCH", "--build-arg", "RUST_VERSION=$RUST_VERSION")
|
||||||
|
@ -580,7 +565,6 @@ windows rust docker stable:
|
||||||
|
|
||||||
windows rust docker msrv:
|
windows rust docker msrv:
|
||||||
extends: '.windows rust docker build'
|
extends: '.windows rust docker build'
|
||||||
when: 'manual'
|
|
||||||
variables:
|
variables:
|
||||||
RUST_IMAGE: !reference [variables, "WINDOWS_RUST_MINIMUM_IMAGE"]
|
RUST_IMAGE: !reference [variables, "WINDOWS_RUST_MINIMUM_IMAGE"]
|
||||||
RUST_UPSTREAM_IMAGE: !reference [variables, "WINDOWS_RUST_MINIMUM_UPSTREAM_IMAGE"]
|
RUST_UPSTREAM_IMAGE: !reference [variables, "WINDOWS_RUST_MINIMUM_UPSTREAM_IMAGE"]
|
||||||
|
@ -592,6 +576,7 @@ windows rust docker msrv:
|
||||||
- 'docker'
|
- 'docker'
|
||||||
- 'windows'
|
- 'windows'
|
||||||
- '2022'
|
- '2022'
|
||||||
|
- "gstreamer-windows"
|
||||||
script:
|
script:
|
||||||
# Skip -sys tests as they don't work
|
# Skip -sys tests as they don't work
|
||||||
# https://github.com/gtk-rs/gtk3-rs/issues/54
|
# https://github.com/gtk-rs/gtk3-rs/issues/54
|
||||||
|
|
33
.gitlab/issue_templates/Bug.md
Normal file
33
.gitlab/issue_templates/Bug.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
### Describe your issue
|
||||||
|
<!-- a clear and concise summary of the bug. -->
|
||||||
|
<!-- For any GStreamer usage question, please contact the community using the #gstreamer channel on IRC https://www.oftc.net/ or the mailing list on https://gstreamer.freedesktop.org/lists/ -->
|
||||||
|
|
||||||
|
#### Expected Behavior
|
||||||
|
<!-- What did you expect to happen -->
|
||||||
|
|
||||||
|
#### Observed Behavior
|
||||||
|
<!-- What actually happened -->
|
||||||
|
|
||||||
|
#### Setup
|
||||||
|
- **Operating System:**
|
||||||
|
- **Device:** Computer / Tablet / Mobile / Virtual Machine <!-- Delete as appropriate !-->
|
||||||
|
- **gstreamer-rs Version:**
|
||||||
|
- **GStreamer Version:**
|
||||||
|
- **Command line:**
|
||||||
|
|
||||||
|
### Steps to reproduce the bug
|
||||||
|
<!-- please fill in exact steps which reproduce the bug on your system, for example: -->
|
||||||
|
1. open terminal
|
||||||
|
2. type `command`
|
||||||
|
|
||||||
|
### How reproducible is the bug?
|
||||||
|
<!-- The reproducibility of the bug is Always/Intermittent/Only once after doing a very specific set of steps-->
|
||||||
|
|
||||||
|
### Screenshots if relevant
|
||||||
|
|
||||||
|
### Solutions you have tried
|
||||||
|
|
||||||
|
### Related non-duplicate issues
|
||||||
|
|
||||||
|
### Additional Information
|
||||||
|
<!-- Any other information such as logs. Make use of <details> for long output -->
|
3124
Cargo.lock
generated
Normal file
3124
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
69
Cargo.toml
69
Cargo.toml
|
@ -1,7 +1,9 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
default-members = [
|
default-members = [
|
||||||
"gstreamer/sys",
|
"gstreamer/sys",
|
||||||
|
"gstreamer-analytics/sys",
|
||||||
"gstreamer-app/sys",
|
"gstreamer-app/sys",
|
||||||
"gstreamer-audio/sys",
|
"gstreamer-audio/sys",
|
||||||
"gstreamer-base/sys",
|
"gstreamer-base/sys",
|
||||||
|
@ -21,6 +23,7 @@ default-members = [
|
||||||
"gstreamer-video/sys",
|
"gstreamer-video/sys",
|
||||||
"gstreamer-webrtc/sys",
|
"gstreamer-webrtc/sys",
|
||||||
"gstreamer",
|
"gstreamer",
|
||||||
|
"gstreamer-analytics",
|
||||||
"gstreamer-app",
|
"gstreamer-app",
|
||||||
"gstreamer-audio",
|
"gstreamer-audio",
|
||||||
"gstreamer-base",
|
"gstreamer-base",
|
||||||
|
@ -36,6 +39,7 @@ default-members = [
|
||||||
"gstreamer-rtsp",
|
"gstreamer-rtsp",
|
||||||
"gstreamer-rtsp-server",
|
"gstreamer-rtsp-server",
|
||||||
"gstreamer-sdp",
|
"gstreamer-sdp",
|
||||||
|
"gstreamer-tag",
|
||||||
"gstreamer-validate",
|
"gstreamer-validate",
|
||||||
"gstreamer-video",
|
"gstreamer-video",
|
||||||
"gstreamer-webrtc",
|
"gstreamer-webrtc",
|
||||||
|
@ -45,6 +49,7 @@ default-members = [
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"gstreamer/sys",
|
"gstreamer/sys",
|
||||||
|
"gstreamer-analytics/sys",
|
||||||
"gstreamer-app/sys",
|
"gstreamer-app/sys",
|
||||||
"gstreamer-audio/sys",
|
"gstreamer-audio/sys",
|
||||||
"gstreamer-base/sys",
|
"gstreamer-base/sys",
|
||||||
|
@ -69,6 +74,7 @@ members = [
|
||||||
"gstreamer-webrtc/sys",
|
"gstreamer-webrtc/sys",
|
||||||
"gstreamer-allocators/sys",
|
"gstreamer-allocators/sys",
|
||||||
"gstreamer",
|
"gstreamer",
|
||||||
|
"gstreamer-analytics",
|
||||||
"gstreamer-app",
|
"gstreamer-app",
|
||||||
"gstreamer-audio",
|
"gstreamer-audio",
|
||||||
"gstreamer-base",
|
"gstreamer-base",
|
||||||
|
@ -88,6 +94,7 @@ members = [
|
||||||
"gstreamer-rtsp",
|
"gstreamer-rtsp",
|
||||||
"gstreamer-rtsp-server",
|
"gstreamer-rtsp-server",
|
||||||
"gstreamer-sdp",
|
"gstreamer-sdp",
|
||||||
|
"gstreamer-tag",
|
||||||
"gstreamer-validate",
|
"gstreamer-validate",
|
||||||
"gstreamer-video",
|
"gstreamer-video",
|
||||||
"gstreamer-webrtc",
|
"gstreamer-webrtc",
|
||||||
|
@ -98,3 +105,65 @@ members = [
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = ["gir"]
|
exclude = ["gir"]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "0.24.0"
|
||||||
|
categories = ["api-bindings", "multimedia"]
|
||||||
|
repository = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
||||||
|
homepage = "https://gstreamer.freedesktop.org"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.71.1"
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
gio-sys = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
glib = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
glib-sys = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
gobject-sys = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "master" }
|
||||||
|
gstreamer-allocators-sys = { path = "gstreamer-allocators/sys" }
|
||||||
|
gstreamer-analytics-sys = { path = "gstreamer-analytics/sys" }
|
||||||
|
gstreamer-app-sys = { path = "gstreamer-app/sys" }
|
||||||
|
gstreamer-audio-sys = { path = "./gstreamer-audio/sys"}
|
||||||
|
gstreamer-base-sys = { path = "./gstreamer-base/sys"}
|
||||||
|
gstreamer-check-sys = { path = "./gstreamer-check/sys" }
|
||||||
|
gstreamer-controller-sys = { path = "./gstreamer-controller/sys" }
|
||||||
|
gstreamer-editing-services-sys = { path = "./gstreamer-editing-services/sys"}
|
||||||
|
gstreamer-gl-egl-sys = { path = "./gstreamer-gl/egl/sys"}
|
||||||
|
gstreamer-gl-wayland-sys = { path = "./gstreamer-gl/wayland/sys"}
|
||||||
|
gstreamer-gl-x11-sys = { path = "./gstreamer-gl/x11/sys"}
|
||||||
|
gstreamer-gl-sys = { path = "./gstreamer-gl/sys"}
|
||||||
|
gstreamer-mpegts-sys = { path = "./gstreamer-mpegts/sys"}
|
||||||
|
gstreamer-net-sys = { path = "./gstreamer-net/sys"}
|
||||||
|
gstreamer-pbutils-sys = { path = "./gstreamer-pbutils/sys"}
|
||||||
|
gstreamer-play-sys = { path = "./gstreamer-play/sys" }
|
||||||
|
gstreamer-player-sys = { path = "./gstreamer-player/sys" }
|
||||||
|
gstreamer-rtp-sys = { path = "./gstreamer-rtp/sys" }
|
||||||
|
gstreamer-rtsp-sys = { path = "./gstreamer-rtsp/sys"}
|
||||||
|
gstreamer-rtsp-server-sys = { path = "./gstreamer-rtsp-server/sys" }
|
||||||
|
gstreamer-sdp-sys = { path = "./gstreamer-sdp/sys"}
|
||||||
|
gstreamer-tag-sys = { path = "./gstreamer-tag/sys" }
|
||||||
|
gstreamer-sys = { path = "./gstreamer/sys"}
|
||||||
|
gstreamer-validate-sys = { path = "./gstreamer-validate/sys" }
|
||||||
|
gstreamer-video-sys = { path = "./gstreamer-video/sys"}
|
||||||
|
gstreamer-webrtc-sys = { path = "./gstreamer-webrtc/sys" }
|
||||||
|
ges = { package = "gstreamer-editing-services", path = "./gstreamer-editing-services" }
|
||||||
|
gst = { package = "gstreamer", path = "./gstreamer" }
|
||||||
|
gst-allocators = { package = "gstreamer-allocators", path = "./gstreamer-allocators" }
|
||||||
|
gst-app = { package = "gstreamer-app", path = "./gstreamer-app" }
|
||||||
|
gst-audio = { package = "gstreamer-audio", path = "./gstreamer-audio" }
|
||||||
|
gst-base = { package = "gstreamer-base", path = "./gstreamer-base" }
|
||||||
|
gst-check = { package = "gstreamer-check", path = "./gstreamer-check" }
|
||||||
|
gst-gl = { package = "gstreamer-gl", path = "./gstreamer-gl" }
|
||||||
|
gst-gl-egl = { package = "gstreamer-gl-egl", path = "./gstreamer-gl/egl" }
|
||||||
|
gst-gl-x11 = { package = "gstreamer-gl-x11", path = "./gstreamer-gl/x11" }
|
||||||
|
gst-net = { package = "gstreamer-net", path = "./gstreamer-net" }
|
||||||
|
gst-pbutils = { package = "gstreamer-pbutils", path = "./gstreamer-pbutils" }
|
||||||
|
gst-play = { package = "gstreamer-play", path = "./gstreamer-play" }
|
||||||
|
gst-player = { package = "gstreamer-player", path = "./gstreamer-player" }
|
||||||
|
gst-rtsp = { package = "gstreamer-rtsp", path = "./gstreamer-rtsp" }
|
||||||
|
gst-rtsp-server = { package = "gstreamer-rtsp-server", path = "./gstreamer-rtsp-server" }
|
||||||
|
gst-sdp = { package = "gstreamer-sdp", path = "./gstreamer-sdp" }
|
||||||
|
gst-video = { package = "gstreamer-video", path = "./gstreamer-video" }
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
variables:
|
variables:
|
||||||
GST_RS_IMG_TAG: '2023-01-26.0'
|
GST_RS_IMG_TAG: "2024-09-12.1"
|
||||||
GST_RS_STABLE: '1.67.0'
|
GST_RS_IMG_WINDOWS_TAG: "2024-09-12.1"
|
||||||
GST_RS_MSRV: '1.64.0'
|
GST_RS_STABLE: "1.81.0"
|
||||||
|
GST_RS_MSRV: "1.71.1"
|
||||||
|
# The branch we use to build GStreamer from in the docker images
|
||||||
|
# Ex. main, 1.24, my-test-branch
|
||||||
|
GST_UPSTREAM_BRANCH: 'main'
|
||||||
|
|
11
ci/install-dav1d.sh
Normal file
11
ci/install-dav1d.sh
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RELEASE=1.4.3
|
||||||
|
|
||||||
|
git clone https://code.videolan.org/videolan/dav1d.git --branch $RELEASE
|
||||||
|
cd dav1d
|
||||||
|
meson build -D prefix=/usr/local
|
||||||
|
ninja -C build
|
||||||
|
ninja -C build install
|
||||||
|
cd ..
|
||||||
|
rm -rf dav1d
|
|
@ -2,37 +2,41 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
pip3 install meson==1.0.0
|
DEFAULT_BRANCH="$GST_UPSTREAM_BRANCH"
|
||||||
|
|
||||||
|
pip3 install meson==1.5.1 --break-system-packages
|
||||||
|
|
||||||
# gstreamer-rs already has a 'gstreamer' directory so don't clone there
|
# gstreamer-rs already has a 'gstreamer' directory so don't clone there
|
||||||
pushd .
|
pushd .
|
||||||
cd ..
|
cd ..
|
||||||
git clone https://gitlab.freedesktop.org/gstreamer/gstreamer.git \
|
git clone https://gitlab.freedesktop.org/gstreamer/gstreamer.git \
|
||||||
--depth 1 \
|
--depth 1 \
|
||||||
--branch 1.22
|
--branch "$DEFAULT_BRANCH"
|
||||||
|
|
||||||
cd gstreamer
|
cd gstreamer
|
||||||
|
|
||||||
# plugins required by tests
|
# plugins required by tests
|
||||||
PLUGINS="-D gst-plugins-base:ogg=enabled \
|
PLUGINS=(
|
||||||
-D gst-plugins-base:vorbis=enabled \
|
-Dgst-plugins-base:ogg=enabled
|
||||||
-D gst-plugins-base:theora=enabled \
|
-Dgst-plugins-base:vorbis=enabled
|
||||||
-D gst-plugins-good:matroska=enabled \
|
-Dgst-plugins-base:theora=enabled
|
||||||
-D gst-plugins-good:vpx=enabled \
|
-Dgst-plugins-good:matroska=enabled
|
||||||
-D gst-plugins-bad:opus=enabled \
|
-Dgst-plugins-good:vpx=enabled
|
||||||
-D gst-plugins-ugly:x264=enabled"
|
-Dgst-plugins-bad:opus=enabled
|
||||||
|
-Dgst-plugins-ugly:x264=enabled
|
||||||
|
)
|
||||||
|
|
||||||
meson setup build \
|
meson setup build \
|
||||||
-D prefix=/usr/local \
|
-Dprefix=/usr/local \
|
||||||
-D gpl=enabled \
|
-Dgpl=enabled \
|
||||||
-D ugly=enabled \
|
-Dugly=enabled \
|
||||||
-D examples=disabled \
|
-Dexamples=disabled \
|
||||||
-D gtk_doc=disabled \
|
-Dgtk_doc=disabled \
|
||||||
-D introspection=disabled \
|
-Dintrospection=disabled \
|
||||||
-D libav=disabled \
|
-Dlibav=disabled \
|
||||||
-D python=disabled \
|
-Dpython=disabled \
|
||||||
-D vaapi=disabled \
|
-Dvaapi=disabled \
|
||||||
$PLUGINS
|
"${PLUGINS[@]}" "$@"
|
||||||
meson compile -C build
|
meson compile -C build
|
||||||
meson install -C build
|
meson install -C build
|
||||||
ldconfig
|
ldconfig
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
#! /bin/sh
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
BRANCH=4.8.3
|
|
||||||
|
|
||||||
git clone https://gitlab.gnome.org/GNOME/gtk.git --branch $BRANCH --depth=1
|
|
||||||
cd gtk
|
|
||||||
|
|
||||||
meson setup build \
|
|
||||||
-D prefix=/usr/local \
|
|
||||||
-Dbuild-tests=false \
|
|
||||||
-Dwayland-protocols:tests=false
|
|
||||||
meson compile -C build
|
|
||||||
meson install -C build
|
|
||||||
ldconfig
|
|
||||||
cd ..
|
|
||||||
rm -rf gtk/
|
|
|
@ -5,7 +5,7 @@ source ./ci/env.sh
|
||||||
set -e
|
set -e
|
||||||
export CARGO_HOME='/usr/local/cargo'
|
export CARGO_HOME='/usr/local/cargo'
|
||||||
|
|
||||||
RUSTUP_VERSION=1.25.1
|
RUSTUP_VERSION=1.27.1
|
||||||
RUST_VERSION=$1
|
RUST_VERSION=$1
|
||||||
RUST_IMAGE_FULL=$2
|
RUST_IMAGE_FULL=$2
|
||||||
RUST_ARCH="x86_64-unknown-linux-gnu"
|
RUST_ARCH="x86_64-unknown-linux-gnu"
|
||||||
|
@ -26,17 +26,34 @@ if [ "$RUST_IMAGE_FULL" = "1" ]; then
|
||||||
rustup component add clippy-preview
|
rustup component add clippy-preview
|
||||||
rustup component add rustfmt
|
rustup component add rustfmt
|
||||||
|
|
||||||
cargo install --force cargo-deny
|
cargo install --locked cargo-deny
|
||||||
cargo install --force cargo-outdated
|
if [ "$RUST_VERSION" = "1.71.1" ]; then
|
||||||
|
cargo install --locked cargo-outdated
|
||||||
|
else
|
||||||
|
# Don't use --locked because time-0.3.30 does not build with 1.80 or newer
|
||||||
|
cargo install cargo-outdated
|
||||||
|
fi
|
||||||
|
cargo install --locked typos-cli --version "1.19.0"
|
||||||
|
|
||||||
# Coverage tools
|
# Coverage tools
|
||||||
rustup component add llvm-tools-preview
|
rustup component add llvm-tools-preview
|
||||||
cargo install --force grcov
|
if [ "$RUST_VERSION" = "1.71.1" ]; then
|
||||||
|
cargo install --locked grcov
|
||||||
|
else
|
||||||
|
# Don't use --locked because time-0.3.30 does not build with 1.80 or newer
|
||||||
|
cargo install grcov
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RUST_VERSION" = "1.71.1" ]; then
|
||||||
|
cargo install --locked cargo-c --version 0.9.26+cargo-0.74
|
||||||
|
else
|
||||||
|
cargo install --locked cargo-c --version 0.10.3+cargo-0.81
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$RUST_VERSION" = "nightly" ]; then
|
if [ "$RUST_VERSION" = "nightly" ]; then
|
||||||
rustup component add rustfmt --toolchain nightly
|
rustup component add rustfmt --toolchain nightly
|
||||||
|
|
||||||
# Documentation tools
|
# Documentation tools
|
||||||
cargo install --force rustdoc-stripper
|
cargo install --locked rustdoc-stripper
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -5,43 +5,29 @@ set -ex
|
||||||
rustc --version
|
rustc --version
|
||||||
cargo --version
|
cargo --version
|
||||||
|
|
||||||
# First build and test all the crates with their relevant features
|
cpus=$(nproc || sysctl -n hw.ncpu)
|
||||||
# Keep features in sync with the list below below
|
CARGO_FLAGS="-j${FDO_CI_CONCURRENT:-$cpus}"
|
||||||
get_features() {
|
|
||||||
crate=$1
|
|
||||||
case "$crate" in
|
|
||||||
gstreamer-audio|gstreamer-editing-services|gstreamer-gl|gstreamer-pbutils|gstreamer-rtp|gstreamer-rtsp|gstreamer-video|gstreamer)
|
|
||||||
echo "--features=serde,v1_22"
|
|
||||||
;;
|
|
||||||
gstreamer-validate)
|
|
||||||
echo ""
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "--features=v1_22"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
for crate in gstreamer* gstreamer-gl/{egl,wayland,x11}; do
|
for crate in gstreamer* gstreamer-gl/{egl,wayland,x11}; do
|
||||||
if [ -e "$crate/Cargo.toml" ]; then
|
if [ -e "$crate/Cargo.toml" ]; then
|
||||||
if [ -n "$ALL_FEATURES" ]; then
|
if [ -n "$ALL_FEATURES" ]; then
|
||||||
FEATURES=$(get_features "$crate")
|
FEATURES="--all-features"
|
||||||
else
|
else
|
||||||
FEATURES=""
|
FEATURES=""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Building and testing $crate with $FEATURES"
|
echo "Building and testing $crate with $FEATURES"
|
||||||
|
|
||||||
cargo build --locked --color=always --manifest-path "$crate/Cargo.toml" $FEATURES
|
cargo build $CARGO_FLAGS --locked --color=always --manifest-path "$crate/Cargo.toml" $FEATURES
|
||||||
G_DEBUG=fatal_warnings cargo test --color=always --manifest-path "$crate/Cargo.toml" $FEATURES
|
RUST_BACKTRACE=1 G_DEBUG=fatal_warnings cargo test $CARGO_FLAGS --color=always --manifest-path "$crate/Cargo.toml" $FEATURES
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
if [ -n "$EXAMPLES_TUTORIALS" ]; then
|
if [ -n "$EXAMPLES_TUTORIALS" ]; then
|
||||||
# Keep in sync with examples/Cargo.toml
|
# Keep in sync with examples/Cargo.toml
|
||||||
# List all features except windows/win32
|
# List all features except windows/win32
|
||||||
EXAMPLES_FEATURES="--features=gtksink,gtkvideooverlay,gtkvideooverlay-x11,gtkvideooverlay-quartz,rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-wayland,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18"
|
EXAMPLES_FEATURES="--features=rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18"
|
||||||
|
|
||||||
cargo build --locked --color=always --manifest-path examples/Cargo.toml --bins --examples "$EXAMPLES_FEATURES"
|
cargo build $CARGO_FLAGS --locked --color=always --manifest-path examples/Cargo.toml --bins --examples "$EXAMPLES_FEATURES"
|
||||||
cargo build --locked --color=always --manifest-path tutorials/Cargo.toml --bins --examples --all-features
|
cargo build $CARGO_FLAGS --locked --color=always --manifest-path tutorials/Cargo.toml --bins --examples --all-features
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -11,13 +11,10 @@ get_features() {
|
||||||
crate=$1
|
crate=$1
|
||||||
case "$crate" in
|
case "$crate" in
|
||||||
gstreamer-audio|gstreamer-editing-services|gstreamer-gl|gstreamer-pbutils|gstreamer-rtp|gstreamer-rtsp|gstreamer-video|gstreamer)
|
gstreamer-audio|gstreamer-editing-services|gstreamer-gl|gstreamer-pbutils|gstreamer-rtp|gstreamer-rtsp|gstreamer-video|gstreamer)
|
||||||
echo "--features=serde,v1_22"
|
echo "--features=serde,v1_26"
|
||||||
;;
|
|
||||||
gstreamer-validate)
|
|
||||||
echo ""
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "--features=v1_22"
|
echo "--features=v1_26"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
@ -34,7 +31,7 @@ done
|
||||||
|
|
||||||
# Keep in sync with examples/Cargo.toml
|
# Keep in sync with examples/Cargo.toml
|
||||||
# List all features except windows/win32
|
# List all features except windows/win32
|
||||||
EXAMPLES_FEATURES="--features=gtksink,gtkvideooverlay,gtkvideooverlay-x11,gtkvideooverlay-quartz,rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-wayland,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18"
|
EXAMPLES_FEATURES="--features=rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18"
|
||||||
|
|
||||||
# And also run over all the examples/tutorials
|
# And also run over all the examples/tutorials
|
||||||
cargo clippy --locked --color=always --manifest-path examples/Cargo.toml --all-targets "$EXAMPLES_FEATURES" -- $CLIPPY_LINTS
|
cargo clippy --locked --color=always --manifest-path examples/Cargo.toml --all-targets "$EXAMPLES_FEATURES" -- $CLIPPY_LINTS
|
||||||
|
|
|
@ -5,35 +5,25 @@ set -ex
|
||||||
rustc --version
|
rustc --version
|
||||||
cargo --version
|
cargo --version
|
||||||
|
|
||||||
get_features() {
|
cpus=$(nproc || sysctl -n hw.ncpu)
|
||||||
module=${1%%/sys}
|
CARGO_FLAGS="-j${FDO_CI_CONCURRENT:-$cpus}"
|
||||||
case "$module" in
|
|
||||||
gstreamer-validate)
|
|
||||||
echo ""
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "--features=v1_22"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# First build and test all the crates with their relevant features
|
|
||||||
# Keep features in sync with below
|
|
||||||
for crate in gstreamer*/sys gstreamer-gl/*/sys; do
|
for crate in gstreamer*/sys gstreamer-gl/*/sys; do
|
||||||
if [ -e "$crate/Cargo.toml" ]; then
|
if [ -e "$crate/Cargo.toml" ]; then
|
||||||
echo "Building $crate with $(get_features "$crate")"
|
echo "Building $crate with --all-features"
|
||||||
cargo build --locked --color=always --manifest-path "$crate/Cargo.toml" $(get_features "$crate")
|
cargo build $CARGO_FLAGS --locked --color=always --manifest-path "$crate/Cargo.toml" --all-features
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# Run tests for crates we can currently run.
|
|
||||||
# Other tests are broken currently.
|
|
||||||
for crate in gstreamer/sys \
|
for crate in gstreamer/sys \
|
||||||
|
gstreamer-allocators/sys \
|
||||||
|
gstreamer-analytics/sys \
|
||||||
gstreamer-app/sys \
|
gstreamer-app/sys \
|
||||||
gstreamer-audio/sys \
|
gstreamer-audio/sys \
|
||||||
gstreamer-base/sys \
|
gstreamer-base/sys \
|
||||||
gstreamer-check/sys \
|
gstreamer-check/sys \
|
||||||
gstreamer-controller/sys \
|
gstreamer-controller/sys \
|
||||||
|
gstreamer-editing-services/sys \
|
||||||
gstreamer-gl/sys \
|
gstreamer-gl/sys \
|
||||||
gstreamer-gl/egl/sys \
|
gstreamer-gl/egl/sys \
|
||||||
gstreamer-gl/wayland/sys \
|
gstreamer-gl/wayland/sys \
|
||||||
|
@ -41,13 +31,16 @@ for crate in gstreamer/sys \
|
||||||
gstreamer-mpegts/sys \
|
gstreamer-mpegts/sys \
|
||||||
gstreamer-net/sys \
|
gstreamer-net/sys \
|
||||||
gstreamer-pbutils/sys \
|
gstreamer-pbutils/sys \
|
||||||
|
gstreamer-play/sys \
|
||||||
gstreamer-player/sys \
|
gstreamer-player/sys \
|
||||||
|
gstreamer-rtp/sys \
|
||||||
gstreamer-rtsp-server/sys \
|
gstreamer-rtsp-server/sys \
|
||||||
gstreamer-rtsp/sys \
|
gstreamer-rtsp/sys \
|
||||||
gstreamer-sdp/sys \
|
gstreamer-sdp/sys \
|
||||||
gstreamer-tag/sys \
|
gstreamer-tag/sys \
|
||||||
|
gstreamer-validate/sys \
|
||||||
gstreamer-video/sys \
|
gstreamer-video/sys \
|
||||||
gstreamer-webrtc/sys; do
|
gstreamer-webrtc/sys; do
|
||||||
echo "Testing $crate with $(get_features $crate)"
|
echo "Testing $crate with --all-features)"
|
||||||
cargo test --locked --color=always --manifest-path $crate/Cargo.toml "$(get_features $crate)"
|
RUST_BACKTRACE=1 cargo test $CARGO_FLAGS --locked --color=always --manifest-path $crate/Cargo.toml --all-features
|
||||||
done
|
done
|
||||||
|
|
|
@ -16,8 +16,7 @@
|
||||||
# 'gstreamer-gl/egl',
|
# 'gstreamer-gl/egl',
|
||||||
# 'gstreamer-gl/wayland',
|
# 'gstreamer-gl/wayland',
|
||||||
# 'gstreamer-gl/x11',
|
# 'gstreamer-gl/x11',
|
||||||
# only has sys
|
'gstreamer-mpegts',
|
||||||
# 'gstreamer-mpegts',
|
|
||||||
'gstreamer-mpegts/sys',
|
'gstreamer-mpegts/sys',
|
||||||
'gstreamer-net',
|
'gstreamer-net',
|
||||||
'gstreamer-pbutils',
|
'gstreamer-pbutils',
|
||||||
|
@ -26,8 +25,7 @@
|
||||||
'gstreamer-rtsp',
|
'gstreamer-rtsp',
|
||||||
'gstreamer-rtsp-server',
|
'gstreamer-rtsp-server',
|
||||||
'gstreamer-sdp',
|
'gstreamer-sdp',
|
||||||
# only has sys
|
'gstreamer-tag',
|
||||||
# 'gstreamer-tag',
|
|
||||||
'gstreamer-tag/sys',
|
'gstreamer-tag/sys',
|
||||||
'gstreamer-video',
|
'gstreamer-video',
|
||||||
'gstreamer-webrtc',
|
'gstreamer-webrtc',
|
||||||
|
@ -38,10 +36,10 @@
|
||||||
# "" is the default build, no flags appended
|
# "" is the default build, no flags appended
|
||||||
[string[]] $features_matrix = @(
|
[string[]] $features_matrix = @(
|
||||||
# "--no-default-features",
|
# "--no-default-features",
|
||||||
# "--features=v1_18,",
|
# "--features=v1_18",
|
||||||
# "--features=v1_20,",
|
# "--features=v1_20",
|
||||||
"",
|
"",
|
||||||
"--features=v1_22,"
|
"--all-features"
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach($features in $features_matrix) {
|
foreach($features in $features_matrix) {
|
||||||
|
@ -54,13 +52,9 @@ foreach($features in $features_matrix) {
|
||||||
# Don't append feature flags if the string is null/empty
|
# Don't append feature flags if the string is null/empty
|
||||||
# Or when we want to build without default features
|
# Or when we want to build without default features
|
||||||
if ($env:LocalFeatures -and ($env:LocalFeatures -ne '--no-default-features')) {
|
if ($env:LocalFeatures -and ($env:LocalFeatures -ne '--no-default-features')) {
|
||||||
if ($crate -eq 'gstreamer') {
|
|
||||||
$env:LocalFeatures += "serde,"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($crate -eq 'examples') {
|
if ($crate -eq 'examples') {
|
||||||
# FIXME: We can do --all-features for examples once we have gtk3 installed in the image
|
# FIXME: We can do --all-features for examples once we have gtk3 installed in the image
|
||||||
$env:LocalFeatures = "--features=rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18,windows"
|
$env:LocalFeatures = "--features=rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18,windows,gl"
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($crate -eq 'tutorials') {
|
if ($crate -eq 'tutorials') {
|
||||||
|
@ -82,6 +76,7 @@ foreach($features in $features_matrix) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$env:G_DEBUG="fatal_warnings"
|
$env:G_DEBUG="fatal_warnings"
|
||||||
|
$env:RUST_BACKTRACE="1"
|
||||||
cargo test --no-fail-fast --color=always --manifest-path $crate/Cargo.toml $env:LocalFeatures
|
cargo test --no-fail-fast --color=always --manifest-path $crate/Cargo.toml $env:LocalFeatures
|
||||||
|
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
# escape=`
|
# escape=`
|
||||||
|
|
||||||
FROM "registry.freedesktop.org/gstreamer/gstreamer/amd64/windows:2022-12-10.0-main"
|
FROM "registry.freedesktop.org/gstreamer/gstreamer/amd64/windows:2023-07-17.0-main"
|
||||||
|
|
||||||
# Make sure any failure in PowerShell is fatal
|
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||||
ENV ErrorActionPreference='Stop'
|
|
||||||
SHELL ["powershell","-NoLogo", "-NonInteractive", "-Command"]
|
|
||||||
|
|
||||||
ARG DEFAULT_BRANCH="1.22"
|
# These arguments are always required to be specified with --build-arg
|
||||||
|
# when building the image.
|
||||||
|
# See DOCKER_BUILD_ARGS in .gitlab-ci.yml for an example
|
||||||
|
ARG DEFAULT_BRANCH="invalid"
|
||||||
ARG RUST_VERSION="invalid"
|
ARG RUST_VERSION="invalid"
|
||||||
|
|
||||||
RUN choco install -y pkgconfiglite nasm llvm
|
RUN choco install -y pkgconfiglite nasm llvm openssl
|
||||||
|
|
||||||
# https://stackoverflow.com/a/50716450
|
# https://stackoverflow.com/a/50716450
|
||||||
RUN setx PATH '%PATH%;C:\Program Files\NASM;C:\gst-install\bin'
|
RUN setx PATH '%PATH%;C:\Program Files\NASM;C:\gst-install\bin'
|
||||||
ENV PKG_CONFIG_PATH="C:\gst-install\lib\pkgconfig"
|
ENV PKG_CONFIG_PATH="C:\gst-install\lib\pkgconfig"
|
||||||
|
|
||||||
COPY install_pango.ps1 install_gst.ps1 install_gtk.ps1 install_dav1d.ps1 C:\
|
COPY install_gst.ps1 install_dav1d.ps1 install_rust.ps1 install_cargo_utils.ps1 C:\
|
||||||
RUN C:\install_pango.ps1
|
|
||||||
RUN C:\install_gst.ps1
|
RUN C:\install_gst.ps1
|
||||||
RUN C:\install_gtk.ps1
|
|
||||||
RUN C:\install_dav1d.ps1
|
RUN C:\install_dav1d.ps1
|
||||||
|
RUN C:\install_rust.ps1
|
||||||
RUN Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile C:\rustup-init.exe
|
RUN C:\install_cargo_utils.ps1
|
||||||
RUN C:\rustup-init.exe -y --profile minimal --default-toolchain $env:RUST_VERSION
|
|
||||||
|
|
18
ci/windows-docker/install_cargo_utils.ps1
Normal file
18
ci/windows-docker/install_cargo_utils.ps1
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
||||||
|
|
||||||
|
rustup --version
|
||||||
|
rustc --version
|
||||||
|
cargo --version
|
||||||
|
|
||||||
|
if ("$env:RUST_VERSION" -eq "1.71.1") {
|
||||||
|
cargo install --locked cargo-c --version 0.9.26+cargo-0.74
|
||||||
|
} else {
|
||||||
|
cargo install --locked cargo-c --version 0.10.3+cargo-0.81
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$?) {
|
||||||
|
Write-Host "Failed to install cargo-c"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cargo-cbuild --version
|
|
@ -1,7 +1,7 @@
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
||||||
|
|
||||||
# Download gstreamer and all its subprojects
|
# Download gstreamer and all its subprojects
|
||||||
git clone -b 1.0.0 --depth 1 https://code.videolan.org/videolan/dav1d.git C:\dav1d
|
git clone -b 1.4.3 --depth 1 https://code.videolan.org/videolan/dav1d.git C:\dav1d
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Failed to clone dav1d"
|
Write-Host "Failed to clone dav1d"
|
||||||
Exit 1
|
Exit 1
|
||||||
|
|
|
@ -12,52 +12,58 @@ Set-Location C:\gstreamer
|
||||||
# Copy the cache we already have in the image to avoid massive redownloads
|
# Copy the cache we already have in the image to avoid massive redownloads
|
||||||
Move-Item C:/subprojects/* C:\gstreamer\subprojects
|
Move-Item C:/subprojects/* C:\gstreamer\subprojects
|
||||||
|
|
||||||
if (!$?) {
|
|
||||||
Write-Host "Failed to copy subprojects cache"
|
|
||||||
Exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Update the subprojects cache
|
# Update the subprojects cache
|
||||||
Write-Output "Running meson subproject reset"
|
Write-Output "Running meson subproject reset"
|
||||||
meson subprojects update --reset
|
meson subprojects update --reset
|
||||||
|
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Failed to reset subprojects state"
|
Write-Host "Failed to update gstreamer subprojects"
|
||||||
Exit 1
|
Exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
$env:MESON_ARGS = "--prefix=C:\gst-install\ " +
|
$MESON_ARGS = @(`
|
||||||
"-Dglib:installed_tests=false " +
|
"--prefix=C:\gst-install", `
|
||||||
"-Dlibnice:tests=disabled " +
|
"-Dglib:installed_tests=false", `
|
||||||
"-Dlibnice:examples=disabled " +
|
"-Dlibnice:tests=disabled", `
|
||||||
"-Dffmpeg:tests=disabled " +
|
"-Dlibnice:examples=disabled", `
|
||||||
"-Dopenh264:tests=disabled " +
|
"-Dffmpeg:tests=disabled", `
|
||||||
"-Dpygobject:tests=false " +
|
"-Dopenh264:tests=disabled", `
|
||||||
"-Dgpl=enabled " +
|
"-Dpygobject:tests=false", `
|
||||||
"-Dugly=enabled " +
|
"-Dgpl=enabled", `
|
||||||
"-Dbad=enabled " +
|
"-Dugly=enabled", `
|
||||||
"-Dges=enabled " +
|
"-Dbad=enabled", `
|
||||||
"-Drtsp_server=enabled " +
|
"-Dges=enabled", `
|
||||||
"-Ddevtools=enabled " +
|
"-Drtsp_server=enabled", `
|
||||||
"-Dsharp=disabled " +
|
"-Ddevtools=enabled", `
|
||||||
"-Dpython=disabled " +
|
"-Dsharp=disabled", `
|
||||||
"-Dlibav=disabled " +
|
"-Dpython=disabled", `
|
||||||
"-Dvaapi=disabled " +
|
"-Dlibav=disabled", `
|
||||||
"-Dgst-plugins-base:pango=enabled " +
|
"-Dvaapi=disabled", `
|
||||||
"-Dgst-plugins-good:cairo=enabled " +
|
"-Dgtk=enabled", `
|
||||||
"-Dgst-plugins-good:lame=disabled "
|
"-Dgst-plugins-base:pango=enabled", `
|
||||||
|
"-Dgst-plugins-good:cairo=enabled", `
|
||||||
Write-Output "Building gst"
|
"-Dgst-plugins-good:lame=disabled"
|
||||||
cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=amd64 && meson setup _build $env:MESON_ARGS && meson compile -C _build && meson install -C _build"
|
)
|
||||||
|
|
||||||
|
Write-Output "Building gstreamer"
|
||||||
|
meson setup --vsenv $MESON_ARGS _build
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Failed to build and install gst"
|
type "_build\meson-logs\meson-log.txt"
|
||||||
|
Write-Host "Failed to run meson setup, see log above"
|
||||||
Exit 1
|
Exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
cd C:\
|
Write-Output "Compiling gstreamer"
|
||||||
cmd /c rmdir /s /q C:\gstreamer
|
meson compile -C _build
|
||||||
if (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Failed to remove gst checkout"
|
Write-Host "Failed to run meson compile"
|
||||||
Exit 1
|
Exit 1
|
||||||
}
|
}
|
||||||
|
# meson install does a spurious rebuild sometimes that then fails
|
||||||
|
meson install --no-rebuild -C _build
|
||||||
|
if (!$?) {
|
||||||
|
Write-Host "Failed to run meson install"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cd c:\
|
||||||
|
Remove-Item -LiteralPath "C:\gstreamer" -Force -Recurse
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
|
||||||
|
|
||||||
$env:MESON_ARGS = "--prefix=C:\gst-install\"
|
|
||||||
|
|
||||||
# Download gtk and all its subprojects
|
|
||||||
git clone -b 4.8.3 --depth 1 https://gitlab.gnome.org/gnome/gtk.git C:\gtk
|
|
||||||
if (!$?) {
|
|
||||||
Write-Host "Failed to clone gtk"
|
|
||||||
Exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location C:\gtk
|
|
||||||
|
|
||||||
Write-Output "Building gtk"
|
|
||||||
cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=amd64 && meson _build $env:MESON_ARGS && meson compile -C _build && ninja -C _build install"
|
|
||||||
|
|
||||||
if (!$?) {
|
|
||||||
Write-Host "Failed to build and install gtk"
|
|
||||||
Exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cd C:\
|
|
||||||
cmd /c rmdir /s /q C:\gtk
|
|
||||||
if (!$?) {
|
|
||||||
Write-Host "Failed to remove gtk checkout"
|
|
||||||
Exit 1
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
|
||||||
|
|
||||||
$env:MESON_ARGS = "--prefix=C:\gst-install\ -Dharfbuzz:freetype=enabled -Dfreetype:harfbuzz=disabled"
|
|
||||||
|
|
||||||
# Download pango all its subprojects
|
|
||||||
git clone -b main --depth 1 https://gitlab.gnome.org/gnome/pango.git C:\pango
|
|
||||||
if (!$?) {
|
|
||||||
Write-Host "Failed to clone pango"
|
|
||||||
Exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location C:\pango
|
|
||||||
|
|
||||||
Write-Output "Building pango"
|
|
||||||
cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=amd64 && meson _build $env:MESON_ARGS && meson compile -C _build && ninja -C _build install"
|
|
||||||
|
|
||||||
if (!$?) {
|
|
||||||
Write-Host "Failed to build and install pango"
|
|
||||||
Exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
cd C:\
|
|
||||||
cmd /c rmdir /s /q C:\pango
|
|
||||||
if (!$?) {
|
|
||||||
Write-Host "Failed to remove gtk checkout"
|
|
||||||
Exit 1
|
|
||||||
}
|
|
17
ci/windows-docker/install_rust.ps1
Normal file
17
ci/windows-docker/install_rust.ps1
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
||||||
|
|
||||||
|
$rustup_url = 'https://win.rustup.rs/x86_64'
|
||||||
|
|
||||||
|
Invoke-WebRequest -Uri $rustup_url -OutFile C:\rustup-init.exe
|
||||||
|
|
||||||
|
if (!$?) {
|
||||||
|
Write-Host "Failed to download rustup"
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
C:\rustup-init.exe -y --profile minimal --default-toolchain $env:RUST_VERSION
|
||||||
|
|
||||||
|
if (!$?) {
|
||||||
|
Write-Host "Failed to install rust"
|
||||||
|
Exit 1
|
||||||
|
}
|
30
deny.toml
30
deny.toml
|
@ -1,26 +1,24 @@
|
||||||
|
[graph]
|
||||||
|
exclude = [
|
||||||
|
"examples",
|
||||||
|
"tutorials",
|
||||||
|
]
|
||||||
|
|
||||||
[advisories]
|
[advisories]
|
||||||
|
version = 2
|
||||||
db-path = "~/.cargo/advisory-db"
|
db-path = "~/.cargo/advisory-db"
|
||||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||||
vulnerability = "deny"
|
|
||||||
unmaintained = "warn"
|
|
||||||
notice = "warn"
|
|
||||||
ignore = []
|
ignore = []
|
||||||
|
|
||||||
[licenses]
|
[licenses]
|
||||||
unlicensed = "deny"
|
version = 2
|
||||||
allow = [
|
|
||||||
"Apache-2.0",
|
|
||||||
]
|
|
||||||
deny = [
|
|
||||||
"GPL-1.0",
|
|
||||||
"GPL-2.0",
|
|
||||||
"GPL-3.0",
|
|
||||||
"AGPL-1.0",
|
|
||||||
"AGPL-3.0",
|
|
||||||
]
|
|
||||||
copyleft = "deny"
|
|
||||||
allow-osi-fsf-free = "either"
|
|
||||||
confidence-threshold = 0.8
|
confidence-threshold = 0.8
|
||||||
|
allow = [
|
||||||
|
"MIT",
|
||||||
|
"Apache-2.0",
|
||||||
|
"Apache-2.0 WITH LLVM-exception",
|
||||||
|
"Unicode-DFS-2016",
|
||||||
|
]
|
||||||
|
|
||||||
[bans]
|
[bans]
|
||||||
multiple-versions = "deny"
|
multiple-versions = "deny"
|
||||||
|
|
|
@ -1,74 +1,79 @@
|
||||||
[package]
|
[package]
|
||||||
name = "examples"
|
name = "examples"
|
||||||
version = "0.20.0"
|
version.workspace = true
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
edition = "2021"
|
edition.workspace = true
|
||||||
rust-version = "1.64"
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
glib.workspace = true
|
||||||
gst = { package = "gstreamer", path = "../gstreamer" }
|
gst.workspace = true
|
||||||
gst-gl = { package = "gstreamer-gl", path = "../gstreamer-gl", optional = true }
|
gst-gl = { workspace = true, optional = true }
|
||||||
gst-gl-egl = { package = "gstreamer-gl-egl", path = "../gstreamer-gl/egl", optional = true }
|
gst-gl-egl = { workspace = true, optional = true }
|
||||||
gst-gl-wayland = { package = "gstreamer-gl-wayland", path = "../gstreamer-gl/wayland", optional = true }
|
gst-gl-x11 = { workspace = true, optional = true }
|
||||||
gst-gl-x11 = { package = "gstreamer-gl-x11", path = "../gstreamer-gl/x11", optional = true }
|
gst-app.workspace = true
|
||||||
gst-app = { package = "gstreamer-app", path = "../gstreamer-app" }
|
gst-audio.workspace = true
|
||||||
gst-audio = { package = "gstreamer-audio", path = "../gstreamer-audio" }
|
gst-base.workspace = true
|
||||||
gst-base = { package = "gstreamer-base", path = "../gstreamer-base" }
|
gst-video.workspace = true
|
||||||
gst-video = { package = "gstreamer-video", path = "../gstreamer-video" }
|
gst-pbutils.workspace = true
|
||||||
gst-pbutils = { package = "gstreamer-pbutils", path = "../gstreamer-pbutils" }
|
gst-play = { workspace = true, optional = true }
|
||||||
gst-play = { package = "gstreamer-play", path = "../gstreamer-play", optional = true }
|
gst-player = { workspace = true, optional = true }
|
||||||
gst-player = { package = "gstreamer-player", path = "../gstreamer-player", optional = true }
|
ges = { workspace = true, optional = true }
|
||||||
ges = { package = "gstreamer-editing-services", path = "../gstreamer-editing-services", optional = true }
|
gst-sdp = { workspace = true, optional = true }
|
||||||
gst-sdp = { package = "gstreamer-sdp", path = "../gstreamer-sdp", optional = true }
|
gst-rtsp = { workspace = true, optional = true }
|
||||||
gst-rtsp = { package = "gstreamer-rtsp", path = "../gstreamer-rtsp", optional = true }
|
gst-rtsp-server = { workspace = true, optional = true }
|
||||||
gst-rtsp-server = { package = "gstreamer-rtsp-server", path = "../gstreamer-rtsp-server", optional = true }
|
gst-allocators = { workspace = true, optional = true }
|
||||||
gst-allocators = { package = "gstreamer-allocators", path = "../gstreamer-allocators", optional = true }
|
gio = { workspace = true, optional = true }
|
||||||
gtk = { git = "https://github.com/gtk-rs/gtk3-rs", optional = true }
|
|
||||||
gdk = { git = "https://github.com/gtk-rs/gtk3-rs", optional = true }
|
|
||||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
byte-slice-cast = "1"
|
||||||
|
cairo-rs = { workspace = true, features=["use_glib"], optional = true }
|
||||||
derive_more = "0.99.5"
|
derive_more = "0.99.5"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
byte-slice-cast = "1"
|
# Since there's nothing Windows-specific to enable on gstreamer-rs, unconditionally enable glutin's WGL backend
|
||||||
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", features=["use_glib"], optional = true }
|
glutin = { version = "0.31", optional = true, default-features = false, features = ["wgl"] }
|
||||||
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
glutin-winit = { version = "0.4", optional = true, default-features = false, features = ["wgl"] }
|
||||||
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
image = { version = "0.24", optional = true, default-features = false, features = ["png", "jpeg"] }
|
||||||
glutin = { version = "0.29", optional = true }
|
|
||||||
once_cell = "1.0"
|
|
||||||
image = { version = "0.24", optional = true }
|
|
||||||
memmap2 = { version = "0.5", optional = true }
|
|
||||||
memfd = { version = "0.6", optional = true }
|
memfd = { version = "0.6", optional = true }
|
||||||
uds = { version = "0.2", optional = true }
|
memmap2 = { version = "0.9", optional = true }
|
||||||
|
pango = { workspace = true, optional = true }
|
||||||
|
pangocairo = { workspace = true, optional = true }
|
||||||
|
raw-window-handle = { version = "0.5", optional = true }
|
||||||
|
uds = { version = "0.4", optional = true }
|
||||||
|
winit = { version = "0.29", optional = true, default-features = false, features = ["rwh_05"] }
|
||||||
|
atomic_refcell = "0.1"
|
||||||
|
data-encoding = "2.0"
|
||||||
|
once_cell = "1"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows = { version = "0.44", features=["Win32_Graphics_Direct3D11",
|
windows = { version = "0.58", features=["Win32_Graphics_Direct3D11",
|
||||||
"Win32_Foundation", "Win32_Graphics_Direct3D", "Win32_Graphics_Dxgi",
|
"Win32_Foundation", "Win32_Graphics_Direct3D", "Win32_Graphics_Dxgi",
|
||||||
"Win32_Graphics_Dxgi_Common", "Win32_Graphics_Direct2D",
|
"Win32_Graphics_Dxgi_Common", "Win32_Graphics_Direct2D",
|
||||||
"Win32_Graphics_Direct2D_Common", "Win32_Graphics_DirectWrite",
|
"Win32_Graphics_Direct2D_Common", "Win32_Graphics_DirectWrite",
|
||||||
"Foundation_Numerics"], optional = true }
|
"Win32_Graphics_Imaging", "Win32_System_Com", "Foundation_Numerics"], optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
cocoa = "0.24"
|
cocoa = "0.26"
|
||||||
|
objc = "0.2.7"
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.build-dependencies]
|
||||||
|
system-deps = "7"
|
||||||
|
|
||||||
|
[package.metadata.system-deps]
|
||||||
|
"gstreamer-1.0" = "1.14"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gl_generator = { version = "0.14", optional = true }
|
gl_generator = { version = "0.14", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
gtksink = ["gtk", "gio"]
|
|
||||||
gtkvideooverlay = ["gtk", "gdk", "gio"]
|
|
||||||
gtkvideooverlay-x11 = ["gtkvideooverlay"]
|
|
||||||
gtkvideooverlay-quartz = ["gtkvideooverlay"]
|
|
||||||
rtsp-server = ["gst-rtsp-server", "gst-rtsp", "gst-sdp"]
|
rtsp-server = ["gst-rtsp-server", "gst-rtsp", "gst-sdp"]
|
||||||
rtsp-server-record = ["gst-rtsp-server", "gst-rtsp", "gio"]
|
rtsp-server-record = ["gst-rtsp-server", "gst-rtsp", "gio"]
|
||||||
pango-cairo = ["pango", "pangocairo", "cairo-rs"]
|
pango-cairo = ["pango", "pangocairo", "cairo-rs"]
|
||||||
overlay-composition = ["pango", "pangocairo", "cairo-rs"]
|
overlay-composition = ["pango", "pangocairo", "cairo-rs"]
|
||||||
gl = ["gst-gl", "gl_generator", "glutin"]
|
gl = ["dep:gst-gl", "dep:gl_generator", "dep:glutin", "dep:glutin-winit", "dep:winit", "dep:raw-window-handle"]
|
||||||
gst-gl-x11 = ["dep:gst-gl-x11"]
|
gst-gl-x11 = ["dep:gst-gl-x11", "glutin-winit?/glx"] # glx turns on x11
|
||||||
gst-gl-egl = ["dep:gst-gl-egl"]
|
gst-gl-egl = ["dep:gst-gl-egl", "glutin-winit?/egl", "glutin-winit?/x11", "glutin-winit?/wayland"] # Use X11 or Wayland via EGL
|
||||||
gst-gl-wayland = ["dep:gst-gl-wayland"]
|
|
||||||
allocators = ["gst-allocators", "memmap2", "memfd", "uds"]
|
allocators = ["gst-allocators", "memmap2", "memfd", "uds"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
@ -95,14 +100,6 @@ name = "encodebin"
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "events"
|
name = "events"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "gtksink"
|
|
||||||
required-features = ["gtksink"]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "gtkvideooverlay"
|
|
||||||
required-features = ["gtkvideooverlay"]
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "iterator"
|
name = "iterator"
|
||||||
|
|
||||||
|
@ -146,6 +143,10 @@ required-features = ["rtsp-server"]
|
||||||
name = "rtsp-server-subclass"
|
name = "rtsp-server-subclass"
|
||||||
required-features = ["rtsp-server"]
|
required-features = ["rtsp-server"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rtsp-server-custom-auth"
|
||||||
|
required-features = ["rtsp-server", "gst-rtsp-server/v1_22"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tagsetter"
|
name = "tagsetter"
|
||||||
|
|
||||||
|
@ -173,6 +174,10 @@ required-features = ["pango-cairo"]
|
||||||
name = "overlay-composition"
|
name = "overlay-composition"
|
||||||
required-features = ["overlay-composition"]
|
required-features = ["overlay-composition"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "overlay-composition-d2d"
|
||||||
|
required-features = ["windows"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "ges"
|
name = "ges"
|
||||||
required-features = ["ges"]
|
required-features = ["ges"]
|
||||||
|
@ -206,3 +211,10 @@ required-features = ["cairo-rs", "gst-video/v1_18"]
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "d3d11videosink"
|
name = "d3d11videosink"
|
||||||
required-features = ["windows"]
|
required-features = ["windows"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "audio_multichannel_interleave"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "zoom"
|
||||||
|
required-features = ["gst-video/v1_18"]
|
||||||
|
|
|
@ -19,4 +19,22 @@ fn generate_gl_bindings() {}
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
println!("cargo:rerun-if-changed=build.rs");
|
||||||
generate_gl_bindings();
|
generate_gl_bindings();
|
||||||
|
|
||||||
|
// https://github.com/rust-lang/cargo/issues/5077#issuecomment-1284482987
|
||||||
|
#[cfg(all(not(docsrs), target_os = "macos"))]
|
||||||
|
match system_deps::Config::new().probe() {
|
||||||
|
Ok(deps) => {
|
||||||
|
let usr = std::path::Path::new("/usr/lib");
|
||||||
|
let usr_local = std::path::Path::new("/usr/local/lib");
|
||||||
|
for dep in deps.all_link_paths() {
|
||||||
|
if dep != &usr && dep != &usr_local {
|
||||||
|
println!("cargo:rustc-link-arg=-Wl,-rpath,{:?}", dep.as_os_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(s) => {
|
||||||
|
println!("cargo:warning={s}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,6 @@
|
||||||
// This is the format we request:
|
// This is the format we request:
|
||||||
// Audio / Signed 16bit / 1 channel / arbitrary sample rate
|
// Audio / Signed 16bit / 1 channel / arbitrary sample rate
|
||||||
|
|
||||||
use std::i16;
|
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
|
@ -46,7 +44,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
pipeline.add_many(&[&src, appsink.upcast_ref()])?;
|
pipeline.add_many([&src, appsink.upcast_ref()])?;
|
||||||
src.link(&appsink)?;
|
src.link(&appsink)?;
|
||||||
|
|
||||||
// Getting data out of the appsink is done by setting callbacks on it.
|
// Getting data out of the appsink is done by setting callbacks on it.
|
||||||
|
@ -91,7 +89,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
element_error!(
|
element_error!(
|
||||||
appsink,
|
appsink,
|
||||||
gst::ResourceError::Failed,
|
gst::ResourceError::Failed,
|
||||||
("Failed to interprete buffer as S16 PCM")
|
("Failed to interpret buffer as S16 PCM")
|
||||||
);
|
);
|
||||||
|
|
||||||
gst::FlowError::Error
|
gst::FlowError::Error
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
use gst_video::prelude::*;
|
||||||
|
|
||||||
#[path = "../examples-common.rs"]
|
#[path = "../examples-common.rs"]
|
||||||
mod examples_common;
|
mod examples_common;
|
||||||
|
@ -49,8 +50,8 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
|
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
|
||||||
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[appsrc.upcast_ref(), &videoconvert, &sink])?;
|
pipeline.add_many([appsrc.upcast_ref(), &videoconvert, &sink])?;
|
||||||
gst::Element::link_many(&[appsrc.upcast_ref(), &videoconvert, &sink])?;
|
gst::Element::link_many([appsrc.upcast_ref(), &videoconvert, &sink])?;
|
||||||
|
|
||||||
// Our frame counter, that is stored in the mutable environment
|
// Our frame counter, that is stored in the mutable environment
|
||||||
// of the closure of the need-data callback
|
// of the closure of the need-data callback
|
||||||
|
|
153
examples/src/bin/audio_multichannel_interleave.rs
Normal file
153
examples/src/bin/audio_multichannel_interleave.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
// This example demonstrates how to mix multiple audio
|
||||||
|
// streams into a single output using the audiomixer element.
|
||||||
|
// In this case, we're mixing 4 stereo streams into a single 8 channel output.
|
||||||
|
|
||||||
|
use gst::prelude::*;
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
#[path = "../examples-common.rs"]
|
||||||
|
mod examples_common;
|
||||||
|
|
||||||
|
const TRACKS: i32 = 4;
|
||||||
|
|
||||||
|
fn create_source_and_link(pipeline: &gst::Pipeline, mixer: &gst::Element, track_number: i32) {
|
||||||
|
let freq = ((track_number + 1) * 1000) as f64;
|
||||||
|
let audiosrc = gst::ElementFactory::make("audiotestsrc")
|
||||||
|
.property("freq", freq)
|
||||||
|
.property("num-buffers", 2000)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let caps = gst_audio::AudioCapsBuilder::new().channels(2).build();
|
||||||
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
||||||
|
.property("caps", &caps)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
pipeline.add_many([&audiosrc, &capsfilter]).unwrap();
|
||||||
|
gst::Element::link_many([&audiosrc, &capsfilter]).unwrap();
|
||||||
|
|
||||||
|
let src_pad = capsfilter.static_pad("src").unwrap();
|
||||||
|
let mixer_pad = mixer.request_pad_simple("sink_%u").unwrap();
|
||||||
|
|
||||||
|
// audiomixer expects a mix-matrix set on each input pad,
|
||||||
|
// indicating which output channels our input should appear in.
|
||||||
|
// Rows => input channels, columns => output channels.
|
||||||
|
// Here each input channel will appear in exactly one output channel.
|
||||||
|
let mut mix_matrix: Vec<Vec<f32>> = vec![];
|
||||||
|
for i in 0..TRACKS {
|
||||||
|
if i == track_number {
|
||||||
|
mix_matrix.push(vec![1.0, 0.0]);
|
||||||
|
mix_matrix.push(vec![0.0, 1.0]);
|
||||||
|
} else {
|
||||||
|
mix_matrix.push(vec![0.0, 0.0]);
|
||||||
|
mix_matrix.push(vec![0.0, 0.0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut audiomixer_config = gst_audio::AudioConverterConfig::new();
|
||||||
|
audiomixer_config.set_mix_matrix(&mix_matrix);
|
||||||
|
mixer_pad.set_property("converter-config", audiomixer_config);
|
||||||
|
|
||||||
|
src_pad.link(&mixer_pad).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn example_main() {
|
||||||
|
gst::init().unwrap();
|
||||||
|
|
||||||
|
let args: Vec<_> = env::args().collect();
|
||||||
|
let output_file = if args.len() == 2 {
|
||||||
|
&args[1]
|
||||||
|
} else {
|
||||||
|
println!("Usage: audiomixer <output file>");
|
||||||
|
std::process::exit(-1);
|
||||||
|
};
|
||||||
|
|
||||||
|
let pipeline = gst::Pipeline::new();
|
||||||
|
let audiomixer = gst::ElementFactory::make("audiomixer").build().unwrap();
|
||||||
|
|
||||||
|
// Using an arbitrary layout of 4 stereo pairs.
|
||||||
|
let positions = [
|
||||||
|
gst_audio::AudioChannelPosition::FrontLeft,
|
||||||
|
gst_audio::AudioChannelPosition::FrontRight,
|
||||||
|
gst_audio::AudioChannelPosition::RearLeft,
|
||||||
|
gst_audio::AudioChannelPosition::RearRight,
|
||||||
|
gst_audio::AudioChannelPosition::SideLeft,
|
||||||
|
gst_audio::AudioChannelPosition::SideRight,
|
||||||
|
gst_audio::AudioChannelPosition::TopFrontLeft,
|
||||||
|
gst_audio::AudioChannelPosition::TopFrontRight,
|
||||||
|
];
|
||||||
|
|
||||||
|
let mask = gst_audio::AudioChannelPosition::positions_to_mask(&positions, true).unwrap();
|
||||||
|
let caps = gst_audio::AudioCapsBuilder::new()
|
||||||
|
.channels(positions.len() as i32)
|
||||||
|
.channel_mask(mask)
|
||||||
|
.build();
|
||||||
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
||||||
|
.property("caps", &caps)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let audioconvert = gst::ElementFactory::make("audioconvert").build().unwrap();
|
||||||
|
let audioresample = gst::ElementFactory::make("audioresample").build().unwrap();
|
||||||
|
let wavenc = gst::ElementFactory::make("wavenc").build().unwrap();
|
||||||
|
let sink = gst::ElementFactory::make("filesink")
|
||||||
|
.property("location", output_file)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.add_many([
|
||||||
|
&audiomixer,
|
||||||
|
&capsfilter,
|
||||||
|
&audioconvert,
|
||||||
|
&audioresample,
|
||||||
|
&wavenc,
|
||||||
|
&sink,
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
gst::Element::link_many([
|
||||||
|
&audiomixer,
|
||||||
|
&capsfilter,
|
||||||
|
&audioconvert,
|
||||||
|
&audioresample,
|
||||||
|
&wavenc,
|
||||||
|
&sink,
|
||||||
|
])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for i in 0..TRACKS {
|
||||||
|
create_source_and_link(&pipeline, &audiomixer, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bus = pipeline.bus().expect("Pipeline without bus");
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Playing)
|
||||||
|
.expect("Unable to start pipeline");
|
||||||
|
|
||||||
|
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
||||||
|
use gst::MessageView;
|
||||||
|
match msg.view() {
|
||||||
|
MessageView::Eos(..) => break,
|
||||||
|
MessageView::Error(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"Error from {:?}: {} ({:?})",
|
||||||
|
msg.src().map(|s| s.path_string()),
|
||||||
|
err.error(),
|
||||||
|
err.debug()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Null)
|
||||||
|
.expect("Unable to change pipeline state to NULL");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// tutorials_common::run is only required to set up the application environment on macOS
|
||||||
|
// (but not necessary in normal Cocoa applications where this is set up automatically)
|
||||||
|
examples_common::run(example_main);
|
||||||
|
}
|
|
@ -12,7 +12,6 @@ mod examples_common;
|
||||||
mod cairo_compositor {
|
mod cairo_compositor {
|
||||||
use gst_base::subclass::prelude::*;
|
use gst_base::subclass::prelude::*;
|
||||||
use gst_video::{prelude::*, subclass::prelude::*};
|
use gst_video::{prelude::*, subclass::prelude::*};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
// In the imp submodule we include the actual implementation of the compositor.
|
// In the imp submodule we include the actual implementation of the compositor.
|
||||||
mod imp {
|
mod imp {
|
||||||
|
@ -53,19 +52,20 @@ mod cairo_compositor {
|
||||||
|
|
||||||
// Implementation of glib::Object virtual methods.
|
// Implementation of glib::Object virtual methods.
|
||||||
impl ObjectImpl for CairoCompositor {
|
impl ObjectImpl for CairoCompositor {
|
||||||
// Specfication of the compositor properties.
|
// Specification of the compositor properties.
|
||||||
// In this case a single property for configuring the background color of the
|
// In this case a single property for configuring the background color of the
|
||||||
// composition.
|
// composition.
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: std::sync::OnceLock<Vec<glib::ParamSpec>> =
|
||||||
|
std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
PROPERTIES.get_or_init(|| {
|
||||||
vec![glib::ParamSpecUInt::builder("background-color")
|
vec![glib::ParamSpecUInt::builder("background-color")
|
||||||
.nick("Background Color")
|
.nick("Background Color")
|
||||||
.blurb("Background color as 0xRRGGBB")
|
.blurb("Background color as 0xRRGGBB")
|
||||||
.default_value(Settings::default().background_color)
|
.default_value(Settings::default().background_color)
|
||||||
.build()]
|
.build()]
|
||||||
});
|
})
|
||||||
|
|
||||||
&PROPERTIES
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the application whenever the value of a property should be changed.
|
// Called by the application whenever the value of a property should be changed.
|
||||||
|
@ -100,20 +100,24 @@ mod cairo_compositor {
|
||||||
// gst-inspect-1.0 and can also be programmatically retrieved from the gst::Registry
|
// gst-inspect-1.0 and can also be programmatically retrieved from the gst::Registry
|
||||||
// after initial registration without having to load the plugin in memory.
|
// after initial registration without having to load the plugin in memory.
|
||||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
static ELEMENT_METADATA: std::sync::OnceLock<gst::subclass::ElementMetadata> =
|
||||||
|
std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
Some(ELEMENT_METADATA.get_or_init(|| {
|
||||||
gst::subclass::ElementMetadata::new(
|
gst::subclass::ElementMetadata::new(
|
||||||
"Cairo Compositor",
|
"Cairo Compositor",
|
||||||
"Compositor/Video",
|
"Compositor/Video",
|
||||||
"Cairo based compositor",
|
"Cairo based compositor",
|
||||||
"Sebastian Dröge <sebastian@centricular.com>",
|
"Sebastian Dröge <sebastian@centricular.com>",
|
||||||
)
|
)
|
||||||
});
|
}))
|
||||||
|
|
||||||
Some(&*ELEMENT_METADATA)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
static PAD_TEMPLATES: std::sync::OnceLock<Vec<gst::PadTemplate>> =
|
||||||
|
std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
PAD_TEMPLATES.get_or_init(|| {
|
||||||
// Create pad templates for our sink and source pad. These are later used for
|
// Create pad templates for our sink and source pad. These are later used for
|
||||||
// actually creating the pads and beforehand already provide information to
|
// actually creating the pads and beforehand already provide information to
|
||||||
// GStreamer about all possible pads that could exist for this type.
|
// GStreamer about all possible pads that could exist for this type.
|
||||||
|
@ -148,9 +152,7 @@ mod cairo_compositor {
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|
||||||
PAD_TEMPLATES.as_ref()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify via the child proxy interface whenever a new pad is added or removed.
|
// Notify via the child proxy interface whenever a new pad is added or removed.
|
||||||
|
@ -453,11 +455,14 @@ mod cairo_compositor {
|
||||||
|
|
||||||
// Implementation of glib::Object virtual methods.
|
// Implementation of glib::Object virtual methods.
|
||||||
impl ObjectImpl for CairoCompositorPad {
|
impl ObjectImpl for CairoCompositorPad {
|
||||||
// Specfication of the compositor pad properties.
|
// Specification of the compositor pad properties.
|
||||||
// In this case there are various properties for defining the position and otherwise
|
// In this case there are various properties for defining the position and otherwise
|
||||||
// the appearance of the stream corresponding to this pad.
|
// the appearance of the stream corresponding to this pad.
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
static PROPERTIES: std::sync::OnceLock<Vec<glib::ParamSpec>> =
|
||||||
|
std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
PROPERTIES.get_or_init(|| {
|
||||||
vec![
|
vec![
|
||||||
glib::ParamSpecDouble::builder("alpha")
|
glib::ParamSpecDouble::builder("alpha")
|
||||||
.nick("Alpha")
|
.nick("Alpha")
|
||||||
|
@ -495,9 +500,7 @@ mod cairo_compositor {
|
||||||
.default_value(Settings::default().ypos)
|
.default_value(Settings::default().ypos)
|
||||||
.build(),
|
.build(),
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|
||||||
PROPERTIES.as_ref()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the application whenever the value of a property should be changed.
|
// Called by the application whenever the value of a property should be changed.
|
||||||
|
@ -576,7 +579,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
|
|
||||||
comp.set_property("background-color", 0xff_33_33_33u32);
|
comp.set_property("background-color", 0xff_33_33_33u32);
|
||||||
|
|
||||||
pipeline.add_many(&[&src1, &src2, comp.upcast_ref(), &conv, &sink])?;
|
pipeline.add_many([&src1, &src2, comp.upcast_ref(), &conv, &sink])?;
|
||||||
|
|
||||||
// Link everything together.
|
// Link everything together.
|
||||||
src1.link_filtered(
|
src1.link_filtered(
|
||||||
|
|
|
@ -48,7 +48,7 @@ fn example_main() {
|
||||||
let main_loop = glib::MainLoop::new(None, false);
|
let main_loop = glib::MainLoop::new(None, false);
|
||||||
|
|
||||||
// This creates a pipeline by parsing the gst-launch pipeline syntax.
|
// This creates a pipeline by parsing the gst-launch pipeline syntax.
|
||||||
let pipeline = gst::parse_launch(
|
let pipeline = gst::parse::launch(
|
||||||
"audiotestsrc name=src ! queue max-size-time=2000000000 ! fakesink name=sink sync=true",
|
"audiotestsrc name=src ! queue max-size-time=2000000000 ! fakesink name=sink sync=true",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -75,31 +75,33 @@ fn example_main() {
|
||||||
// Add a pad probe on the sink pad and catch the custom event we sent, then send
|
// Add a pad probe on the sink pad and catch the custom event we sent, then send
|
||||||
// an EOS event on the pipeline.
|
// an EOS event on the pipeline.
|
||||||
sinkpad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, move |_, probe_info| {
|
sinkpad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, move |_, probe_info| {
|
||||||
match probe_info.data {
|
let Some(event) = probe_info.event() else {
|
||||||
Some(gst::PadProbeData::Event(ref ev))
|
return gst::PadProbeReturn::Ok;
|
||||||
if ev.type_() == gst::EventType::CustomDownstream =>
|
};
|
||||||
{
|
|
||||||
if let Some(custom_event) = ExampleCustomEvent::parse(ev) {
|
let Some(custom_event) = ExampleCustomEvent::parse(event) else {
|
||||||
if let Some(pipeline) = pipeline_weak.upgrade() {
|
return gst::PadProbeReturn::Ok;
|
||||||
if custom_event.send_eos {
|
};
|
||||||
/* Send EOS event to shut down the pipeline, but from an async callback, as we're
|
|
||||||
* in a pad probe blocking the stream thread here... */
|
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||||
println!("Got custom event with send_eos=true. Sending EOS");
|
return gst::PadProbeReturn::Ok;
|
||||||
let ev = gst::event::Eos::new();
|
};
|
||||||
let pipeline_weak = pipeline_weak.clone();
|
|
||||||
pipeline.call_async(move |_| {
|
if custom_event.send_eos {
|
||||||
if let Some(pipeline) = pipeline_weak.upgrade() {
|
/* Send EOS event to shut down the pipeline, but from an async callback, as we're
|
||||||
pipeline.send_event(ev);
|
* in a pad probe blocking the stream thread here... */
|
||||||
}
|
println!("Got custom event with send_eos=true. Sending EOS");
|
||||||
});
|
let ev = gst::event::Eos::new();
|
||||||
} else {
|
let pipeline_weak = pipeline_weak.clone();
|
||||||
println!("Got custom event, with send_eos=false. Ignoring");
|
pipeline.call_async(move |_| {
|
||||||
}
|
if let Some(pipeline) = pipeline_weak.upgrade() {
|
||||||
}
|
pipeline.send_event(ev);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
_ => (),
|
} else {
|
||||||
|
println!("Got custom event, with send_eos=false. Ignoring");
|
||||||
}
|
}
|
||||||
|
|
||||||
gst::PadProbeReturn::Ok
|
gst::PadProbeReturn::Ok
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -113,9 +115,8 @@ fn example_main() {
|
||||||
glib::timeout_add_seconds(2 + i as u32, move || {
|
glib::timeout_add_seconds(2 + i as u32, move || {
|
||||||
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
||||||
// we moved into this callback.
|
// we moved into this callback.
|
||||||
let pipeline = match pipeline_weak.upgrade() {
|
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||||
Some(pipeline) => pipeline,
|
return glib::ControlFlow::Break;
|
||||||
None => return glib::Continue(false),
|
|
||||||
};
|
};
|
||||||
println!("Sending custom event to the pipeline with send_eos={send_eos}");
|
println!("Sending custom event to the pipeline with send_eos={send_eos}");
|
||||||
let ev = ExampleCustomEvent::new(*send_eos);
|
let ev = ExampleCustomEvent::new(*send_eos);
|
||||||
|
@ -124,42 +125,43 @@ fn example_main() {
|
||||||
}
|
}
|
||||||
// Remove this handler, the pipeline will shutdown once our pad probe catches the custom
|
// Remove this handler, the pipeline will shutdown once our pad probe catches the custom
|
||||||
// event and sends EOS
|
// event and sends EOS
|
||||||
glib::Continue(false)
|
glib::ControlFlow::Break
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let main_loop_clone = main_loop.clone();
|
let main_loop_clone = main_loop.clone();
|
||||||
// This sets the bus's signal handler (don't be mislead by the "add", there can only be one).
|
// This sets the bus's signal handler (don't be mislead by the "add", there can only be one).
|
||||||
// Every message from the bus is passed through this function. Its returnvalue determines
|
// Every message from the bus is passed through this function. Its returnvalue determines
|
||||||
// whether the handler wants to be called again. If glib::Continue(false) is returned, the
|
// whether the handler wants to be called again. If glib::ControlFlow::Break is returned, the
|
||||||
// handler is removed and will never be called again. The mainloop still runs though.
|
// handler is removed and will never be called again. The mainloop still runs though.
|
||||||
bus.add_watch(move |_, msg| {
|
let _bus_watch = bus
|
||||||
use gst::MessageView;
|
.add_watch(move |_, msg| {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
let main_loop = &main_loop_clone;
|
let main_loop = &main_loop_clone;
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::Eos(..) => {
|
MessageView::Eos(..) => {
|
||||||
println!("received eos");
|
println!("received eos");
|
||||||
// An EndOfStream event was sent to the pipeline, so we tell our main loop
|
// An EndOfStream event was sent to the pipeline, so we tell our main loop
|
||||||
// to stop execution here.
|
// to stop execution here.
|
||||||
main_loop.quit()
|
main_loop.quit()
|
||||||
}
|
}
|
||||||
MessageView::Error(err) => {
|
MessageView::Error(err) => {
|
||||||
println!(
|
println!(
|
||||||
"Error from {:?}: {} ({:?})",
|
"Error from {:?}: {} ({:?})",
|
||||||
err.src().map(|s| s.path_string()),
|
err.src().map(|s| s.path_string()),
|
||||||
err.error(),
|
err.error(),
|
||||||
err.debug()
|
err.debug()
|
||||||
);
|
);
|
||||||
main_loop.quit();
|
main_loop.quit();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tell the mainloop to continue executing this callback.
|
// Tell the mainloop to continue executing this callback.
|
||||||
glib::Continue(true)
|
glib::ControlFlow::Continue
|
||||||
})
|
})
|
||||||
.expect("Failed to add bus watch");
|
.expect("Failed to add bus watch");
|
||||||
|
|
||||||
// Operate GStreamer's bus, facilitating GLib's mainloop here.
|
// Operate GStreamer's bus, facilitating GLib's mainloop here.
|
||||||
// This function call will block until you tell the mainloop to quit
|
// This function call will block until you tell the mainloop to quit
|
||||||
|
@ -169,11 +171,6 @@ fn example_main() {
|
||||||
pipeline
|
pipeline
|
||||||
.set_state(gst::State::Null)
|
.set_state(gst::State::Null)
|
||||||
.expect("Unable to set the pipeline to the `Null` state");
|
.expect("Unable to set the pipeline to the `Null` state");
|
||||||
|
|
||||||
// Remove the watch function from the bus.
|
|
||||||
// Again: There can always only be one watch function.
|
|
||||||
// Thus we don't have to tell him which function to remove.
|
|
||||||
bus.remove_watch().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -72,7 +72,6 @@ mod custom_meta {
|
||||||
use std::{mem, ptr};
|
use std::{mem, ptr};
|
||||||
|
|
||||||
use glib::translate::*;
|
use glib::translate::*;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
|
|
||||||
pub(super) struct CustomMetaParams {
|
pub(super) struct CustomMetaParams {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
@ -87,8 +86,10 @@ mod custom_meta {
|
||||||
|
|
||||||
// Function to register the meta API and get a type back.
|
// Function to register the meta API and get a type back.
|
||||||
pub(super) fn custom_meta_api_get_type() -> glib::Type {
|
pub(super) fn custom_meta_api_get_type() -> glib::Type {
|
||||||
static TYPE: Lazy<glib::Type> = Lazy::new(|| unsafe {
|
static TYPE: std::sync::OnceLock<glib::Type> = std::sync::OnceLock::new();
|
||||||
let t = from_glib(gst::ffi::gst_meta_api_type_register(
|
|
||||||
|
*TYPE.get_or_init(|| unsafe {
|
||||||
|
let t = glib::Type::from_glib(gst::ffi::gst_meta_api_type_register(
|
||||||
b"MyCustomMetaAPI\0".as_ptr() as *const _,
|
b"MyCustomMetaAPI\0".as_ptr() as *const _,
|
||||||
// We provide no tags here as our meta is just a label and does
|
// We provide no tags here as our meta is just a label and does
|
||||||
// not refer to any specific aspect of the buffer.
|
// not refer to any specific aspect of the buffer.
|
||||||
|
@ -98,9 +99,7 @@ mod custom_meta {
|
||||||
assert_ne!(t, glib::Type::INVALID);
|
assert_ne!(t, glib::Type::INVALID);
|
||||||
|
|
||||||
t
|
t
|
||||||
});
|
})
|
||||||
|
|
||||||
*TYPE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialization function for our meta. This needs to ensure all fields are correctly
|
// Initialization function for our meta. This needs to ensure all fields are correctly
|
||||||
|
@ -157,21 +156,24 @@ mod custom_meta {
|
||||||
unsafe impl Send for MetaInfo {}
|
unsafe impl Send for MetaInfo {}
|
||||||
unsafe impl Sync for MetaInfo {}
|
unsafe impl Sync for MetaInfo {}
|
||||||
|
|
||||||
static META_INFO: Lazy<MetaInfo> = Lazy::new(|| unsafe {
|
static META_INFO: std::sync::OnceLock<MetaInfo> = std::sync::OnceLock::new();
|
||||||
MetaInfo(
|
|
||||||
ptr::NonNull::new(gst::ffi::gst_meta_register(
|
|
||||||
custom_meta_api_get_type().into_glib(),
|
|
||||||
b"MyCustomMeta\0".as_ptr() as *const _,
|
|
||||||
mem::size_of::<CustomMeta>(),
|
|
||||||
Some(custom_meta_init),
|
|
||||||
Some(custom_meta_free),
|
|
||||||
Some(custom_meta_transform),
|
|
||||||
) as *mut gst::ffi::GstMetaInfo)
|
|
||||||
.expect("Failed to register meta API"),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
META_INFO.0.as_ptr()
|
META_INFO
|
||||||
|
.get_or_init(|| unsafe {
|
||||||
|
MetaInfo(
|
||||||
|
ptr::NonNull::new(gst::ffi::gst_meta_register(
|
||||||
|
custom_meta_api_get_type().into_glib(),
|
||||||
|
b"MyCustomMeta\0".as_ptr() as *const _,
|
||||||
|
mem::size_of::<CustomMeta>(),
|
||||||
|
Some(custom_meta_init),
|
||||||
|
Some(custom_meta_free),
|
||||||
|
Some(custom_meta_transform),
|
||||||
|
) as *mut gst::ffi::GstMetaInfo)
|
||||||
|
.expect("Failed to register meta API"),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.0
|
||||||
|
.as_ptr()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// This example demonstrates the use of the d3d11videosink's "present"
|
// This example demonstrates the use of the d3d11videosink's "present"
|
||||||
// signal and the use of Direct2D/DirectWrite APIs in Rust.
|
// signal and the use of Direct2D/DirectWrite APIs in Rust.
|
||||||
//
|
//
|
||||||
// Application can perform various hardware-acceleated 2D graphics operation
|
// Application can perform various hardware-accelerated 2D graphics operation
|
||||||
// (e.g., like cairo can support) and text rendering via the Windows APIs.
|
// (e.g., like cairo can support) and text rendering via the Windows APIs.
|
||||||
// In this example, 2D graphics operation and text rendering will happen
|
// In this example, 2D graphics operation and text rendering will happen
|
||||||
// directly to the on the DXGI swapchain's backbuffer via Windows API in
|
// directly to the on the DXGI swapchain's backbuffer via Windows API in
|
||||||
|
@ -129,11 +129,11 @@ fn main() -> Result<()> {
|
||||||
// APIs are marked as unsafe, except for cast.
|
// APIs are marked as unsafe, except for cast.
|
||||||
//
|
//
|
||||||
// In theory, all the Direct3D/Direct2D APIs could fail for
|
// In theory, all the Direct3D/Direct2D APIs could fail for
|
||||||
// some reasons (it's hardware!), but in pratice, it's very unexpected
|
// some reasons (it's hardware!), but in practice, it's very unexpected
|
||||||
// situation and any of failure below would mean we are doing
|
// situation and any of failure below would mean we are doing
|
||||||
// something in wrong way or driver bug or so.
|
// something in wrong way or driver bug or so.
|
||||||
unsafe {
|
unsafe {
|
||||||
let rtv = ID3D11RenderTargetView::from_raw_borrowed(&rtv_raw);
|
let rtv = ID3D11RenderTargetView::from_raw_borrowed(&rtv_raw).unwrap();
|
||||||
let resource = rtv.GetResource().unwrap();
|
let resource = rtv.GetResource().unwrap();
|
||||||
|
|
||||||
let texture = resource.cast::<ID3D11Texture2D>().unwrap();
|
let texture = resource.cast::<ID3D11Texture2D>().unwrap();
|
||||||
|
@ -194,7 +194,7 @@ fn main() -> Result<()> {
|
||||||
let mut metrics = DWRITE_TEXT_METRICS::default();
|
let mut metrics = DWRITE_TEXT_METRICS::default();
|
||||||
layout.GetMetrics(&mut metrics).unwrap();
|
layout.GetMetrics(&mut metrics).unwrap();
|
||||||
layout
|
layout
|
||||||
.GetFontSize2(0, &mut font_size, Some(&mut range))
|
.GetFontSize(0, &mut font_size, Some(&mut range))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if metrics.widthIncludingTrailingWhitespace >= desc.Width as f32 {
|
if metrics.widthIncludingTrailingWhitespace >= desc.Width as f32 {
|
||||||
|
@ -304,20 +304,19 @@ fn main() -> Result<()> {
|
||||||
// Add pad probe to calculate framerate
|
// Add pad probe to calculate framerate
|
||||||
let sinkpad = videosink.static_pad("sink").unwrap();
|
let sinkpad = videosink.static_pad("sink").unwrap();
|
||||||
let overlay_context_weak = Arc::downgrade(&overlay_context);
|
let overlay_context_weak = Arc::downgrade(&overlay_context);
|
||||||
sinkpad.add_probe(gst::PadProbeType::BUFFER, move |_, probe_info| {
|
sinkpad.add_probe(gst::PadProbeType::BUFFER, move |_pad, _probe_info| {
|
||||||
if let Some(gst::PadProbeData::Buffer(_)) = probe_info.data {
|
let overlay_context = overlay_context_weak.upgrade().unwrap();
|
||||||
let overlay_context = overlay_context_weak.upgrade().unwrap();
|
let mut context = overlay_context.lock().unwrap();
|
||||||
let mut context = overlay_context.lock().unwrap();
|
context.timestamp_queue.push_back(SystemTime::now());
|
||||||
context.timestamp_queue.push_back(SystemTime::now());
|
// Updates framerate per 10 frames
|
||||||
// Updates framerate per 10 frames
|
if context.timestamp_queue.len() >= 10 {
|
||||||
if context.timestamp_queue.len() >= 10 {
|
let now = context.timestamp_queue.back().unwrap();
|
||||||
let now = context.timestamp_queue.back().unwrap();
|
let front = context.timestamp_queue.front().unwrap();
|
||||||
let front = context.timestamp_queue.front().unwrap();
|
let duration = now.duration_since(*front).unwrap().as_millis() as f32;
|
||||||
let duration = now.duration_since(*front).unwrap().as_millis() as f32;
|
context.avg_fps = 1000f32 * (context.timestamp_queue.len() - 1) as f32 / duration;
|
||||||
context.avg_fps = 1000f32 * (context.timestamp_queue.len() - 1) as f32 / duration;
|
context.timestamp_queue.clear();
|
||||||
context.timestamp_queue.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gst::PadProbeReturn::Ok
|
gst::PadProbeReturn::Ok
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -329,30 +328,31 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
let main_loop_clone = main_loop.clone();
|
let main_loop_clone = main_loop.clone();
|
||||||
let bus = playbin.bus().unwrap();
|
let bus = playbin.bus().unwrap();
|
||||||
bus.add_watch(move |_, msg| {
|
let _bus_watch = bus
|
||||||
use gst::MessageView;
|
.add_watch(move |_, msg| {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
let main_loop = &main_loop_clone;
|
let main_loop = &main_loop_clone;
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::Eos(..) => {
|
MessageView::Eos(..) => {
|
||||||
println!("received eos");
|
println!("received eos");
|
||||||
main_loop.quit()
|
main_loop.quit()
|
||||||
}
|
}
|
||||||
MessageView::Error(err) => {
|
MessageView::Error(err) => {
|
||||||
println!(
|
println!(
|
||||||
"Error from {:?}: {} ({:?})",
|
"Error from {:?}: {} ({:?})",
|
||||||
err.src().map(|s| s.path_string()),
|
err.src().map(|s| s.path_string()),
|
||||||
err.error(),
|
err.error(),
|
||||||
err.debug()
|
err.debug()
|
||||||
);
|
);
|
||||||
main_loop.quit();
|
main_loop.quit();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
glib::Continue(true)
|
glib::ControlFlow::Continue
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
playbin.set_state(gst::State::Playing).unwrap();
|
playbin.set_state(gst::State::Playing).unwrap();
|
||||||
|
|
||||||
|
|
|
@ -19,15 +19,15 @@ fn example_main() {
|
||||||
|
|
||||||
/* Disable stdout debug, then configure the debug ringbuffer and enable
|
/* Disable stdout debug, then configure the debug ringbuffer and enable
|
||||||
* all debug */
|
* all debug */
|
||||||
gst::debug_remove_default_log_function();
|
gst::log::remove_default_log_function();
|
||||||
/* Keep 1KB of logs per thread, removing old threads after 10 seconds */
|
/* Keep 1KB of logs per thread, removing old threads after 10 seconds */
|
||||||
gst::debug_add_ring_buffer_logger(1024, 10);
|
gst::log::add_ring_buffer_logger(1024, 10);
|
||||||
/* Enable all debug categories */
|
/* Enable all debug categories */
|
||||||
gst::debug_set_default_threshold(gst::DebugLevel::Log);
|
gst::log::set_default_threshold(gst::DebugLevel::Log);
|
||||||
|
|
||||||
let mut context = gst::ParseContext::new();
|
let mut context = gst::ParseContext::new();
|
||||||
let pipeline =
|
let pipeline =
|
||||||
match gst::parse_launch_full(pipeline_str, Some(&mut context), gst::ParseFlags::empty()) {
|
match gst::parse::launch_full(pipeline_str, Some(&mut context), gst::ParseFlags::empty()) {
|
||||||
Ok(pipeline) => pipeline,
|
Ok(pipeline) => pipeline,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Some(gst::ParseError::NoSuchElement) = err.kind::<gst::ParseError>() {
|
if let Some(gst::ParseError::NoSuchElement) = err.kind::<gst::ParseError>() {
|
||||||
|
@ -73,7 +73,7 @@ fn example_main() {
|
||||||
gst::error!(gst::CAT_DEFAULT, "Hi from the debug log ringbuffer example");
|
gst::error!(gst::CAT_DEFAULT, "Hi from the debug log ringbuffer example");
|
||||||
|
|
||||||
println!("Dumping debug logs\n");
|
println!("Dumping debug logs\n");
|
||||||
for s in gst::debug_ring_buffer_logger_get_logs().iter() {
|
for s in gst::log::ring_buffer_logger_get_logs().iter() {
|
||||||
println!("{s}\n------------------");
|
println!("{s}\n------------------");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,8 +70,8 @@ fn example_main() -> Result<(), Error> {
|
||||||
.build()?;
|
.build()?;
|
||||||
let decodebin = gst::ElementFactory::make("decodebin").build()?;
|
let decodebin = gst::ElementFactory::make("decodebin").build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &decodebin])?;
|
pipeline.add_many([&src, &decodebin])?;
|
||||||
gst::Element::link_many(&[&src, &decodebin])?;
|
gst::Element::link_many([&src, &decodebin])?;
|
||||||
|
|
||||||
// Need to move a new reference into the closure.
|
// Need to move a new reference into the closure.
|
||||||
// !!ATTENTION!!:
|
// !!ATTENTION!!:
|
||||||
|
@ -90,9 +90,8 @@ fn example_main() -> Result<(), Error> {
|
||||||
decodebin.connect_pad_added(move |dbin, src_pad| {
|
decodebin.connect_pad_added(move |dbin, src_pad| {
|
||||||
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
||||||
// we moved into this callback.
|
// we moved into this callback.
|
||||||
let pipeline = match pipeline_weak.upgrade() {
|
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||||
Some(pipeline) => pipeline,
|
return;
|
||||||
None => return,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Try to detect whether the raw stream decodebin provided us with
|
// Try to detect whether the raw stream decodebin provided us with
|
||||||
|
@ -190,7 +189,7 @@ fn example_main() -> Result<(), Error> {
|
||||||
("Failed to insert sink"),
|
("Failed to insert sink"),
|
||||||
details: gst::Structure::builder("error-details")
|
details: gst::Structure::builder("error-details")
|
||||||
.field("error",
|
.field("error",
|
||||||
&ErrorValue(Arc::new(Mutex::new(Some(err)))))
|
ErrorValue(Arc::new(Mutex::new(Some(err)))))
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,11 @@ fn print_tags(info: &DiscovererInfo) {
|
||||||
|
|
||||||
fn print_stream_info(stream: &DiscovererStreamInfo) {
|
fn print_stream_info(stream: &DiscovererStreamInfo) {
|
||||||
println!("Stream: ");
|
println!("Stream: ");
|
||||||
println!(" Stream id: {}", stream.stream_id());
|
|
||||||
|
if let Some(stream_id) = stream.stream_id() {
|
||||||
|
println!(" Stream id: {}", stream_id);
|
||||||
|
}
|
||||||
|
|
||||||
let caps_str = match stream.caps() {
|
let caps_str = match stream.caps() {
|
||||||
Some(caps) => caps.to_string(),
|
Some(caps) => caps.to_string(),
|
||||||
None => String::from("--"),
|
None => String::from("--"),
|
||||||
|
|
|
@ -97,13 +97,13 @@ fn example_main() -> Result<(), Error> {
|
||||||
configure_encodebin(&encodebin);
|
configure_encodebin(&encodebin);
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
.add_many(&[&src, &encodebin, &sink])
|
.add_many([&src, &encodebin, &sink])
|
||||||
.expect("failed to add elements to pipeline");
|
.expect("failed to add elements to pipeline");
|
||||||
// It is clear from the start, that encodebin has only one src pad, so we can
|
// It is clear from the start, that encodebin has only one src pad, so we can
|
||||||
// directly link it to our filesink without problems.
|
// directly link it to our filesink without problems.
|
||||||
// The caps of encodebin's src-pad are set after we configured the encoding-profile.
|
// The caps of encodebin's src-pad are set after we configured the encoding-profile.
|
||||||
// (But filesink doesn't really care about the caps at its input anyway)
|
// (But filesink doesn't really care about the caps at its input anyway)
|
||||||
gst::Element::link_many(&[&encodebin, &sink])?;
|
gst::Element::link_many([&encodebin, &sink])?;
|
||||||
|
|
||||||
// Need to move a new reference into the closure.
|
// Need to move a new reference into the closure.
|
||||||
// !!ATTENTION!!:
|
// !!ATTENTION!!:
|
||||||
|
@ -120,9 +120,8 @@ fn example_main() -> Result<(), Error> {
|
||||||
src.connect_pad_added(move |dbin, dbin_src_pad| {
|
src.connect_pad_added(move |dbin, dbin_src_pad| {
|
||||||
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
||||||
// we moved into this callback.
|
// we moved into this callback.
|
||||||
let pipeline = match pipeline_weak.upgrade() {
|
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||||
Some(pipeline) => pipeline,
|
return;
|
||||||
None => return,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let (is_audio, is_video) = {
|
let (is_audio, is_video) = {
|
||||||
|
@ -189,7 +188,7 @@ fn example_main() -> Result<(), Error> {
|
||||||
|
|
||||||
// Request a sink pad from our encodebin, that can handle a raw videostream.
|
// Request a sink pad from our encodebin, that can handle a raw videostream.
|
||||||
// The encodebin will then automatically create an internal pipeline, that encodes
|
// The encodebin will then automatically create an internal pipeline, that encodes
|
||||||
// the audio stream in the format we specified in the EncodingProfile.
|
// the video stream in the format we specified in the EncodingProfile.
|
||||||
let enc_sink_pad = encodebin
|
let enc_sink_pad = encodebin
|
||||||
.request_pad_simple("video_%u")
|
.request_pad_simple("video_%u")
|
||||||
.expect("Could not get video pad from encodebin");
|
.expect("Could not get video pad from encodebin");
|
||||||
|
@ -216,7 +215,7 @@ fn example_main() -> Result<(), Error> {
|
||||||
("Failed to insert sink"),
|
("Failed to insert sink"),
|
||||||
details: gst::Structure::builder("error-details")
|
details: gst::Structure::builder("error-details")
|
||||||
.field("error",
|
.field("error",
|
||||||
&ErrorValue(Arc::new(Mutex::new(Some(err)))))
|
ErrorValue(Arc::new(Mutex::new(Some(err)))))
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,44 @@ fn example_main() {
|
||||||
let main_loop = glib::MainLoop::new(None, false);
|
let main_loop = glib::MainLoop::new(None, false);
|
||||||
|
|
||||||
// This creates a pipeline by parsing the gst-launch pipeline syntax.
|
// This creates a pipeline by parsing the gst-launch pipeline syntax.
|
||||||
let pipeline = gst::parse_launch("audiotestsrc ! fakesink").unwrap();
|
let pipeline = gst::parse::launch("audiotestsrc ! identity name=capsmut ! fakesink").unwrap();
|
||||||
let bus = pipeline.bus().unwrap();
|
let bus = pipeline.bus().unwrap();
|
||||||
|
|
||||||
|
// This is a contrived example to mutate events. This would normally be code inside an element,
|
||||||
|
// which might transform caps to reflect transformation in the data
|
||||||
|
let identity = pipeline
|
||||||
|
.downcast_ref::<gst::Bin>()
|
||||||
|
.unwrap()
|
||||||
|
.by_name("capsmut")
|
||||||
|
.unwrap();
|
||||||
|
let _ = identity.static_pad("sink").unwrap().add_probe(
|
||||||
|
gst::PadProbeType::EVENT_DOWNSTREAM,
|
||||||
|
move |_, probe_info| {
|
||||||
|
let Some(e) = probe_info.event() else {
|
||||||
|
return gst::PadProbeReturn::Ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
if e.type_() != gst::EventType::Caps {
|
||||||
|
return gst::PadProbeReturn::Ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ev = probe_info.take_event().unwrap();
|
||||||
|
let ev_ref = ev.make_mut();
|
||||||
|
|
||||||
|
let gst::EventViewMut::Caps(caps) = ev_ref.view_mut() else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
caps.structure_mut().set("custom-field", true);
|
||||||
|
identity
|
||||||
|
.static_pad("src")
|
||||||
|
.unwrap()
|
||||||
|
.push_event(ev_ref.to_owned());
|
||||||
|
|
||||||
|
gst::PadProbeReturn::Drop
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
.set_state(gst::State::Playing)
|
.set_state(gst::State::Playing)
|
||||||
.expect("Unable to set the pipeline to the `Playing` state");
|
.expect("Unable to set the pipeline to the `Playing` state");
|
||||||
|
@ -50,14 +85,13 @@ fn example_main() {
|
||||||
// Add a timeout to the main loop. This closure will be executed
|
// Add a timeout to the main loop. This closure will be executed
|
||||||
// in an interval of 5 seconds. The return value of the handler function
|
// in an interval of 5 seconds. The return value of the handler function
|
||||||
// determines whether the handler still wants to be called:
|
// determines whether the handler still wants to be called:
|
||||||
// - glib::Continue(false) - stop calling this handler, remove timeout
|
// - glib::ControlFlow::Break - stop calling this handler, remove timeout
|
||||||
// - glib::Continue(true) - continue calling this handler
|
// - glib::ControlFlow::Continue- continue calling this handler
|
||||||
glib::timeout_add_seconds(5, move || {
|
glib::timeout_add_seconds(5, move || {
|
||||||
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
||||||
// we moved into this callback.
|
// we moved into this callback.
|
||||||
let pipeline = match pipeline_weak.upgrade() {
|
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||||
Some(pipeline) => pipeline,
|
return glib::ControlFlow::Break;
|
||||||
None => return glib::Continue(false),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("sending eos");
|
println!("sending eos");
|
||||||
|
@ -77,7 +111,7 @@ fn example_main() {
|
||||||
|
|
||||||
// Remove this handler, the pipeline will shutdown anyway, now that we
|
// Remove this handler, the pipeline will shutdown anyway, now that we
|
||||||
// sent the EOS event.
|
// sent the EOS event.
|
||||||
glib::Continue(false)
|
glib::ControlFlow::Break
|
||||||
});
|
});
|
||||||
|
|
||||||
//bus.add_signal_watch();
|
//bus.add_signal_watch();
|
||||||
|
@ -85,35 +119,36 @@ fn example_main() {
|
||||||
let main_loop_clone = main_loop.clone();
|
let main_loop_clone = main_loop.clone();
|
||||||
// This sets the bus's signal handler (don't be mislead by the "add", there can only be one).
|
// This sets the bus's signal handler (don't be mislead by the "add", there can only be one).
|
||||||
// Every message from the bus is passed through this function. Its returnvalue determines
|
// Every message from the bus is passed through this function. Its returnvalue determines
|
||||||
// whether the handler wants to be called again. If glib::Continue(false) is returned, the
|
// whether the handler wants to be called again. If glib::ControlFlow::Break is returned, the
|
||||||
// handler is removed and will never be called again. The mainloop still runs though.
|
// handler is removed and will never be called again. The mainloop still runs though.
|
||||||
bus.add_watch(move |_, msg| {
|
let _bus_watch = bus
|
||||||
use gst::MessageView;
|
.add_watch(move |_, msg| {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
let main_loop = &main_loop_clone;
|
let main_loop = &main_loop_clone;
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::Eos(..) => {
|
MessageView::Eos(..) => {
|
||||||
println!("received eos");
|
println!("received eos");
|
||||||
// An EndOfStream event was sent to the pipeline, so we tell our main loop
|
// An EndOfStream event was sent to the pipeline, so we tell our main loop
|
||||||
// to stop execution here.
|
// to stop execution here.
|
||||||
main_loop.quit()
|
main_loop.quit()
|
||||||
}
|
}
|
||||||
MessageView::Error(err) => {
|
MessageView::Error(err) => {
|
||||||
println!(
|
println!(
|
||||||
"Error from {:?}: {} ({:?})",
|
"Error from {:?}: {} ({:?})",
|
||||||
err.src().map(|s| s.path_string()),
|
err.src().map(|s| s.path_string()),
|
||||||
err.error(),
|
err.error(),
|
||||||
err.debug()
|
err.debug()
|
||||||
);
|
);
|
||||||
main_loop.quit();
|
main_loop.quit();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Tell the mainloop to continue executing this callback.
|
// Tell the mainloop to continue executing this callback.
|
||||||
glib::Continue(true)
|
glib::ControlFlow::Continue
|
||||||
})
|
})
|
||||||
.expect("Failed to add bus watch");
|
.expect("Failed to add bus watch");
|
||||||
|
|
||||||
// Operate GStreamer's bus, facilliating GLib's mainloop here.
|
// Operate GStreamer's bus, facilliating GLib's mainloop here.
|
||||||
// This function call will block until you tell the mainloop to quit
|
// This function call will block until you tell the mainloop to quit
|
||||||
|
@ -123,11 +158,6 @@ fn example_main() {
|
||||||
pipeline
|
pipeline
|
||||||
.set_state(gst::State::Null)
|
.set_state(gst::State::Null)
|
||||||
.expect("Unable to set the pipeline to the `Null` state");
|
.expect("Unable to set the pipeline to the `Null` state");
|
||||||
|
|
||||||
// Remove the watch function from the bus.
|
|
||||||
// Again: There can always only be one watch function.
|
|
||||||
// Thus we don't have to tell him which function to remove.
|
|
||||||
bus.remove_watch().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
|
@ -45,8 +45,8 @@ fn create_receiver_pipeline(
|
||||||
let queue = gst::ElementFactory::make("queue").build()?;
|
let queue = gst::ElementFactory::make("queue").build()?;
|
||||||
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[src.upcast_ref(), &filter, &convert, &queue, &sink])?;
|
pipeline.add_many([src.upcast_ref(), &filter, &convert, &queue, &sink])?;
|
||||||
gst::Element::link_many(&[src.upcast_ref(), &filter, &convert, &queue, &sink])?;
|
gst::Element::link_many([src.upcast_ref(), &filter, &convert, &queue, &sink])?;
|
||||||
|
|
||||||
let fd_allocator = gst_allocators::FdAllocator::new();
|
let fd_allocator = gst_allocators::FdAllocator::new();
|
||||||
let video_info = video_info.clone();
|
let video_info = video_info.clone();
|
||||||
|
@ -115,8 +115,8 @@ fn create_sender_pipeline(
|
||||||
.ok_or_else(|| anyhow::anyhow!("is not a appsink"))?
|
.ok_or_else(|| anyhow::anyhow!("is not a appsink"))?
|
||||||
.set_caps(Some(&caps));
|
.set_caps(Some(&caps));
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &sink])?;
|
pipeline.add_many([&src, &sink])?;
|
||||||
gst::Element::link_many(&[&src, &sink])?;
|
gst::Element::link_many([&src, &sink])?;
|
||||||
|
|
||||||
let appsink = sink
|
let appsink = sink
|
||||||
.downcast::<gst_app::AppSink>()
|
.downcast::<gst_app::AppSink>()
|
||||||
|
@ -363,7 +363,7 @@ mod video_filter {
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use gst::{subclass::prelude::*, PadDirection, PadPresence, PadTemplate};
|
use gst::{subclass::prelude::*, PadDirection, PadPresence, PadTemplate};
|
||||||
use gst_app::gst_base::subclass::BaseTransformMode;
|
use gst_app::gst_base::subclass::BaseTransformMode;
|
||||||
use gst_video::{subclass::prelude::*, VideoFrameRef};
|
use gst_video::{prelude::*, subclass::prelude::*, VideoFrameRef};
|
||||||
use memmap2::MmapMut;
|
use memmap2::MmapMut;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
@ -430,7 +430,10 @@ mod video_filter {
|
||||||
|
|
||||||
impl ElementImpl for FdMemoryFadeInVideoFilter {
|
impl ElementImpl for FdMemoryFadeInVideoFilter {
|
||||||
fn pad_templates() -> &'static [PadTemplate] {
|
fn pad_templates() -> &'static [PadTemplate] {
|
||||||
static PAD_TEMPLATES: Lazy<Vec<PadTemplate>> = Lazy::new(|| {
|
static PAD_TEMPLATES: std::sync::OnceLock<Vec<PadTemplate>> =
|
||||||
|
std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
PAD_TEMPLATES.get_or_init(|| {
|
||||||
let caps = gst_video::VideoCapsBuilder::new()
|
let caps = gst_video::VideoCapsBuilder::new()
|
||||||
.format(gst_video::VideoFormat::Bgra)
|
.format(gst_video::VideoFormat::Bgra)
|
||||||
.build();
|
.build();
|
||||||
|
@ -440,9 +443,7 @@ mod video_filter {
|
||||||
PadTemplate::new("src", PadDirection::Src, PadPresence::Always, &caps)
|
PadTemplate::new("src", PadDirection::Src, PadPresence::Always, &caps)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|
||||||
PAD_TEMPLATES.as_ref()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,7 +459,7 @@ mod video_filter {
|
||||||
frame: &mut VideoFrameRef<&mut gst::BufferRef>,
|
frame: &mut VideoFrameRef<&mut gst::BufferRef>,
|
||||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
self.transform_fd_mem_ip(frame).map_err(|err| {
|
self.transform_fd_mem_ip(frame).map_err(|err| {
|
||||||
gst::error!(CAT, imp: self, "Failed to transform frame`: {}", err);
|
gst::error!(CAT, imp = self, "Failed to transform frame`: {}", err);
|
||||||
gst::FlowError::Error
|
gst::FlowError::Error
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ fn example_main() {
|
||||||
gst::init().unwrap();
|
gst::init().unwrap();
|
||||||
|
|
||||||
// Create a pipeline from the launch-syntax given on the cli.
|
// Create a pipeline from the launch-syntax given on the cli.
|
||||||
let pipeline = gst::parse_launch(&pipeline_str).unwrap();
|
let pipeline = gst::parse::launch(&pipeline_str).unwrap();
|
||||||
let bus = pipeline.bus().unwrap();
|
let bus = pipeline.bus().unwrap();
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(clippy::non_send_fields_in_send_ty)]
|
#![allow(clippy::non_send_fields_in_send_ty)]
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
#[path = "../glupload.rs"]
|
#[path = "../glupload.rs"]
|
||||||
mod glupload;
|
mod glupload;
|
||||||
use glupload::*;
|
use glupload::*;
|
||||||
|
@ -78,7 +80,7 @@ mod mirror {
|
||||||
|
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp = self,
|
||||||
"Compiling fragment shader {}",
|
"Compiling fragment shader {}",
|
||||||
FRAGMENT_SHADER
|
FRAGMENT_SHADER
|
||||||
);
|
);
|
||||||
|
@ -97,7 +99,7 @@ mod mirror {
|
||||||
|
|
||||||
gst::debug!(
|
gst::debug!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp = self,
|
||||||
"Successfully compiled and linked {:?}",
|
"Successfully compiled and linked {:?}",
|
||||||
shader
|
shader
|
||||||
);
|
);
|
||||||
|
@ -161,14 +163,12 @@ mod mirror {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn example_main() {
|
fn example_main() -> Result<()> {
|
||||||
gst::init().unwrap();
|
gst::init().unwrap();
|
||||||
let glfilter = mirror::GLMirrorFilter::new(Some("foo"));
|
let glfilter = mirror::GLMirrorFilter::new(Some("Mirror filter"));
|
||||||
App::new(Some(glfilter.as_ref()))
|
App::new(Some(glfilter.as_ref())).and_then(main_loop)
|
||||||
.and_then(main_loop)
|
|
||||||
.unwrap_or_else(|e| eprintln!("Error! {e}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<()> {
|
||||||
examples_common::run(example_main);
|
examples_common::run(example_main)
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ fn example_main() {
|
||||||
gst::init().unwrap();
|
gst::init().unwrap();
|
||||||
|
|
||||||
// Create a pipeline from the launch-syntax given on the cli.
|
// Create a pipeline from the launch-syntax given on the cli.
|
||||||
let pipeline = gst::parse_launch(&pipeline_str).unwrap();
|
let pipeline = gst::parse::launch(&pipeline_str).unwrap();
|
||||||
let bus = pipeline.bus().unwrap();
|
let bus = pipeline.bus().unwrap();
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(clippy::non_send_fields_in_send_ty)]
|
#![allow(clippy::non_send_fields_in_send_ty)]
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
#[path = "../glupload.rs"]
|
#[path = "../glupload.rs"]
|
||||||
mod glupload;
|
mod glupload;
|
||||||
use glupload::*;
|
use glupload::*;
|
||||||
|
@ -7,12 +9,10 @@ use glupload::*;
|
||||||
#[path = "../examples-common.rs"]
|
#[path = "../examples-common.rs"]
|
||||||
pub mod examples_common;
|
pub mod examples_common;
|
||||||
|
|
||||||
fn example_main() {
|
fn example_main() -> Result<()> {
|
||||||
App::new(None)
|
App::new(None).and_then(main_loop)
|
||||||
.and_then(main_loop)
|
|
||||||
.unwrap_or_else(|e| eprintln!("Error! {e}"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<()> {
|
||||||
examples_common::run(example_main);
|
examples_common::run(example_main)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,179 +0,0 @@
|
||||||
// This example demonstrates how to use gstreamer in conjunction with the gtk widget toolkit.
|
|
||||||
// This example shows the video produced by a videotestsrc within a small gtk gui.
|
|
||||||
// For this, the gtkglsink is used, which creates a gtk widget one can embed the gtk gui.
|
|
||||||
// For this, there multiple types of widgets. gtkglsink uses OpenGL to render frames, and
|
|
||||||
// gtksink uses the CPU to render the frames (which is way slower).
|
|
||||||
// So the example application first tries to use OpenGL, and when that fails, fall back.
|
|
||||||
// The pipeline looks like the following:
|
|
||||||
|
|
||||||
// gtk-gui: {gtkglsink}-widget
|
|
||||||
// (|)
|
|
||||||
// {videotestsrc} - {glsinkbin}
|
|
||||||
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use gio::prelude::*;
|
|
||||||
use gst::prelude::*;
|
|
||||||
use gtk::prelude::*;
|
|
||||||
|
|
||||||
fn create_ui(app: >k::Application) {
|
|
||||||
let pipeline = gst::Pipeline::default();
|
|
||||||
let src = gst::ElementFactory::make("videotestsrc").build().unwrap();
|
|
||||||
// Create the gtk sink and retrieve the widget from it. The sink element will be used
|
|
||||||
// in the pipeline, and the widget will be embedded in our gui.
|
|
||||||
// Gstreamer then displays frames in the gtk widget.
|
|
||||||
// First, we try to use the OpenGL version - and if that fails, we fall back to non-OpenGL.
|
|
||||||
let (sink, widget) = if let Ok(gtkglsink) = gst::ElementFactory::make("gtkglsink").build() {
|
|
||||||
// Using the OpenGL widget succeeded, so we are in for a nice playback experience with
|
|
||||||
// low cpu usage. :)
|
|
||||||
// The gtkglsink essentially allocates an OpenGL texture on the GPU, that it will display.
|
|
||||||
// Now we create the glsinkbin element, which is responsible for conversions and for uploading
|
|
||||||
// video frames to our texture (if they are not already in the GPU). Now we tell the OpenGL-sink
|
|
||||||
// about our gtkglsink element, form where it will retrieve the OpenGL texture to fill.
|
|
||||||
let glsinkbin = gst::ElementFactory::make("glsinkbin")
|
|
||||||
.property("sink", >kglsink)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
// The gtkglsink creates the gtk widget for us. This is accessible through a property.
|
|
||||||
// So we get it and use it later to add it to our gui.
|
|
||||||
let widget = gtkglsink.property::<gtk::Widget>("widget");
|
|
||||||
(glsinkbin, widget)
|
|
||||||
} else {
|
|
||||||
// Unfortunately, using the OpenGL widget didn't work out, so we will have to render
|
|
||||||
// our frames manually, using the CPU. An example why this may fail is, when
|
|
||||||
// the PC doesn't have proper graphics drivers installed.
|
|
||||||
let sink = gst::ElementFactory::make("gtksink").build().unwrap();
|
|
||||||
// The gtksink creates the gtk widget for us. This is accessible through a property.
|
|
||||||
// So we get it and use it later to add it to our gui.
|
|
||||||
let widget = sink.property::<gtk::Widget>("widget");
|
|
||||||
(sink, widget)
|
|
||||||
};
|
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &sink]).unwrap();
|
|
||||||
src.link(&sink).unwrap();
|
|
||||||
|
|
||||||
// Create a simple gtk gui window to place our widget into.
|
|
||||||
let window = gtk::Window::new(gtk::WindowType::Toplevel);
|
|
||||||
window.set_default_size(320, 240);
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
|
||||||
// Add our widget to the gui
|
|
||||||
vbox.pack_start(&widget, true, true, 0);
|
|
||||||
let label = gtk::Label::new(Some("Position: 00:00:00"));
|
|
||||||
vbox.pack_start(&label, true, true, 5);
|
|
||||||
window.add(&vbox);
|
|
||||||
window.show_all();
|
|
||||||
|
|
||||||
app.add_window(&window);
|
|
||||||
|
|
||||||
// Need to move a new reference into the closure.
|
|
||||||
// !!ATTENTION!!:
|
|
||||||
// It might seem appealing to use pipeline.clone() here, because that greatly
|
|
||||||
// simplifies the code within the callback. What this actually does, however, is creating
|
|
||||||
// a memory leak. The clone of a pipeline is a new strong reference on the pipeline.
|
|
||||||
// Storing this strong reference of the pipeline within the callback (we are moving it in!),
|
|
||||||
// which is in turn stored in another strong reference on the pipeline is creating a
|
|
||||||
// reference cycle.
|
|
||||||
// DO NOT USE pipeline.clone() TO USE THE PIPELINE WITHIN A CALLBACK
|
|
||||||
let pipeline_weak = pipeline.downgrade();
|
|
||||||
// Add a timeout to the main loop that will periodically (every 500ms) be
|
|
||||||
// executed. This will query the current position within the stream from
|
|
||||||
// the underlying pipeline, and display it in our gui.
|
|
||||||
// Since this closure is called by the mainloop thread, we are allowed
|
|
||||||
// to modify the gui widgets here.
|
|
||||||
let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(500), move || {
|
|
||||||
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
|
||||||
// we moved into this callback.
|
|
||||||
let pipeline = match pipeline_weak.upgrade() {
|
|
||||||
Some(pipeline) => pipeline,
|
|
||||||
None => return glib::Continue(true),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Query the current playing position from the underlying pipeline.
|
|
||||||
let position = pipeline.query_position::<gst::ClockTime>();
|
|
||||||
// Display the playing position in the gui.
|
|
||||||
label.set_text(&format!("Position: {:.0}", position.display()));
|
|
||||||
// Tell the callback to continue calling this closure.
|
|
||||||
glib::Continue(true)
|
|
||||||
});
|
|
||||||
|
|
||||||
let bus = pipeline.bus().unwrap();
|
|
||||||
|
|
||||||
pipeline
|
|
||||||
.set_state(gst::State::Playing)
|
|
||||||
.expect("Unable to set the pipeline to the `Playing` state");
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
bus.add_watch_local(move |_, msg| {
|
|
||||||
use gst::MessageView;
|
|
||||||
|
|
||||||
let app = match app_weak.upgrade() {
|
|
||||||
Some(app) => app,
|
|
||||||
None => return glib::Continue(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
match msg.view() {
|
|
||||||
MessageView::Eos(..) => app.quit(),
|
|
||||||
MessageView::Error(err) => {
|
|
||||||
println!(
|
|
||||||
"Error from {:?}: {} ({:?})",
|
|
||||||
err.src().map(|s| s.path_string()),
|
|
||||||
err.error(),
|
|
||||||
err.debug()
|
|
||||||
);
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
glib::Continue(true)
|
|
||||||
})
|
|
||||||
.expect("Failed to add bus watch");
|
|
||||||
|
|
||||||
// Pipeline reference is owned by the closure below, so will be
|
|
||||||
// destroyed once the app is destroyed
|
|
||||||
let timeout_id = RefCell::new(Some(timeout_id));
|
|
||||||
let pipeline = RefCell::new(Some(pipeline));
|
|
||||||
app.connect_shutdown(move |_| {
|
|
||||||
// Optional, by manually destroying the window here we ensure that
|
|
||||||
// the gst element is destroyed when shutting down instead of having to wait
|
|
||||||
// for the process to terminate, allowing us to use the leaks tracer.
|
|
||||||
unsafe {
|
|
||||||
window.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// GTK will keep the Application alive for the whole process lifetime.
|
|
||||||
// Wrapping the pipeline in a RefCell<Option<_>> and removing it from it here
|
|
||||||
// ensures the pipeline is actually destroyed when shutting down, allowing us
|
|
||||||
// to use the leaks tracer for example.
|
|
||||||
if let Some(pipeline) = pipeline.borrow_mut().take() {
|
|
||||||
pipeline
|
|
||||||
.set_state(gst::State::Null)
|
|
||||||
.expect("Unable to set the pipeline to the `Null` state");
|
|
||||||
pipeline.bus().unwrap().remove_watch().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(timeout_id) = timeout_id.borrow_mut().take() {
|
|
||||||
timeout_id.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> glib::ExitCode {
|
|
||||||
// Initialize gstreamer and the gtk widget toolkit libraries.
|
|
||||||
gst::init().unwrap();
|
|
||||||
gtk::init().unwrap();
|
|
||||||
|
|
||||||
let res = {
|
|
||||||
let app = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE);
|
|
||||||
|
|
||||||
app.connect_activate(create_ui);
|
|
||||||
app.run()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Optional, can be used to detect leaks using the leaks tracer
|
|
||||||
unsafe {
|
|
||||||
gst::deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
|
@ -1,286 +0,0 @@
|
||||||
// This example demonstrates another type of combination of gtk and gstreamer,
|
|
||||||
// in comparision to the gtksink example.
|
|
||||||
// This example uses regions that are managed by the window system, and uses
|
|
||||||
// the window system's api to insert a videostream into these regions.
|
|
||||||
// So essentially, the window system of the system overlays our gui with
|
|
||||||
// the video frames - within the region that we tell it to use.
|
|
||||||
// Disadvantage of this method is, that it's highly platform specific, since
|
|
||||||
// the big platforms all have their own window system. Thus, this example
|
|
||||||
// has special code to handle differences between platforms.
|
|
||||||
// Windows could theoretically be supported by this example, but is not yet implemented.
|
|
||||||
// One of the very few (if not the single one) platform, that can not provide the API
|
|
||||||
// needed for this are Linux desktops using Wayland.
|
|
||||||
// TODO: Add Windows support
|
|
||||||
// In this case, a testvideo is displayed within our gui, using the
|
|
||||||
// following pipeline:
|
|
||||||
|
|
||||||
// {videotestsrc} - {xvimagesink(on linux)}
|
|
||||||
// {videotestsrc} - {glimagesink(on mac)}
|
|
||||||
|
|
||||||
use std::{cell::RefCell, os::raw::c_void, process};
|
|
||||||
|
|
||||||
use gio::prelude::*;
|
|
||||||
use gst_video::prelude::*;
|
|
||||||
use gtk::prelude::*;
|
|
||||||
|
|
||||||
#[cfg(all(target_os = "linux", feature = "gtkvideooverlay-x11"))]
|
|
||||||
fn create_video_sink() -> gst::Element {
|
|
||||||
// When we are on linux with the Xorg display server, we use the
|
|
||||||
// X11 protocol's XV extension, which allows to overlay regions
|
|
||||||
// with video streams. For this, we use the xvimagesink element.
|
|
||||||
gst::ElementFactory::make("xvimagesink").build().unwrap()
|
|
||||||
}
|
|
||||||
#[cfg(all(target_os = "linux", feature = "gtkvideooverlay-x11"))]
|
|
||||||
fn set_window_handle(video_overlay: &gst_video::VideoOverlay, gdk_window: &gdk::Window) {
|
|
||||||
let display_type_name = gdk_window.display().type_().name();
|
|
||||||
|
|
||||||
// Check if we're using X11 or ...
|
|
||||||
if display_type_name == "GdkX11Display" {
|
|
||||||
extern "C" {
|
|
||||||
pub fn gdk_x11_window_get_xid(window: *mut glib::gobject_ffi::GObject) -> *mut c_void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is unsafe because the "window handle" we pass here is basically like a raw pointer.
|
|
||||||
// If a wrong value were to be passed here (and you can pass any integer), then the window
|
|
||||||
// system will most likely cause the application to crash.
|
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
|
||||||
unsafe {
|
|
||||||
// Here we ask gdk what native window handle we got assigned for
|
|
||||||
// our video region from the window system, and then we will
|
|
||||||
// pass this unique identifier to the overlay provided by our
|
|
||||||
// sink - so the sink can then arrange the overlay.
|
|
||||||
let xid = gdk_x11_window_get_xid(gdk_window.as_ptr() as *mut _);
|
|
||||||
video_overlay.set_window_handle(xid as usize);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("Add support for display type '{display_type_name}'");
|
|
||||||
process::exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(target_os = "macos", feature = "gtkvideooverlay-quartz"))]
|
|
||||||
fn create_video_sink() -> gst::Element {
|
|
||||||
// On Mac, this is done by overlaying a window region with an
|
|
||||||
// OpenGL-texture, using the glimagesink element.
|
|
||||||
gst::ElementFactory::make("glimagesink").build().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(target_os = "macos", feature = "gtkvideooverlay-quartz"))]
|
|
||||||
fn set_window_handle(video_overlay: &gst_video::VideoOverlay, gdk_window: &gdk::Window) {
|
|
||||||
let display_type_name = gdk_window.display().type_().name();
|
|
||||||
|
|
||||||
if display_type_name == "GdkQuartzDisplay" {
|
|
||||||
extern "C" {
|
|
||||||
pub fn gdk_quartz_window_get_nsview(
|
|
||||||
window: *mut glib::gobject_ffi::GObject,
|
|
||||||
) -> *mut c_void;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is unsafe because the "window handle" we pass here is basically like a raw pointer.
|
|
||||||
// If a wrong value were to be passed here (and you can pass any integer), then the window
|
|
||||||
// system will most likely cause the application to crash.
|
|
||||||
#[allow(clippy::cast_ptr_alignment)]
|
|
||||||
unsafe {
|
|
||||||
// Here we ask gdk what native window handle we got assigned for
|
|
||||||
// our video region from the windowing system, and then we will
|
|
||||||
// pass this unique identifier to the overlay provided by our
|
|
||||||
// sink - so the sink can then arrange the overlay.
|
|
||||||
let window = gdk_quartz_window_get_nsview(gdk_window.as_ptr() as *mut _);
|
|
||||||
video_overlay.set_window_handle(window as usize);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("Unsupported display type '{}", display_type_name);
|
|
||||||
process::exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_ui(app: >k::Application) {
|
|
||||||
let pipeline = gst::Pipeline::default();
|
|
||||||
let src = gst::ElementFactory::make("videotestsrc").build().unwrap();
|
|
||||||
|
|
||||||
// Since using the window system to overlay our gui window is making
|
|
||||||
// direct contact with the windowing system, this is highly platform-
|
|
||||||
// specific. This example supports Linux and Mac (using X11 and Quartz).
|
|
||||||
let sink = create_video_sink();
|
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &sink]).unwrap();
|
|
||||||
src.link(&sink).unwrap();
|
|
||||||
|
|
||||||
// First, we create our gtk window - which will contain a region where
|
|
||||||
// our overlayed video will be displayed in.
|
|
||||||
let window = gtk::Window::new(gtk::WindowType::Toplevel);
|
|
||||||
window.set_default_size(320, 240);
|
|
||||||
|
|
||||||
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
|
|
||||||
|
|
||||||
// This creates the widget we will display our overlay in.
|
|
||||||
// Later, we will try to tell our window system about this region, so
|
|
||||||
// it can overlay it with our video stream.
|
|
||||||
let video_window = gtk::DrawingArea::new();
|
|
||||||
video_window.set_size_request(320, 240);
|
|
||||||
|
|
||||||
// Use the platform-specific sink to create our overlay.
|
|
||||||
// Since we only use the video_overlay in the closure below, we need a weak reference.
|
|
||||||
// !!ATTENTION!!:
|
|
||||||
// It might seem appealing to use .clone() here, because that greatly
|
|
||||||
// simplifies the code within the callback. What this actually does, however, is creating
|
|
||||||
// a memory leak.
|
|
||||||
let video_overlay = sink
|
|
||||||
.dynamic_cast::<gst_video::VideoOverlay>()
|
|
||||||
.unwrap()
|
|
||||||
.downgrade();
|
|
||||||
// Connect to this widget's realize signal, which will be emitted
|
|
||||||
// after its display has been initialized. This is neccessary, because
|
|
||||||
// the window system doesn't know about our region until it was initialized.
|
|
||||||
video_window.connect_realize(move |video_window| {
|
|
||||||
// Here we temporarily retrieve a strong reference on the video-overlay from the
|
|
||||||
// weak reference that we moved into the closure.
|
|
||||||
let video_overlay = match video_overlay.upgrade() {
|
|
||||||
Some(video_overlay) => video_overlay,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Gtk uses gdk under the hood, to handle its drawing. Drawing regions are
|
|
||||||
// called gdk windows. We request this underlying drawing region from the
|
|
||||||
// widget we will overlay with our video.
|
|
||||||
let gdk_window = video_window.window().unwrap();
|
|
||||||
|
|
||||||
// This is where we tell our window system about the drawing-region we
|
|
||||||
// want it to overlay. Most often, the window system would only know
|
|
||||||
// about our most outer region (or: our window).
|
|
||||||
if !gdk_window.ensure_native() {
|
|
||||||
println!("Can't create native window for widget");
|
|
||||||
process::exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
set_window_handle(&video_overlay, &gdk_window);
|
|
||||||
});
|
|
||||||
|
|
||||||
vbox.pack_start(&video_window, true, true, 0);
|
|
||||||
|
|
||||||
let label = gtk::Label::new(Some("Position: 00:00:00"));
|
|
||||||
vbox.pack_start(&label, true, true, 5);
|
|
||||||
window.add(&vbox);
|
|
||||||
|
|
||||||
window.show_all();
|
|
||||||
|
|
||||||
app.add_window(&window);
|
|
||||||
|
|
||||||
// Need to move a new reference into the closure.
|
|
||||||
// !!ATTENTION!!:
|
|
||||||
// It might seem appealing to use pipeline.clone() here, because that greatly
|
|
||||||
// simplifies the code within the callback. What this actually does, however, is creating
|
|
||||||
// a memory leak. The clone of a pipeline is a new strong reference on the pipeline.
|
|
||||||
// Storing this strong reference of the pipeline within the callback (we are moving it in!),
|
|
||||||
// which is in turn stored in another strong reference on the pipeline is creating a
|
|
||||||
// reference cycle.
|
|
||||||
// DO NOT USE pipeline.clone() TO USE THE PIPELINE WITHIN A CALLBACK
|
|
||||||
let pipeline_weak = pipeline.downgrade();
|
|
||||||
// Add a timeout to the main loop that will periodically (every 500ms) be
|
|
||||||
// executed. This will query the current position within the stream from
|
|
||||||
// the underlying pipeline, and display it in our gui.
|
|
||||||
// Since this closure is called by the mainloop thread, we are allowed
|
|
||||||
// to modify the gui widgets here.
|
|
||||||
let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(500), move || {
|
|
||||||
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
|
||||||
// we moved into this callback.
|
|
||||||
let pipeline = match pipeline_weak.upgrade() {
|
|
||||||
Some(pipeline) => pipeline,
|
|
||||||
None => return glib::Continue(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Query the current playing position from the underlying pipeline.
|
|
||||||
let position = pipeline.query_position::<gst::ClockTime>();
|
|
||||||
// Display the playing position in the gui.
|
|
||||||
label.set_text(&format!("Position: {:.0}", position.display()));
|
|
||||||
// Tell the timeout to continue calling this callback.
|
|
||||||
glib::Continue(true)
|
|
||||||
});
|
|
||||||
|
|
||||||
let bus = pipeline.bus().unwrap();
|
|
||||||
|
|
||||||
pipeline
|
|
||||||
.set_state(gst::State::Playing)
|
|
||||||
.expect("Unable to set the pipeline to the `Playing` state");
|
|
||||||
|
|
||||||
let app_weak = app.downgrade();
|
|
||||||
bus.add_watch_local(move |_, msg| {
|
|
||||||
use gst::MessageView;
|
|
||||||
|
|
||||||
let app = match app_weak.upgrade() {
|
|
||||||
Some(app) => app,
|
|
||||||
None => return glib::Continue(false),
|
|
||||||
};
|
|
||||||
|
|
||||||
match msg.view() {
|
|
||||||
MessageView::Eos(..) => app.quit(),
|
|
||||||
MessageView::Error(err) => {
|
|
||||||
println!(
|
|
||||||
"Error from {:?}: {} ({:?})",
|
|
||||||
err.src().map(|s| s.path_string()),
|
|
||||||
err.error(),
|
|
||||||
err.debug()
|
|
||||||
);
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
glib::Continue(true)
|
|
||||||
})
|
|
||||||
.expect("Failed to add bus watch");
|
|
||||||
|
|
||||||
// Pipeline reference is owned by the closure below, so will be
|
|
||||||
// destroyed once the app is destroyed
|
|
||||||
let timeout_id = RefCell::new(Some(timeout_id));
|
|
||||||
let pipeline = RefCell::new(Some(pipeline));
|
|
||||||
app.connect_shutdown(move |_| {
|
|
||||||
// Optional, by manually destroying the window here we ensure that
|
|
||||||
// the gst element is destroyed when shutting down instead of having to wait
|
|
||||||
// for the process to terminate, allowing us to use the leaks tracer.
|
|
||||||
unsafe {
|
|
||||||
window.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// GTK will keep the Application alive for the whole process lifetime.
|
|
||||||
// Wrapping the pipeline in a RefCell<Option<_>> and removing it from it here
|
|
||||||
// ensures the pipeline is actually destroyed when shutting down, allowing us
|
|
||||||
// to use the leaks tracer for example.
|
|
||||||
if let Some(pipeline) = pipeline.borrow_mut().take() {
|
|
||||||
pipeline
|
|
||||||
.set_state(gst::State::Null)
|
|
||||||
.expect("Unable to set the pipeline to the `Null` state");
|
|
||||||
pipeline.bus().unwrap().remove_watch().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(timeout_id) = timeout_id.borrow_mut().take() {
|
|
||||||
timeout_id.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> glib::ExitCode {
|
|
||||||
#[cfg(not(unix))]
|
|
||||||
{
|
|
||||||
println!("Add support for target platform");
|
|
||||||
process::exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize gstreamer and the gtk widget toolkit libraries.
|
|
||||||
gst::init().unwrap();
|
|
||||||
gtk::init().unwrap();
|
|
||||||
|
|
||||||
let res = {
|
|
||||||
let app = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE);
|
|
||||||
|
|
||||||
app.connect_activate(create_ui);
|
|
||||||
app.run()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Optional, can be used to detect leaks using the leaks tracer
|
|
||||||
unsafe {
|
|
||||||
gst::deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
// This example demonstrates how to use GStreamer's iteration APIs.
|
// This example demonstrates how to use GStreamer's iteration APIs.
|
||||||
// This is used at multiple occassions - for example to iterate an
|
// This is used at multiple occasions - for example to iterate an
|
||||||
// element's pads.
|
// element's pads.
|
||||||
|
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
|
|
@ -17,7 +17,7 @@ fn example_main() {
|
||||||
gst::init().unwrap();
|
gst::init().unwrap();
|
||||||
|
|
||||||
// Let GStreamer create a pipeline from the parsed launch syntax on the cli.
|
// Let GStreamer create a pipeline from the parsed launch syntax on the cli.
|
||||||
// In comparision to the launch_glib_main example, this is using the advanced launch syntax
|
// In comparison to the launch_glib_main example, this is using the advanced launch syntax
|
||||||
// parsing API of GStreamer. The function returns a Result, handing us the pipeline if
|
// parsing API of GStreamer. The function returns a Result, handing us the pipeline if
|
||||||
// parsing and creating succeeded, and hands us detailed error information if something
|
// parsing and creating succeeded, and hands us detailed error information if something
|
||||||
// went wrong. The error is passed as gst::ParseError. In this example, we separately
|
// went wrong. The error is passed as gst::ParseError. In this example, we separately
|
||||||
|
@ -26,19 +26,22 @@ fn example_main() {
|
||||||
// Especially GUIs should probably handle this case, to tell users that they need to
|
// Especially GUIs should probably handle this case, to tell users that they need to
|
||||||
// install the corresponding gstreamer plugins.
|
// install the corresponding gstreamer plugins.
|
||||||
let mut context = gst::ParseContext::new();
|
let mut context = gst::ParseContext::new();
|
||||||
let pipeline =
|
let pipeline = match gst::parse::launch_full(
|
||||||
match gst::parse_launch_full(&pipeline_str, Some(&mut context), gst::ParseFlags::empty()) {
|
&pipeline_str,
|
||||||
Ok(pipeline) => pipeline,
|
Some(&mut context),
|
||||||
Err(err) => {
|
gst::ParseFlags::empty(),
|
||||||
if let Some(gst::ParseError::NoSuchElement) = err.kind::<gst::ParseError>() {
|
) {
|
||||||
println!("Missing element(s): {:?}", context.missing_elements());
|
Ok(pipeline) => pipeline,
|
||||||
} else {
|
Err(err) => {
|
||||||
println!("Failed to parse pipeline: {err}");
|
if let Some(gst::ParseError::NoSuchElement) = err.kind::<gst::ParseError>() {
|
||||||
}
|
println!("Missing element(s): {:?}", context.missing_elements());
|
||||||
|
} else {
|
||||||
process::exit(-1)
|
println!("Failed to parse pipeline: {err}");
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
process::exit(-1)
|
||||||
|
}
|
||||||
|
};
|
||||||
let bus = pipeline.bus().unwrap();
|
let bus = pipeline.bus().unwrap();
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
|
|
|
@ -24,7 +24,7 @@ fn example_main() {
|
||||||
let main_loop = glib::MainLoop::new(None, false);
|
let main_loop = glib::MainLoop::new(None, false);
|
||||||
|
|
||||||
// Let GStreamer create a pipeline from the parsed launch syntax on the cli.
|
// Let GStreamer create a pipeline from the parsed launch syntax on the cli.
|
||||||
let pipeline = gst::parse_launch(&pipeline_str).unwrap();
|
let pipeline = gst::parse::launch(&pipeline_str).unwrap();
|
||||||
let bus = pipeline.bus().unwrap();
|
let bus = pipeline.bus().unwrap();
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
|
@ -35,38 +35,34 @@ fn example_main() {
|
||||||
|
|
||||||
//bus.add_signal_watch();
|
//bus.add_signal_watch();
|
||||||
//bus.connect_message(None, move |_, msg| {
|
//bus.connect_message(None, move |_, msg| {
|
||||||
bus.add_watch(move |_, msg| {
|
let _bus_watch = bus
|
||||||
use gst::MessageView;
|
.add_watch(move |_, msg| {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
let main_loop = &main_loop_clone;
|
let main_loop = &main_loop_clone;
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::Eos(..) => main_loop.quit(),
|
MessageView::Eos(..) => main_loop.quit(),
|
||||||
MessageView::Error(err) => {
|
MessageView::Error(err) => {
|
||||||
println!(
|
println!(
|
||||||
"Error from {:?}: {} ({:?})",
|
"Error from {:?}: {} ({:?})",
|
||||||
err.src().map(|s| s.path_string()),
|
err.src().map(|s| s.path_string()),
|
||||||
err.error(),
|
err.error(),
|
||||||
err.debug()
|
err.debug()
|
||||||
);
|
);
|
||||||
main_loop.quit();
|
main_loop.quit();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
glib::Continue(true)
|
glib::ControlFlow::Continue
|
||||||
})
|
})
|
||||||
.expect("Failed to add bus watch");
|
.expect("Failed to add bus watch");
|
||||||
|
|
||||||
main_loop.run();
|
main_loop.run();
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
.set_state(gst::State::Null)
|
.set_state(gst::State::Null)
|
||||||
.expect("Unable to set the pipeline to the `Null` state");
|
.expect("Unable to set the pipeline to the `Null` state");
|
||||||
|
|
||||||
// Here we remove the bus watch we added above. This avoids a memory leak, that might
|
|
||||||
// otherwise happen because we moved a strong reference (clone of main_loop) into the
|
|
||||||
// callback closure above.
|
|
||||||
bus.remove_watch().unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
|
368
examples/src/bin/overlay-composition-d2d.rs
Normal file
368
examples/src/bin/overlay-composition-d2d.rs
Normal file
|
@ -0,0 +1,368 @@
|
||||||
|
// This example demonstrates how to draw an overlay on a video stream using
|
||||||
|
// Direct2D/DirectWrite/WIC and the overlay composition element.
|
||||||
|
|
||||||
|
// {videotestsrc} - {overlaycomposition} - {capsfilter} - {videoconvert} - {autovideosink}
|
||||||
|
// The capsfilter element allows us to dictate the video resolution we want for the
|
||||||
|
// videotestsrc and the overlaycomposition element.
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use derive_more::{Display, Error};
|
||||||
|
use gst::prelude::*;
|
||||||
|
use windows::{
|
||||||
|
Foundation::Numerics::*,
|
||||||
|
Win32::{
|
||||||
|
Graphics::{
|
||||||
|
Direct2D::{Common::*, *},
|
||||||
|
DirectWrite::*,
|
||||||
|
Dxgi::Common::*,
|
||||||
|
Imaging::*,
|
||||||
|
},
|
||||||
|
System::Com::*,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Error)]
|
||||||
|
#[display(fmt = "Received error from {}: {} (debug: {:?})", src, error, debug)]
|
||||||
|
struct ErrorMessage {
|
||||||
|
src: glib::GString,
|
||||||
|
error: glib::Error,
|
||||||
|
debug: Option<glib::GString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DrawingContext {
|
||||||
|
// Factory for creating render target
|
||||||
|
d2d_factory: ID2D1Factory,
|
||||||
|
|
||||||
|
// Used to create WIC bitmap surface
|
||||||
|
wic_factory: IWICImagingFactory,
|
||||||
|
|
||||||
|
// text layout holding text information (string, font, size, etc)
|
||||||
|
text_layout: IDWriteTextLayout,
|
||||||
|
|
||||||
|
// Holding rendred image
|
||||||
|
bitmap: Option<IWICBitmap>,
|
||||||
|
|
||||||
|
// Bound to bitmap and used to actual Direct2D rendering
|
||||||
|
render_target: Option<ID2D1RenderTarget>,
|
||||||
|
|
||||||
|
info: Option<gst_video::VideoInfo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Required for IWICBitmap
|
||||||
|
unsafe impl Send for DrawingContext {}
|
||||||
|
|
||||||
|
fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
|
gst::init()?;
|
||||||
|
|
||||||
|
let pipeline = gst::Pipeline::default();
|
||||||
|
|
||||||
|
// The videotestsrc supports multiple test patterns. In this example, we will use the
|
||||||
|
// pattern with a white ball moving around the video's center point.
|
||||||
|
let src = gst::ElementFactory::make("videotestsrc")
|
||||||
|
.property_from_str("pattern", "ball")
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let overlay = gst::ElementFactory::make("overlaycomposition").build()?;
|
||||||
|
|
||||||
|
let caps = gst_video::VideoCapsBuilder::new()
|
||||||
|
.width(800)
|
||||||
|
.height(800)
|
||||||
|
.framerate((30, 1).into())
|
||||||
|
.build();
|
||||||
|
let capsfilter = gst::ElementFactory::make("capsfilter")
|
||||||
|
.property("caps", &caps)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
|
||||||
|
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
||||||
|
|
||||||
|
pipeline.add_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
||||||
|
gst::Element::link_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
||||||
|
|
||||||
|
// Most Direct2D/DirectWrite APIs (including factory methods) are marked as
|
||||||
|
// "unsafe", but they shouldn't fail in practice
|
||||||
|
let drawer = unsafe {
|
||||||
|
let d2d_factory =
|
||||||
|
D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None).unwrap();
|
||||||
|
let dwrite_factory =
|
||||||
|
DWriteCreateFactory::<IDWriteFactory>(DWRITE_FACTORY_TYPE_SHARED).unwrap();
|
||||||
|
let text_format = dwrite_factory
|
||||||
|
.CreateTextFormat(
|
||||||
|
windows::core::w!("Arial"),
|
||||||
|
None,
|
||||||
|
DWRITE_FONT_WEIGHT_BOLD,
|
||||||
|
DWRITE_FONT_STYLE_NORMAL,
|
||||||
|
DWRITE_FONT_STRETCH_NORMAL,
|
||||||
|
32f32,
|
||||||
|
windows::core::w!("en-us"),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let text_layout = dwrite_factory
|
||||||
|
.CreateTextLayout(
|
||||||
|
windows::core::w!("GStreamer").as_wide(),
|
||||||
|
&text_format,
|
||||||
|
// Size will be updated later on "caps-changed" signal
|
||||||
|
800f32,
|
||||||
|
800f32,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Top (default) and center alignment
|
||||||
|
text_layout
|
||||||
|
.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let wic_factory: IWICImagingFactory =
|
||||||
|
CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_ALL).unwrap();
|
||||||
|
|
||||||
|
Arc::new(Mutex::new(DrawingContext {
|
||||||
|
d2d_factory,
|
||||||
|
wic_factory,
|
||||||
|
text_layout,
|
||||||
|
bitmap: None,
|
||||||
|
render_target: None,
|
||||||
|
info: None,
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
overlay.connect_closure(
|
||||||
|
"draw",
|
||||||
|
false,
|
||||||
|
glib::closure!(
|
||||||
|
#[strong]
|
||||||
|
drawer,
|
||||||
|
move |_overlay: &gst::Element, sample: &gst::Sample| {
|
||||||
|
use std::f64::consts::PI;
|
||||||
|
|
||||||
|
let drawer = drawer.lock().unwrap();
|
||||||
|
|
||||||
|
let buffer = sample.buffer().unwrap();
|
||||||
|
let timestamp = buffer.pts().unwrap();
|
||||||
|
|
||||||
|
let info = drawer.info.as_ref().unwrap();
|
||||||
|
let text_layout = &drawer.text_layout;
|
||||||
|
let bitmap = drawer.bitmap.as_ref().unwrap();
|
||||||
|
let render_target = drawer.render_target.as_ref().unwrap();
|
||||||
|
|
||||||
|
let global_angle = 360.
|
||||||
|
* (timestamp % (10 * gst::ClockTime::SECOND)).nseconds() as f64
|
||||||
|
/ (10.0 * gst::ClockTime::SECOND.nseconds() as f64);
|
||||||
|
let center_x = (info.width() / 2) as f32;
|
||||||
|
let center_y = (info.height() / 2) as f32;
|
||||||
|
let top_margin = (info.height() / 20) as f32;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Begin drawing
|
||||||
|
render_target.BeginDraw();
|
||||||
|
|
||||||
|
// Clear background
|
||||||
|
render_target.Clear(Some(&D2D1_COLOR_F {
|
||||||
|
r: 0f32,
|
||||||
|
g: 0f32,
|
||||||
|
b: 0f32,
|
||||||
|
a: 0f32,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// This loop will render 10 times the string "GStreamer" in a circle
|
||||||
|
for i in 0..10 {
|
||||||
|
let angle = (360. * f64::from(i)) / 10.0;
|
||||||
|
let red = ((1.0 + f64::cos((angle - 60.0) * PI / 180.0)) / 2.0) as f32;
|
||||||
|
let text_brush = render_target
|
||||||
|
.CreateSolidColorBrush(
|
||||||
|
&D2D1_COLOR_F {
|
||||||
|
r: red,
|
||||||
|
g: 0f32,
|
||||||
|
b: 1f32 - red,
|
||||||
|
a: 1f32,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let angle = (angle + global_angle) as f32;
|
||||||
|
let matrix = Matrix3x2::rotation(angle, center_x, center_y);
|
||||||
|
render_target.SetTransform(&matrix);
|
||||||
|
render_target.DrawTextLayout(
|
||||||
|
D2D_POINT_2F {
|
||||||
|
x: 0f32,
|
||||||
|
y: top_margin,
|
||||||
|
},
|
||||||
|
text_layout,
|
||||||
|
&text_brush,
|
||||||
|
D2D1_DRAW_TEXT_OPTIONS_NONE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EndDraw may not be successful for some reasons.
|
||||||
|
// Ignores any error in this example
|
||||||
|
let _ = render_target.EndDraw(None, None);
|
||||||
|
|
||||||
|
// Make sure all operations is completed before copying
|
||||||
|
// bitmap to buffer
|
||||||
|
let _ = render_target.Flush(None::<*mut u64>, None::<*mut u64>);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buffer =
|
||||||
|
gst::Buffer::with_size((info.width() * info.height() * 4) as usize).unwrap();
|
||||||
|
{
|
||||||
|
let buffer_mut = buffer.get_mut().unwrap();
|
||||||
|
let mut map = buffer_mut.map_writable().unwrap();
|
||||||
|
let dst = map.as_mut_slice_of::<u8>().unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Bitmap size is equal to the background image size.
|
||||||
|
// Copy entire memory
|
||||||
|
bitmap
|
||||||
|
.CopyPixels(std::ptr::null(), info.width() * 4, dst)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_video::VideoMeta::add_full(
|
||||||
|
buffer.get_mut().unwrap(),
|
||||||
|
gst_video::VideoFrameFlags::empty(),
|
||||||
|
gst_video::VideoFormat::Bgra,
|
||||||
|
info.width(),
|
||||||
|
info.height(),
|
||||||
|
&[0],
|
||||||
|
&[(info.width() * 4) as i32],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Turn the buffer into a VideoOverlayRectangle, then place
|
||||||
|
// that into a VideoOverlayComposition and return it.
|
||||||
|
//
|
||||||
|
// A VideoOverlayComposition can take a Vec of such rectangles
|
||||||
|
// spaced around the video frame, but we're just outputting 1
|
||||||
|
// here
|
||||||
|
let rect = gst_video::VideoOverlayRectangle::new_raw(
|
||||||
|
&buffer,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
info.width(),
|
||||||
|
info.height(),
|
||||||
|
gst_video::VideoOverlayFormatFlags::PREMULTIPLIED_ALPHA,
|
||||||
|
);
|
||||||
|
|
||||||
|
gst_video::VideoOverlayComposition::new(Some(&rect)).unwrap()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add a signal handler to the overlay's "caps-changed" signal. This could e.g.
|
||||||
|
// be called when the sink that we render to does not support resizing the image
|
||||||
|
// itself - but the user just changed the window-size. The element after the overlay
|
||||||
|
// will then change its caps and we use the notification about this change to
|
||||||
|
// resize our canvas's size.
|
||||||
|
// Another possibility for when this might happen is, when our video is a network
|
||||||
|
// stream that dynamically changes resolution when enough bandwidth is available.
|
||||||
|
overlay.connect_closure(
|
||||||
|
"caps-changed",
|
||||||
|
false,
|
||||||
|
glib::closure!(move |_overlay: &gst::Element,
|
||||||
|
caps: &gst::Caps,
|
||||||
|
_width: u32,
|
||||||
|
_height: u32| {
|
||||||
|
let mut drawer = drawer.lock().unwrap();
|
||||||
|
let info = gst_video::VideoInfo::from_caps(caps).unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Update text layout to be identical to new video resolution
|
||||||
|
drawer.text_layout.SetMaxWidth(info.width() as f32).unwrap();
|
||||||
|
drawer
|
||||||
|
.text_layout
|
||||||
|
.SetMaxHeight(info.height() as f32)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Create new WIC bitmap with PBGRA format (pre-multiplied BGRA)
|
||||||
|
let bitmap = drawer
|
||||||
|
.wic_factory
|
||||||
|
.CreateBitmap(
|
||||||
|
info.width(),
|
||||||
|
info.height(),
|
||||||
|
&GUID_WICPixelFormat32bppPBGRA,
|
||||||
|
WICBitmapCacheOnDemand,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let render_target = drawer
|
||||||
|
.d2d_factory
|
||||||
|
.CreateWicBitmapRenderTarget(
|
||||||
|
&bitmap,
|
||||||
|
&D2D1_RENDER_TARGET_PROPERTIES {
|
||||||
|
r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,
|
||||||
|
pixelFormat: D2D1_PIXEL_FORMAT {
|
||||||
|
format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||||
|
alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
|
||||||
|
},
|
||||||
|
// zero means default DPI
|
||||||
|
dpiX: 0f32,
|
||||||
|
dpiY: 0f32,
|
||||||
|
usage: D2D1_RENDER_TARGET_USAGE_NONE,
|
||||||
|
minLevel: D2D1_FEATURE_LEVEL_DEFAULT,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
drawer.render_target = Some(render_target);
|
||||||
|
drawer.bitmap = Some(bitmap);
|
||||||
|
}
|
||||||
|
drawer.info = Some(info);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(pipeline)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main_loop(pipeline: gst::Pipeline) -> Result<(), Error> {
|
||||||
|
pipeline.set_state(gst::State::Playing)?;
|
||||||
|
|
||||||
|
let bus = pipeline
|
||||||
|
.bus()
|
||||||
|
.expect("Pipeline without bus. Shouldn't happen!");
|
||||||
|
|
||||||
|
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
|
match msg.view() {
|
||||||
|
MessageView::Eos(..) => break,
|
||||||
|
MessageView::Error(err) => {
|
||||||
|
pipeline.set_state(gst::State::Null)?;
|
||||||
|
return Err(ErrorMessage {
|
||||||
|
src: msg
|
||||||
|
.src()
|
||||||
|
.map(|s| s.path_string())
|
||||||
|
.unwrap_or_else(|| glib::GString::from("UNKNOWN")),
|
||||||
|
error: err.error(),
|
||||||
|
debug: err.debug(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.set_state(gst::State::Null)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// WIC requires COM initialization
|
||||||
|
unsafe {
|
||||||
|
CoInitializeEx(None, COINIT_MULTITHREADED).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
match create_pipeline().and_then(main_loop) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => eprintln!("Error! {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
CoUninitialize();
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,8 +77,8 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
|
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
|
||||||
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
pipeline.add_many([&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
||||||
gst::Element::link_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
gst::Element::link_many([&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
||||||
|
|
||||||
// The PangoFontMap represents the set of fonts available for a particular rendering system.
|
// The PangoFontMap represents the set of fonts available for a particular rendering system.
|
||||||
let fontmap = pangocairo::FontMap::new();
|
let fontmap = pangocairo::FontMap::new();
|
||||||
|
@ -119,8 +119,9 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
overlay.connect_closure(
|
overlay.connect_closure(
|
||||||
"draw",
|
"draw",
|
||||||
false,
|
false,
|
||||||
glib::closure!(@strong drawer => move |_overlay: &gst::Element,
|
glib::closure!(
|
||||||
sample: &gst::Sample| {
|
#[strong] drawer,
|
||||||
|
move |_overlay: &gst::Element, sample: &gst::Sample| {
|
||||||
use std::f64::consts::PI;
|
use std::f64::consts::PI;
|
||||||
|
|
||||||
let drawer = drawer.lock().unwrap();
|
let drawer = drawer.lock().unwrap();
|
||||||
|
@ -150,7 +151,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
|
|
||||||
// The image we draw (the text) will be static, but we will change the
|
// The image we draw (the text) will be static, but we will change the
|
||||||
// transformation on the drawing context, which rotates and shifts everything
|
// transformation on the drawing context, which rotates and shifts everything
|
||||||
// that we draw afterwards. Like this, we have no complicated calulations
|
// that we draw afterwards. Like this, we have no complicated calculations
|
||||||
// in the actual drawing below.
|
// in the actual drawing below.
|
||||||
// Calling multiple transformation methods after each other will apply the
|
// Calling multiple transformation methods after each other will apply the
|
||||||
// new transformation on top. If you repeat the cr.rotate(angle) line below
|
// new transformation on top. If you repeat the cr.rotate(angle) line below
|
||||||
|
@ -179,7 +180,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
// to end up as a 200x100 rectangle would now be 100x200.
|
// to end up as a 200x100 rectangle would now be 100x200.
|
||||||
pangocairo::functions::update_layout(&cr, layout);
|
pangocairo::functions::update_layout(&cr, layout);
|
||||||
let (width, _height) = layout.size();
|
let (width, _height) = layout.size();
|
||||||
// Using width and height of the text, we can properly possition it within
|
// Using width and height of the text, we can properly position it within
|
||||||
// our canvas.
|
// our canvas.
|
||||||
cr.move_to(
|
cr.move_to(
|
||||||
-(f64::from(width) / f64::from(pango::SCALE)) / 2.0,
|
-(f64::from(width) / f64::from(pango::SCALE)) / 2.0,
|
||||||
|
@ -241,7 +242,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
// will then change its caps and we use the notification about this change to
|
// will then change its caps and we use the notification about this change to
|
||||||
// resize our canvas's size.
|
// resize our canvas's size.
|
||||||
// Another possibility for when this might happen is, when our video is a network
|
// Another possibility for when this might happen is, when our video is a network
|
||||||
// stream that dynamically changes resolution when enough bandwith is available.
|
// stream that dynamically changes resolution when enough bandwidth is available.
|
||||||
overlay.connect_closure(
|
overlay.connect_closure(
|
||||||
"caps-changed",
|
"caps-changed",
|
||||||
false,
|
false,
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
// {audiotestsrc} - {fakesink}
|
// {audiotestsrc} - {fakesink}
|
||||||
#![allow(clippy::question_mark)]
|
#![allow(clippy::question_mark)]
|
||||||
|
|
||||||
use std::i16;
|
|
||||||
|
|
||||||
use byte_slice_cast::*;
|
use byte_slice_cast::*;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
@ -22,7 +20,7 @@ fn example_main() {
|
||||||
// Parse the pipeline we want to probe from a static in-line string.
|
// Parse the pipeline we want to probe from a static in-line string.
|
||||||
// Here we give our audiotestsrc a name, so we can retrieve that element
|
// Here we give our audiotestsrc a name, so we can retrieve that element
|
||||||
// from the resulting pipeline.
|
// from the resulting pipeline.
|
||||||
let pipeline = gst::parse_launch(&format!(
|
let pipeline = gst::parse::launch(&format!(
|
||||||
"audiotestsrc name=src ! audio/x-raw,format={},channels=1 ! fakesink",
|
"audiotestsrc name=src ! audio/x-raw,format={},channels=1 ! fakesink",
|
||||||
gst_audio::AUDIO_FORMAT_S16
|
gst_audio::AUDIO_FORMAT_S16
|
||||||
))
|
))
|
||||||
|
@ -38,36 +36,38 @@ fn example_main() {
|
||||||
// This handler gets called for every buffer that passes the pad we probe.
|
// This handler gets called for every buffer that passes the pad we probe.
|
||||||
src_pad.add_probe(gst::PadProbeType::BUFFER, |_, probe_info| {
|
src_pad.add_probe(gst::PadProbeType::BUFFER, |_, probe_info| {
|
||||||
// Interpret the data sent over the pad as one buffer
|
// Interpret the data sent over the pad as one buffer
|
||||||
if let Some(gst::PadProbeData::Buffer(ref buffer)) = probe_info.data {
|
let Some(buffer) = probe_info.buffer() else {
|
||||||
// At this point, buffer is only a reference to an existing memory region somewhere.
|
return gst::PadProbeReturn::Ok;
|
||||||
// When we want to access its content, we have to map it while requesting the required
|
};
|
||||||
// mode of access (read, read/write).
|
|
||||||
// This type of abstraction is necessary, because the buffer in question might not be
|
|
||||||
// on the machine's main memory itself, but rather in the GPU's memory.
|
|
||||||
// So mapping the buffer makes the underlying memory region accessible to us.
|
|
||||||
// See: https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html
|
|
||||||
let map = buffer.map_readable().unwrap();
|
|
||||||
|
|
||||||
// We know what format the data in the memory region has, since we requested
|
// At this point, buffer is only a reference to an existing memory region somewhere.
|
||||||
// it by setting the appsink's caps. So what we do here is interpret the
|
// When we want to access its content, we have to map it while requesting the required
|
||||||
// memory region we mapped as an array of signed 16 bit integers.
|
// mode of access (read, read/write).
|
||||||
let samples = if let Ok(samples) = map.as_slice_of::<i16>() {
|
// This type of abstraction is necessary, because the buffer in question might not be
|
||||||
samples
|
// on the machine's main memory itself, but rather in the GPU's memory.
|
||||||
} else {
|
// So mapping the buffer makes the underlying memory region accessible to us.
|
||||||
return gst::PadProbeReturn::Ok;
|
// See: https://gstreamer.freedesktop.org/documentation/plugin-development/advanced/allocation.html
|
||||||
};
|
let map = buffer.map_readable().unwrap();
|
||||||
|
|
||||||
// For buffer (= chunk of samples), we calculate the root mean square:
|
// We know what format the data in the memory region has, since we requested
|
||||||
let sum: f64 = samples
|
// it by setting the appsink's caps. So what we do here is interpret the
|
||||||
.iter()
|
// memory region we mapped as an array of signed 16 bit integers.
|
||||||
.map(|sample| {
|
let samples = if let Ok(samples) = map.as_slice_of::<i16>() {
|
||||||
let f = f64::from(*sample) / f64::from(i16::MAX);
|
samples
|
||||||
f * f
|
} else {
|
||||||
})
|
return gst::PadProbeReturn::Ok;
|
||||||
.sum();
|
};
|
||||||
let rms = (sum / (samples.len() as f64)).sqrt();
|
|
||||||
println!("rms: {rms}");
|
// For buffer (= chunk of samples), we calculate the root mean square:
|
||||||
}
|
let sum: f64 = samples
|
||||||
|
.iter()
|
||||||
|
.map(|sample| {
|
||||||
|
let f = f64::from(*sample) / f64::from(i16::MAX);
|
||||||
|
f * f
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
let rms = (sum / (samples.len() as f64)).sqrt();
|
||||||
|
println!("rms: {rms}");
|
||||||
|
|
||||||
gst::PadProbeReturn::Ok
|
gst::PadProbeReturn::Ok
|
||||||
});
|
});
|
||||||
|
|
|
@ -76,8 +76,8 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
|
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
|
||||||
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
let sink = gst::ElementFactory::make("autovideosink").build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
pipeline.add_many([&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
||||||
gst::Element::link_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
gst::Element::link_many([&src, &overlay, &capsfilter, &videoconvert, &sink])?;
|
||||||
|
|
||||||
// The PangoFontMap represents the set of fonts available for a particular rendering system.
|
// The PangoFontMap represents the set of fonts available for a particular rendering system.
|
||||||
let fontmap = pangocairo::FontMap::new();
|
let fontmap = pangocairo::FontMap::new();
|
||||||
|
@ -134,7 +134,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
|
|
||||||
// The image we draw (the text) will be static, but we will change the
|
// The image we draw (the text) will be static, but we will change the
|
||||||
// transformation on the drawing context, which rotates and shifts everything
|
// transformation on the drawing context, which rotates and shifts everything
|
||||||
// that we draw afterwards. Like this, we have no complicated calulations
|
// that we draw afterwards. Like this, we have no complicated calculations
|
||||||
// in the actual drawing below.
|
// in the actual drawing below.
|
||||||
// Calling multiple transformation methods after each other will apply the
|
// Calling multiple transformation methods after each other will apply the
|
||||||
// new transformation on top. If you repeat the cr.rotate(angle) line below
|
// new transformation on top. If you repeat the cr.rotate(angle) line below
|
||||||
|
@ -163,7 +163,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
// to end up as a 200x100 rectangle would now be 100x200.
|
// to end up as a 200x100 rectangle would now be 100x200.
|
||||||
pangocairo::functions::update_layout(&cr, layout);
|
pangocairo::functions::update_layout(&cr, layout);
|
||||||
let (width, _height) = layout.size();
|
let (width, _height) = layout.size();
|
||||||
// Using width and height of the text, we can properly possition it within
|
// Using width and height of the text, we can properly position it within
|
||||||
// our canvas.
|
// our canvas.
|
||||||
cr.move_to(
|
cr.move_to(
|
||||||
-(f64::from(width) / f64::from(pango::SCALE)) / 2.0,
|
-(f64::from(width) / f64::from(pango::SCALE)) / 2.0,
|
||||||
|
@ -187,7 +187,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
// will then change its caps and we use the notification about this change to
|
// will then change its caps and we use the notification about this change to
|
||||||
// resize our canvas's size.
|
// resize our canvas's size.
|
||||||
// Another possibility for when this might happen is, when our video is a network
|
// Another possibility for when this might happen is, when our video is a network
|
||||||
// stream that dynamically changes resolution when enough bandwith is available.
|
// stream that dynamically changes resolution when enough bandwidth is available.
|
||||||
overlay.connect("caps-changed", false, move |args| {
|
overlay.connect("caps-changed", false, move |args| {
|
||||||
let _overlay = args[0].get::<gst::Element>().unwrap();
|
let _overlay = args[0].get::<gst::Element>().unwrap();
|
||||||
let caps = args[1].get::<gst::Caps>().unwrap();
|
let caps = args[1].get::<gst::Caps>().unwrap();
|
||||||
|
|
|
@ -37,6 +37,11 @@ fn main_loop(uri: &str) -> Result<(), Error> {
|
||||||
Err(_) => unreachable!(),
|
Err(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the message bus to flushing to ensure that all pending messages are dropped and there
|
||||||
|
// are no further references to the play instance.
|
||||||
|
play.message_bus().set_flushing(true);
|
||||||
|
|
||||||
result.map_err(|e| e.into())
|
result.map_err(|e| e.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,14 +36,14 @@ fn example_main() {
|
||||||
// For flags handling
|
// For flags handling
|
||||||
// With flags, one can configure playbin's behavior such as whether it
|
// With flags, one can configure playbin's behavior such as whether it
|
||||||
// should play back contained video streams, or if it should render subtitles.
|
// should play back contained video streams, or if it should render subtitles.
|
||||||
// let flags = playbin.get_property("flags").unwrap();
|
// let flags = playbin.property_value("flags");
|
||||||
// let flags_class = FlagsClass::new(flags.type_()).unwrap();
|
// let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
|
||||||
// let flags = flags_class.builder_with_value(flags).unwrap()
|
// let flags = flags_class.builder_with_value(flags).unwrap()
|
||||||
// .unset_by_nick("text")
|
// .unset_by_nick("text")
|
||||||
// .unset_by_nick("video")
|
// .unset_by_nick("video")
|
||||||
// .build()
|
// .build()
|
||||||
// .unwrap();
|
// .unwrap();
|
||||||
// playbin.set_property_from_value("flags", &flags).unwrap();
|
// playbin.set_property_from_value("flags", &flags);
|
||||||
|
|
||||||
// The playbin also provides any kind of metadata that it found in the played stream.
|
// The playbin also provides any kind of metadata that it found in the played stream.
|
||||||
// For this, the playbin provides signals notifying about changes in the metadata.
|
// For this, the playbin provides signals notifying about changes in the metadata.
|
||||||
|
@ -59,7 +59,7 @@ fn example_main() {
|
||||||
let playbin = values[0]
|
let playbin = values[0]
|
||||||
.get::<glib::Object>()
|
.get::<glib::Object>()
|
||||||
.expect("playbin \"audio-tags-changed\" signal values[1]");
|
.expect("playbin \"audio-tags-changed\" signal values[1]");
|
||||||
// This gets the index of the stream that changed. This is neccessary, since
|
// This gets the index of the stream that changed. This is necessary, since
|
||||||
// there could e.g. be multiple audio streams (english, spanish, ...).
|
// there could e.g. be multiple audio streams (english, spanish, ...).
|
||||||
let idx = values[1]
|
let idx = values[1]
|
||||||
.get::<i32>()
|
.get::<i32>()
|
||||||
|
|
|
@ -28,7 +28,7 @@ fn example_main() {
|
||||||
let main_loop = glib::MainLoop::new(None, false);
|
let main_loop = glib::MainLoop::new(None, false);
|
||||||
|
|
||||||
// Let GStreamer create a pipeline from the parsed launch syntax on the cli.
|
// Let GStreamer create a pipeline from the parsed launch syntax on the cli.
|
||||||
let pipeline = gst::parse_launch(&pipeline_str).unwrap();
|
let pipeline = gst::parse::launch(&pipeline_str).unwrap();
|
||||||
let bus = pipeline.bus().unwrap();
|
let bus = pipeline.bus().unwrap();
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
|
@ -50,9 +50,8 @@ fn example_main() {
|
||||||
let timeout_id = glib::timeout_add_seconds(1, move || {
|
let timeout_id = glib::timeout_add_seconds(1, move || {
|
||||||
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
||||||
// we moved into this callback.
|
// we moved into this callback.
|
||||||
let pipeline = match pipeline_weak.upgrade() {
|
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||||
Some(pipeline) => pipeline,
|
return glib::ControlFlow::Break;
|
||||||
None => return glib::Continue(true),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//let pos = pipeline.query_position(gst::Format::Time).unwrap_or(-1);
|
//let pos = pipeline.query_position(gst::Format::Time).unwrap_or(-1);
|
||||||
|
@ -87,34 +86,35 @@ fn example_main() {
|
||||||
|
|
||||||
println!("{} / {}", pos.display(), dur.display());
|
println!("{} / {}", pos.display(), dur.display());
|
||||||
|
|
||||||
glib::Continue(true)
|
glib::ControlFlow::Continue
|
||||||
});
|
});
|
||||||
|
|
||||||
// Need to move a new reference into the closure.
|
// Need to move a new reference into the closure.
|
||||||
let main_loop_clone = main_loop.clone();
|
let main_loop_clone = main_loop.clone();
|
||||||
//bus.add_signal_watch();
|
//bus.add_signal_watch();
|
||||||
//bus.connect_message(None, move |_, msg| {
|
//bus.connect_message(None, move |_, msg| {
|
||||||
bus.add_watch(move |_, msg| {
|
let _bus_watch = bus
|
||||||
use gst::MessageView;
|
.add_watch(move |_, msg| {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
let main_loop = &main_loop_clone;
|
let main_loop = &main_loop_clone;
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::Eos(..) => main_loop.quit(),
|
MessageView::Eos(..) => main_loop.quit(),
|
||||||
MessageView::Error(err) => {
|
MessageView::Error(err) => {
|
||||||
println!(
|
println!(
|
||||||
"Error from {:?}: {} ({:?})",
|
"Error from {:?}: {} ({:?})",
|
||||||
err.src().map(|s| s.path_string()),
|
err.src().map(|s| s.path_string()),
|
||||||
err.error(),
|
err.error(),
|
||||||
err.debug()
|
err.debug()
|
||||||
);
|
);
|
||||||
main_loop.quit();
|
main_loop.quit();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
glib::Continue(true)
|
glib::ControlFlow::Continue
|
||||||
})
|
})
|
||||||
.expect("Failed to add bus watch");
|
.expect("Failed to add bus watch");
|
||||||
|
|
||||||
main_loop.run();
|
main_loop.run();
|
||||||
|
|
||||||
|
@ -122,7 +122,6 @@ fn example_main() {
|
||||||
.set_state(gst::State::Null)
|
.set_state(gst::State::Null)
|
||||||
.expect("Unable to set the pipeline to the `Null` state");
|
.expect("Unable to set the pipeline to the `Null` state");
|
||||||
|
|
||||||
bus.remove_watch().unwrap();
|
|
||||||
timeout_id.remove();
|
timeout_id.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -114,8 +114,8 @@ fn example_main() -> Result<(), Error> {
|
||||||
.property("caps", &video_caps)
|
.property("caps", &video_caps)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &netsim, &rtpbin, &depay, &dec, &conv, &scale, &filter])?;
|
pipeline.add_many([&src, &netsim, &rtpbin, &depay, &dec, &conv, &scale, &filter])?;
|
||||||
gst::Element::link_many(&[&depay, &dec, &conv, &scale, &filter])?;
|
gst::Element::link_many([&depay, &dec, &conv, &scale, &filter])?;
|
||||||
|
|
||||||
match args[1].as_str() {
|
match args[1].as_str() {
|
||||||
"play" => {
|
"play" => {
|
||||||
|
@ -132,8 +132,8 @@ fn example_main() -> Result<(), Error> {
|
||||||
.property("location", "out.mkv")
|
.property("location", "out.mkv")
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&enc, &mux, &sink])?;
|
pipeline.add_many([&enc, &mux, &sink])?;
|
||||||
gst::Element::link_many(&[&filter, &enc, &mux, &sink])?;
|
gst::Element::link_many([&filter, &enc, &mux, &sink])?;
|
||||||
eprintln!("Recording to out.mkv");
|
eprintln!("Recording to out.mkv");
|
||||||
}
|
}
|
||||||
_ => return Err(Error::from(UsageError(args[0].clone()))),
|
_ => return Err(Error::from(UsageError(args[0].clone()))),
|
||||||
|
@ -203,9 +203,8 @@ fn example_main() -> Result<(), Error> {
|
||||||
|
|
||||||
let depay_weak = depay.downgrade();
|
let depay_weak = depay.downgrade();
|
||||||
rtpbin.connect_pad_added(move |rtpbin, src_pad| {
|
rtpbin.connect_pad_added(move |rtpbin, src_pad| {
|
||||||
let depay = match depay_weak.upgrade() {
|
let Some(depay) = depay_weak.upgrade() else {
|
||||||
Some(depay) => depay,
|
return;
|
||||||
None => return,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match connect_rtpbin_srcpad(src_pad, &depay) {
|
match connect_rtpbin_srcpad(src_pad, &depay) {
|
||||||
|
@ -253,11 +252,7 @@ fn example_main() -> Result<(), Error> {
|
||||||
if let Some(element) = msg.src() {
|
if let Some(element) = msg.src() {
|
||||||
if element == &pipeline && s.current() == gst::State::Playing {
|
if element == &pipeline && s.current() == gst::State::Playing {
|
||||||
eprintln!("PLAYING");
|
eprintln!("PLAYING");
|
||||||
gst::debug_bin_to_dot_file(
|
pipeline.debug_to_dot_file(gst::DebugGraphDetails::all(), "client-playing");
|
||||||
&pipeline,
|
|
||||||
gst::DebugGraphDetails::all(),
|
|
||||||
"client-playing",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ fn example_main() -> Result<(), Error> {
|
||||||
.property("sync", true)
|
.property("sync", true)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &conv, &q1, &enc, &q2, &pay, &rtpbin, &sink])?;
|
pipeline.add_many([&src, &conv, &q1, &enc, &q2, &pay, &rtpbin, &sink])?;
|
||||||
|
|
||||||
conv.link(&q1)?;
|
conv.link(&q1)?;
|
||||||
q1.link(&enc)?;
|
q1.link(&enc)?;
|
||||||
|
@ -179,11 +179,7 @@ fn example_main() -> Result<(), Error> {
|
||||||
if let Some(element) = msg.src() {
|
if let Some(element) = msg.src() {
|
||||||
if element == &pipeline && s.current() == gst::State::Playing {
|
if element == &pipeline && s.current() == gst::State::Playing {
|
||||||
eprintln!("PLAYING");
|
eprintln!("PLAYING");
|
||||||
gst::debug_bin_to_dot_file(
|
pipeline.debug_to_dot_file(gst::DebugGraphDetails::all(), "server-playing");
|
||||||
&pipeline,
|
|
||||||
gst::DebugGraphDetails::all(),
|
|
||||||
"server-playing",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
223
examples/src/bin/rtsp-server-custom-auth.rs
Normal file
223
examples/src/bin/rtsp-server-custom-auth.rs
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
// This example demonstrates how to set up a rtsp server using GStreamer
|
||||||
|
// and extending the default auth module behaviour by subclassing RTSPAuth
|
||||||
|
// For this, the example creates a videotestsrc pipeline manually to be used
|
||||||
|
// by the RTSP server for providing data
|
||||||
|
#![allow(clippy::non_send_fields_in_send_ty)]
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use derive_more::{Display, Error};
|
||||||
|
use gst_rtsp_server::prelude::*;
|
||||||
|
|
||||||
|
#[path = "../examples-common.rs"]
|
||||||
|
mod examples_common;
|
||||||
|
|
||||||
|
#[derive(Debug, Display, Error)]
|
||||||
|
#[display(fmt = "Could not get mount points")]
|
||||||
|
struct NoMountPoints;
|
||||||
|
|
||||||
|
fn main_loop() -> Result<(), Error> {
|
||||||
|
let main_loop = glib::MainLoop::new(None, false);
|
||||||
|
let server = gst_rtsp_server::RTSPServer::new();
|
||||||
|
|
||||||
|
// We create our custom auth module.
|
||||||
|
// The job of the auth module is to authenticate users and authorize
|
||||||
|
// factories access/construction.
|
||||||
|
let auth = auth::Auth::default();
|
||||||
|
server.set_auth(Some(&auth));
|
||||||
|
|
||||||
|
// Much like HTTP servers, RTSP servers have multiple endpoints that
|
||||||
|
// provide different streams. Here, we ask our server to give
|
||||||
|
// us a reference to his list of endpoints, so we can add our
|
||||||
|
// test endpoint, providing the pipeline from the cli.
|
||||||
|
let mounts = server.mount_points().ok_or(NoMountPoints)?;
|
||||||
|
|
||||||
|
// Next, we create a factory for the endpoint we want to create.
|
||||||
|
// The job of the factory is to create a new pipeline for each client that
|
||||||
|
// connects, or (if configured to do so) to reuse an existing pipeline.
|
||||||
|
let factory = gst_rtsp_server::RTSPMediaFactory::new();
|
||||||
|
// Here we tell the media factory the media we want to serve.
|
||||||
|
// This is done in the launch syntax. When the first client connects,
|
||||||
|
// the factory will use this syntax to create a new pipeline instance.
|
||||||
|
factory.set_launch("( videotestsrc ! vp8enc ! rtpvp8pay name=pay0 )");
|
||||||
|
// This setting specifies whether each connecting client gets the output
|
||||||
|
// of a new instance of the pipeline, or whether all connected clients share
|
||||||
|
// the output of the same pipeline.
|
||||||
|
// If you want to stream a fixed video you have stored on the server to any
|
||||||
|
// client, you would not set this to shared here (since every client wants
|
||||||
|
// to start at the beginning of the video). But if you want to distribute
|
||||||
|
// a live source, you will probably want to set this to shared, to save
|
||||||
|
// computing and memory capacity on the server.
|
||||||
|
factory.set_shared(true);
|
||||||
|
|
||||||
|
// Now we add a new mount-point and tell the RTSP server to serve the content
|
||||||
|
// provided by the factory we configured above, when a client connects to
|
||||||
|
// this specific path.
|
||||||
|
mounts.add_factory("/test", factory);
|
||||||
|
|
||||||
|
// Attach the server to our main context.
|
||||||
|
// A main context is the thing where other stuff is registering itself for its
|
||||||
|
// events (e.g. sockets, GStreamer bus, ...) and the main loop is something that
|
||||||
|
// polls the main context for its events and dispatches them to whoever is
|
||||||
|
// interested in them. In this example, we only do have one, so we can
|
||||||
|
// leave the context parameter empty, it will automatically select
|
||||||
|
// the default one.
|
||||||
|
let id = server.attach(None)?;
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Stream ready at rtsp://127.0.0.1:{}/test",
|
||||||
|
server.bound_port()
|
||||||
|
);
|
||||||
|
println!("user admin/password can access stream");
|
||||||
|
println!("user demo/demo passes authentication but receives 404");
|
||||||
|
println!("other users do not pass pass authentication and receive 401");
|
||||||
|
|
||||||
|
// Start the mainloop. From this point on, the server will start to serve
|
||||||
|
// our quality content to connecting clients.
|
||||||
|
main_loop.run();
|
||||||
|
|
||||||
|
id.remove();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our custom auth module
|
||||||
|
mod auth {
|
||||||
|
// In the imp submodule we include the actual implementation
|
||||||
|
mod imp {
|
||||||
|
use gst_rtsp::{RTSPHeaderField, RTSPStatusCode};
|
||||||
|
use gst_rtsp_server::{prelude::*, subclass::prelude::*, RTSPContext};
|
||||||
|
|
||||||
|
// This is the private data of our auth
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Auth;
|
||||||
|
|
||||||
|
impl Auth {
|
||||||
|
// Simulate external auth validation and user extraction
|
||||||
|
// authorized users are admin/password and demo/demo
|
||||||
|
fn external_auth(&self, auth: &str) -> Option<String> {
|
||||||
|
if let Ok(decoded) = data_encoding::BASE64.decode(auth.as_bytes()) {
|
||||||
|
if let Ok(decoded) = std::str::from_utf8(&decoded) {
|
||||||
|
let tokens = decoded.split(':').collect::<Vec<_>>();
|
||||||
|
if tokens == vec!["admin", "password"] || tokens == vec!["demo", "demo"] {
|
||||||
|
return Some(tokens[0].into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate external role check
|
||||||
|
// admin user can construct and access media factory
|
||||||
|
fn external_access_check(&self, user: &str) -> bool {
|
||||||
|
user == "admin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This trait registers our type with the GObject object system and
|
||||||
|
// provides the entry points for creating a new instance and setting
|
||||||
|
// up the class data
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for Auth {
|
||||||
|
const NAME: &'static str = "RsRTSPAuth";
|
||||||
|
type Type = super::Auth;
|
||||||
|
type ParentType = gst_rtsp_server::RTSPAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of glib::Object virtual methods
|
||||||
|
impl ObjectImpl for Auth {}
|
||||||
|
|
||||||
|
// Implementation of gst_rtsp_server::RTSPAuth virtual methods
|
||||||
|
impl RTSPAuthImpl for Auth {
|
||||||
|
fn authenticate(&self, ctx: &RTSPContext) -> bool {
|
||||||
|
// authenticate should always be called with a valid context request
|
||||||
|
let req = ctx
|
||||||
|
.request()
|
||||||
|
.expect("Context without request. Should not happen !");
|
||||||
|
|
||||||
|
if let Some(auth_credentials) = req.parse_auth_credentials().first() {
|
||||||
|
if let Some(authorization) = auth_credentials.authorization() {
|
||||||
|
if let Some(user) = self.external_auth(authorization) {
|
||||||
|
// Update context token with authenticated username
|
||||||
|
ctx.set_token(
|
||||||
|
gst_rtsp_server::RTSPToken::builder()
|
||||||
|
.field("user", user)
|
||||||
|
.build(),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self, ctx: &RTSPContext, role: &glib::GString) -> bool {
|
||||||
|
// We only check media factory access
|
||||||
|
if !role.starts_with("auth.check.media.factory") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.token().is_none() {
|
||||||
|
// If we do not have a context token yet, check if there are any auth credentials in request
|
||||||
|
if !self.authenticate(ctx) {
|
||||||
|
// If there were no credentials, send a "401 Unauthorized" response
|
||||||
|
if let Some(resp) = ctx.response() {
|
||||||
|
resp.init_response(RTSPStatusCode::Unauthorized, ctx.request());
|
||||||
|
resp.add_header(
|
||||||
|
RTSPHeaderField::WwwAuthenticate,
|
||||||
|
"Basic realm=\"CustomRealm\"",
|
||||||
|
);
|
||||||
|
if let Some(client) = ctx.client() {
|
||||||
|
client.send_message(resp, ctx.session());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(token) = ctx.token() {
|
||||||
|
// If we already have a user token...
|
||||||
|
if self.external_access_check(&token.string("user").unwrap_or_default()) {
|
||||||
|
// grant access if user may access factory
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// send a "404 Not Found" response if user may not access factory
|
||||||
|
if let Some(resp) = ctx.response() {
|
||||||
|
resp.init_response(RTSPStatusCode::NotFound, ctx.request());
|
||||||
|
if let Some(client) = ctx.client() {
|
||||||
|
client.send_message(resp, ctx.session());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This here defines the public interface of our auth and implements
|
||||||
|
// the corresponding traits so that it behaves like any other RTSPAuth
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct Auth(ObjectSubclass<imp::Auth>) @extends gst_rtsp_server::RTSPAuth;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Auth {
|
||||||
|
// Creates a new instance of our auth
|
||||||
|
fn default() -> Self {
|
||||||
|
glib::Object::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn example_main() -> Result<(), Error> {
|
||||||
|
gst::init()?;
|
||||||
|
main_loop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
match examples_common::run(example_main) {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => eprintln!("Error! {e}"),
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,11 +4,10 @@
|
||||||
// send to the server. For this, the launch syntax pipeline, that is passed
|
// send to the server. For this, the launch syntax pipeline, that is passed
|
||||||
// to this example's cli is spawned and the client's media is streamed into it.
|
// to this example's cli is spawned and the client's media is streamed into it.
|
||||||
|
|
||||||
use std::{env, ptr};
|
use std::env;
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
use glib::translate::*;
|
|
||||||
use gst_rtsp_server::prelude::*;
|
use gst_rtsp_server::prelude::*;
|
||||||
|
|
||||||
#[path = "../examples-common.rs"]
|
#[path = "../examples-common.rs"]
|
||||||
|
@ -45,12 +44,11 @@ fn main_loop() -> Result<(), Error> {
|
||||||
// Here we configure a method of authentication that we want the
|
// Here we configure a method of authentication that we want the
|
||||||
// server to require from clients.
|
// server to require from clients.
|
||||||
let auth = gst_rtsp_server::RTSPAuth::new();
|
let auth = gst_rtsp_server::RTSPAuth::new();
|
||||||
let token = gst_rtsp_server::RTSPToken::new(&[(
|
let token = gst_rtsp_server::RTSPToken::builder()
|
||||||
gst_rtsp_server::RTSP_TOKEN_MEDIA_FACTORY_ROLE,
|
.field(gst_rtsp_server::RTSP_TOKEN_MEDIA_FACTORY_ROLE, "user")
|
||||||
&"user",
|
.build();
|
||||||
)]);
|
|
||||||
let basic = gst_rtsp_server::RTSPAuth::make_basic("user", "password");
|
let basic = gst_rtsp_server::RTSPAuth::make_basic("user", "password");
|
||||||
// For propery authentication, we want to use encryption. And there's no
|
// For proper authentication, we want to use encryption. And there's no
|
||||||
// encryption without a certificate!
|
// encryption without a certificate!
|
||||||
let cert = gio::TlsCertificate::from_pem(
|
let cert = gio::TlsCertificate::from_pem(
|
||||||
"-----BEGIN CERTIFICATE-----\
|
"-----BEGIN CERTIFICATE-----\
|
||||||
|
@ -78,24 +76,14 @@ fn main_loop() -> Result<(), Error> {
|
||||||
W535W8UBbEg=-----END PRIVATE KEY-----",
|
W535W8UBbEg=-----END PRIVATE KEY-----",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Bindable versions were added in b1f515178a363df0322d7adbd5754e1f6e2083c9
|
|
||||||
// This declares that the user "user" (once authenticated) has a role that
|
// This declares that the user "user" (once authenticated) has a role that
|
||||||
// allows them to access and construct media factories.
|
// allows them to access and construct media factories.
|
||||||
unsafe {
|
factory.add_role_from_structure(
|
||||||
gst_rtsp_server::ffi::gst_rtsp_media_factory_add_role(
|
&gst::Structure::builder("user")
|
||||||
factory.to_glib_none().0,
|
.field(gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_ACCESS, true)
|
||||||
"user".to_glib_none().0,
|
.field(gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, true)
|
||||||
gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_ACCESS
|
.build(),
|
||||||
.to_glib_none()
|
);
|
||||||
.0,
|
|
||||||
<bool as StaticType>::static_type().into_glib() as *const u8,
|
|
||||||
true.into_glib() as *const u8,
|
|
||||||
gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_CONSTRUCT.as_ptr() as *const u8,
|
|
||||||
<bool as StaticType>::static_type().into_glib() as *const u8,
|
|
||||||
true.into_glib() as *const u8,
|
|
||||||
ptr::null_mut::<u8>(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auth.set_tls_certificate(Some(&cert));
|
auth.set_tls_certificate(Some(&cert));
|
||||||
auth.add_basic(basic.as_str(), &token);
|
auth.add_basic(basic.as_str(), &token);
|
||||||
|
|
|
@ -19,10 +19,6 @@ mod examples_common;
|
||||||
#[display(fmt = "Could not get mount points")]
|
#[display(fmt = "Could not get mount points")]
|
||||||
struct NoMountPoints;
|
struct NoMountPoints;
|
||||||
|
|
||||||
#[derive(Debug, Display, Error)]
|
|
||||||
#[display(fmt = "Usage: {_0} LAUNCH_LINE")]
|
|
||||||
struct UsageError(#[error(not(source))] String);
|
|
||||||
|
|
||||||
fn main_loop() -> Result<(), Error> {
|
fn main_loop() -> Result<(), Error> {
|
||||||
let main_loop = glib::MainLoop::new(None, false);
|
let main_loop = glib::MainLoop::new(None, false);
|
||||||
let server = server::Server::default();
|
let server = server::Server::default();
|
||||||
|
@ -137,8 +133,8 @@ mod media_factory {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
bin.add_many(&[&src, &enc, &pay]).unwrap();
|
bin.add_many([&src, &enc, &pay]).unwrap();
|
||||||
gst::Element::link_many(&[&src, &enc, &pay]).unwrap();
|
gst::Element::link_many([&src, &enc, &pay]).unwrap();
|
||||||
|
|
||||||
Some(bin.upcast())
|
Some(bin.upcast())
|
||||||
}
|
}
|
||||||
|
@ -303,6 +299,12 @@ mod client {
|
||||||
self.parent_closed();
|
self.parent_closed();
|
||||||
println!("Client {client:?} closed");
|
println!("Client {client:?} closed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn describe_request(&self, ctx: &gst_rtsp_server::RTSPContext) {
|
||||||
|
self.parent_describe_request(ctx);
|
||||||
|
let request_uri = ctx.uri().unwrap().request_uri();
|
||||||
|
println!("Describe request for uri: {request_uri:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,23 +60,27 @@ mod fir_filter {
|
||||||
// Implementation of gst::Element virtual methods
|
// Implementation of gst::Element virtual methods
|
||||||
impl ElementImpl for FirFilter {
|
impl ElementImpl for FirFilter {
|
||||||
// The element specific metadata. This information is what is visible from
|
// The element specific metadata. This information is what is visible from
|
||||||
// gst-inspect-1.0 and can also be programatically retrieved from the gst::Registry
|
// gst-inspect-1.0 and can also be programmatically retrieved from the gst::Registry
|
||||||
// after initial registration without having to load the plugin in memory.
|
// after initial registration without having to load the plugin in memory.
|
||||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
static ELEMENT_METADATA: std::sync::OnceLock<gst::subclass::ElementMetadata> =
|
||||||
|
std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
Some(ELEMENT_METADATA.get_or_init(|| {
|
||||||
gst::subclass::ElementMetadata::new(
|
gst::subclass::ElementMetadata::new(
|
||||||
"FIR Filter",
|
"FIR Filter",
|
||||||
"Filter/Effect/Audio",
|
"Filter/Effect/Audio",
|
||||||
"A FIR audio filter",
|
"A FIR audio filter",
|
||||||
"Sebastian Dröge <sebastian@centricular.com>",
|
"Sebastian Dröge <sebastian@centricular.com>",
|
||||||
)
|
)
|
||||||
});
|
}))
|
||||||
|
|
||||||
Some(&*ELEMENT_METADATA)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
static PAD_TEMPLATES: std::sync::OnceLock<Vec<gst::PadTemplate>> =
|
||||||
|
std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
PAD_TEMPLATES.get_or_init(|| {
|
||||||
// Create pad templates for our sink and source pad. These are later used for
|
// Create pad templates for our sink and source pad. These are later used for
|
||||||
// actually creating the pads and beforehand already provide information to
|
// actually creating the pads and beforehand already provide information to
|
||||||
// GStreamer about all possible pads that could exist for this type.
|
// GStreamer about all possible pads that could exist for this type.
|
||||||
|
@ -107,9 +111,7 @@ mod fir_filter {
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|
||||||
PAD_TEMPLATES.as_ref()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +142,7 @@ mod fir_filter {
|
||||||
// Drop state
|
// Drop state
|
||||||
self.history.lock().unwrap().clear();
|
self.history.lock().unwrap().clear();
|
||||||
|
|
||||||
gst::info!(CAT, imp: self, "Stopped");
|
gst::info!(CAT, imp = self, "Stopped");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -153,7 +155,7 @@ mod fir_filter {
|
||||||
// Get coefficients and return directly if we have none
|
// Get coefficients and return directly if we have none
|
||||||
let coeffs = self.coeffs.lock().unwrap();
|
let coeffs = self.coeffs.lock().unwrap();
|
||||||
if coeffs.is_empty() {
|
if coeffs.is_empty() {
|
||||||
gst::trace!(CAT, imp: self, "No coefficients set -- passthrough");
|
gst::trace!(CAT, imp = self, "No coefficients set -- passthrough");
|
||||||
return Ok(gst::FlowSuccess::Ok);
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +183,7 @@ mod fir_filter {
|
||||||
|
|
||||||
gst::trace!(
|
gst::trace!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp = self,
|
||||||
"Transforming {} samples with filter of length {}",
|
"Transforming {} samples with filter of length {}",
|
||||||
samples.len(),
|
samples.len(),
|
||||||
coeffs.len()
|
coeffs.len()
|
||||||
|
@ -248,7 +250,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
||||||
let conv = gst::ElementFactory::make("audioconvert").build()?;
|
let conv = gst::ElementFactory::make("audioconvert").build()?;
|
||||||
let sink = gst::ElementFactory::make("autoaudiosink").build()?;
|
let sink = gst::ElementFactory::make("autoaudiosink").build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&src, filter.upcast_ref(), &conv, &sink])?;
|
pipeline.add_many([&src, filter.upcast_ref(), &conv, &sink])?;
|
||||||
src.link(&filter)?;
|
src.link(&filter)?;
|
||||||
filter.link(&conv)?;
|
filter.link(&conv)?;
|
||||||
conv.link(&sink)?;
|
conv.link(&sink)?;
|
||||||
|
|
188
examples/src/bin/subclass_vfuncs/iirfilter/imp.rs
Normal file
188
examples/src/bin/subclass_vfuncs/iirfilter/imp.rs
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
// In the imp submodule we include the actual implementation
|
||||||
|
|
||||||
|
use std::{collections::VecDeque, sync::Mutex};
|
||||||
|
|
||||||
|
use glib::prelude::*;
|
||||||
|
use gst_audio::subclass::prelude::*;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use byte_slice_cast::*;
|
||||||
|
|
||||||
|
use atomic_refcell::AtomicRefCell;
|
||||||
|
|
||||||
|
// The debug category we use below for our filter
|
||||||
|
pub static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
|
gst::DebugCategory::new(
|
||||||
|
"rsiirfilter",
|
||||||
|
gst::DebugColorFlags::empty(),
|
||||||
|
Some("Rust IIR Filter"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
// This is the state of our filter
|
||||||
|
struct State {
|
||||||
|
a: Vec<f64>,
|
||||||
|
b: Vec<f64>,
|
||||||
|
x: VecDeque<f64>,
|
||||||
|
y: VecDeque<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the private data of our filter
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct IirFilter {
|
||||||
|
coeffs: Mutex<Option<(Vec<f64>, Vec<f64>)>>,
|
||||||
|
state: AtomicRefCell<State>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This trait registers our type with the GObject object system and
|
||||||
|
// provides the entry points for creating a new instance and setting
|
||||||
|
// up the class data
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for IirFilter {
|
||||||
|
const NAME: &'static str = "RsIirFilter";
|
||||||
|
const ABSTRACT: bool = true;
|
||||||
|
type Type = super::IirFilter;
|
||||||
|
type ParentType = gst_audio::AudioFilter;
|
||||||
|
type Class = super::Class;
|
||||||
|
|
||||||
|
// Here we set default implementations for all the virtual methods.
|
||||||
|
// This is mandatory for all virtual methods that are not `Option`s.
|
||||||
|
fn class_init(class: &mut Self::Class) {
|
||||||
|
class.set_rate = |obj, rate| obj.imp().set_rate_default(rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of glib::Object virtual methods
|
||||||
|
impl ObjectImpl for IirFilter {}
|
||||||
|
|
||||||
|
impl GstObjectImpl for IirFilter {}
|
||||||
|
|
||||||
|
// Implementation of gst::Element virtual methods
|
||||||
|
impl ElementImpl for IirFilter {}
|
||||||
|
|
||||||
|
// Implementation of gst_base::BaseTransform virtual methods
|
||||||
|
impl BaseTransformImpl for IirFilter {
|
||||||
|
// Configure basetransform so that we are always running in-place,
|
||||||
|
// don't passthrough on same caps and also never call transform_ip
|
||||||
|
// in passthrough mode (which does not matter for us here).
|
||||||
|
//
|
||||||
|
// The way how our processing is implemented, in-place transformation
|
||||||
|
// is simpler.
|
||||||
|
const MODE: gst_base::subclass::BaseTransformMode =
|
||||||
|
gst_base::subclass::BaseTransformMode::AlwaysInPlace;
|
||||||
|
const PASSTHROUGH_ON_SAME_CAPS: bool = false;
|
||||||
|
const TRANSFORM_IP_ON_PASSTHROUGH: bool = false;
|
||||||
|
|
||||||
|
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
|
self.parent_start()?;
|
||||||
|
|
||||||
|
*self.state.borrow_mut() = State::default();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
|
self.parent_stop()?;
|
||||||
|
|
||||||
|
*self.state.borrow_mut() = State::default();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_ip(&self, buf: &mut gst::BufferRef) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||||
|
let mut state = self.state.borrow_mut();
|
||||||
|
|
||||||
|
// Update coefficients if new coefficients were set
|
||||||
|
{
|
||||||
|
let mut coeffs = self.coeffs.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some((a, b)) = coeffs.take() {
|
||||||
|
state.x.clear();
|
||||||
|
state.y.clear();
|
||||||
|
if !a.is_empty() {
|
||||||
|
state.y.resize(a.len() - 1, 0.0);
|
||||||
|
}
|
||||||
|
if !b.is_empty() {
|
||||||
|
state.x.resize(b.len() - 1, 0.0);
|
||||||
|
}
|
||||||
|
state.a = a;
|
||||||
|
state.b = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.a.is_empty() | state.b.is_empty() {
|
||||||
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut map = buf.map_writable().map_err(|_| {
|
||||||
|
gst::error!(CAT, imp = self, "Failed to map buffer writable");
|
||||||
|
gst::FlowError::Error
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let samples = map.as_mut_slice_of::<f32>().unwrap();
|
||||||
|
|
||||||
|
assert!(state.b.len() - 1 == state.x.len());
|
||||||
|
assert!(state.a.len() - 1 == state.y.len());
|
||||||
|
|
||||||
|
for sample in samples.iter_mut() {
|
||||||
|
let mut val = state.b[0] * *sample as f64;
|
||||||
|
|
||||||
|
for (b, x) in Iterator::zip(state.b.iter().skip(1), state.x.iter()) {
|
||||||
|
val += b * x;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (a, y) in Iterator::zip(state.a.iter().skip(1), state.y.iter()) {
|
||||||
|
val -= a * y;
|
||||||
|
}
|
||||||
|
|
||||||
|
val /= state.a[0];
|
||||||
|
|
||||||
|
let _ = state.x.pop_back().unwrap();
|
||||||
|
state.x.push_front(*sample as f64);
|
||||||
|
|
||||||
|
let _ = state.y.pop_back().unwrap();
|
||||||
|
state.y.push_front(val);
|
||||||
|
|
||||||
|
*sample = val as f32;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(gst::FlowSuccess::Ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioFilterImpl for IirFilter {
|
||||||
|
fn allowed_caps() -> &'static gst::Caps {
|
||||||
|
static CAPS: std::sync::OnceLock<gst::Caps> = std::sync::OnceLock::new();
|
||||||
|
CAPS.get_or_init(|| {
|
||||||
|
// On both of pads we can only handle F32 mono at any sample rate.
|
||||||
|
gst_audio::AudioCapsBuilder::new_interleaved()
|
||||||
|
.format(gst_audio::AUDIO_FORMAT_F32)
|
||||||
|
.channels(1)
|
||||||
|
.build()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&self, info: &gst_audio::AudioInfo) -> Result<(), gst::LoggableError> {
|
||||||
|
self.parent_setup(info)?;
|
||||||
|
|
||||||
|
gst::debug!(CAT, imp = self, "Rate changed to {}", info.rate());
|
||||||
|
let obj = self.obj();
|
||||||
|
(obj.class().as_ref().set_rate)(&obj, info.rate());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrappers for public methods and associated helper functions.
|
||||||
|
impl IirFilter {
|
||||||
|
pub(super) fn set_coeffs(&self, a: Vec<f64>, b: Vec<f64>) {
|
||||||
|
gst::debug!(CAT, imp = self, "Setting coefficients a: {a:?}, b: {b:?}");
|
||||||
|
*self.coeffs.lock().unwrap() = Some((a, b));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default virtual method implementations.
|
||||||
|
impl IirFilter {
|
||||||
|
fn set_rate_default(&self, _rate: u32) {}
|
||||||
|
}
|
86
examples/src/bin/subclass_vfuncs/iirfilter/mod.rs
Normal file
86
examples/src/bin/subclass_vfuncs/iirfilter/mod.rs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
use gst::{prelude::*, subclass::prelude::*};
|
||||||
|
use gst_audio::subclass::prelude::*;
|
||||||
|
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
// This here defines the public interface of our element and implements
|
||||||
|
// the corresponding traits so that it behaves like any other gst::Element
|
||||||
|
//
|
||||||
|
// GObject
|
||||||
|
// ╰──GstObject
|
||||||
|
// ╰──GstElement
|
||||||
|
// ╰──GstBaseTransform
|
||||||
|
// ╰──GstAudioFilter
|
||||||
|
// ╰──IirFilter
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct IirFilter(ObjectSubclass<imp::IirFilter>) @extends gst_audio::AudioFilter, gst_base::BaseTransform, gst::Element, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait containing extension methods for `IirFilter`.
|
||||||
|
pub trait IirFilterExt: IsA<IirFilter> {
|
||||||
|
// Sets the coefficients by getting access to the private struct and simply setting them
|
||||||
|
fn set_coeffs(&self, a: Vec<f64>, b: Vec<f64>) {
|
||||||
|
self.upcast_ref::<IirFilter>().imp().set_coeffs(a, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<O: IsA<IirFilter>> IirFilterExt for O {}
|
||||||
|
|
||||||
|
/// Trait to implement in `IirFilter` subclasses.
|
||||||
|
pub trait IirFilterImpl: AudioFilterImpl {
|
||||||
|
/// Called whenever the sample rate is changing.
|
||||||
|
fn set_rate(&self, rate: u32) {
|
||||||
|
self.parent_set_rate(rate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait containing extension methods for `IirFilterImpl`, specifically methods for chaining
|
||||||
|
/// up to the parent implementation of virtual methods.
|
||||||
|
pub trait IirFilterImplExt: IirFilterImpl {
|
||||||
|
fn parent_set_rate(&self, rate: u32) {
|
||||||
|
unsafe {
|
||||||
|
let data = Self::type_data();
|
||||||
|
let parent_class = &*(data.as_ref().parent_class() as *mut Class);
|
||||||
|
(parent_class.set_rate)(self.obj().unsafe_cast_ref(), rate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IirFilterImpl> IirFilterImplExt for T {}
|
||||||
|
|
||||||
|
/// Class struct for `IirFilter`.
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Class {
|
||||||
|
parent: <<imp::IirFilter as ObjectSubclass>::ParentType as ObjectType>::GlibClassType,
|
||||||
|
|
||||||
|
set_rate: fn(&IirFilter, rate: u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl ClassStruct for Class {
|
||||||
|
type Type = imp::IirFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This allows directly using `Class` as e.g. `gst_audio::AudioFilterClass` or
|
||||||
|
/// `gst_base::BaseTransformClass` without having to cast.
|
||||||
|
impl std::ops::Deref for Class {
|
||||||
|
type Target = glib::Class<<<Self as ClassStruct>::Type as ObjectSubclass>::ParentType>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
unsafe { &*(&self.parent as *const _ as *const _) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Overrides the virtual methods with the actual implementation of the subclass as is provided by
|
||||||
|
/// the subclass' implementation of the `Impl` trait.
|
||||||
|
unsafe impl<T: IirFilterImpl> IsSubclassable<T> for IirFilter {
|
||||||
|
fn class_init(class: &mut glib::Class<Self>) {
|
||||||
|
Self::parent_class_init::<T>(class);
|
||||||
|
|
||||||
|
let class = class.as_mut();
|
||||||
|
|
||||||
|
class.set_rate = |obj, rate| unsafe {
|
||||||
|
let imp = obj.unsafe_cast_ref::<T::Type>().imp();
|
||||||
|
imp.set_rate(rate);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
170
examples/src/bin/subclass_vfuncs/lowpass/imp.rs
Normal file
170
examples/src/bin/subclass_vfuncs/lowpass/imp.rs
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
// In the imp submodule we include the actual implementation
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use glib::prelude::*;
|
||||||
|
use gst::prelude::*;
|
||||||
|
use gst_audio::subclass::prelude::*;
|
||||||
|
|
||||||
|
use crate::iirfilter::{IirFilterExt, IirFilterImpl};
|
||||||
|
|
||||||
|
// These are the property values of our filter
|
||||||
|
pub struct Settings {
|
||||||
|
cutoff: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Settings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Settings { cutoff: 0.0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the state of our filter
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct State {
|
||||||
|
rate: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the private data of our filter
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Lowpass {
|
||||||
|
settings: Mutex<Settings>,
|
||||||
|
state: Mutex<State>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// This trait registers our type with the GObject object system and
|
||||||
|
// provides the entry points for creating a new instance and setting
|
||||||
|
// up the class data
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for Lowpass {
|
||||||
|
const NAME: &'static str = "RsLowpass";
|
||||||
|
type Type = super::Lowpass;
|
||||||
|
type ParentType = crate::iirfilter::IirFilter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of glib::Object virtual methods
|
||||||
|
impl ObjectImpl for Lowpass {
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
static PROPERTIES: std::sync::OnceLock<Vec<glib::ParamSpec>> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
PROPERTIES.get_or_init(|| {
|
||||||
|
vec![glib::ParamSpecFloat::builder("cutoff")
|
||||||
|
.nick("Cutoff")
|
||||||
|
.blurb("Cutoff frequency in Hz")
|
||||||
|
.default_value(Settings::default().cutoff)
|
||||||
|
.minimum(0.0)
|
||||||
|
.mutable_playing()
|
||||||
|
.build()]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
match pspec.name() {
|
||||||
|
"cutoff" => {
|
||||||
|
self.settings.lock().unwrap().cutoff = value.get().unwrap();
|
||||||
|
self.calculate_coeffs();
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"cutoff" => self.settings.lock().unwrap().cutoff.to_value(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GstObjectImpl for Lowpass {}
|
||||||
|
|
||||||
|
// Implementation of gst::Element virtual methods
|
||||||
|
impl ElementImpl for Lowpass {
|
||||||
|
// The element specific metadata. This information is what is visible from
|
||||||
|
// gst-inspect-1.0 and can also be programmatically retrieved from the gst::Registry
|
||||||
|
// after initial registration without having to load the plugin in memory.
|
||||||
|
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||||
|
static ELEMENT_METADATA: std::sync::OnceLock<gst::subclass::ElementMetadata> =
|
||||||
|
std::sync::OnceLock::new();
|
||||||
|
Some(ELEMENT_METADATA.get_or_init(|| {
|
||||||
|
gst::subclass::ElementMetadata::new(
|
||||||
|
"Lowpass Filter",
|
||||||
|
"Filter/Effect/Audio",
|
||||||
|
"A Lowpass audio filter",
|
||||||
|
"Sebastian Dröge <sebastian@centricular.com>",
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementation of gst_base::BaseTransform virtual methods
|
||||||
|
impl BaseTransformImpl for Lowpass {
|
||||||
|
const MODE: gst_base::subclass::BaseTransformMode =
|
||||||
|
<<crate::iirfilter::IirFilter as glib::object::ObjectSubclassIs>::Subclass>::MODE;
|
||||||
|
const PASSTHROUGH_ON_SAME_CAPS: bool =
|
||||||
|
<<crate::iirfilter::IirFilter as glib::object::ObjectSubclassIs>::Subclass>::PASSTHROUGH_ON_SAME_CAPS;
|
||||||
|
const TRANSFORM_IP_ON_PASSTHROUGH: bool =
|
||||||
|
<<crate::iirfilter::IirFilter as glib::object::ObjectSubclassIs>::Subclass>::TRANSFORM_IP_ON_PASSTHROUGH;
|
||||||
|
|
||||||
|
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
|
self.parent_start()?;
|
||||||
|
|
||||||
|
*self.state.lock().unwrap() = State::default();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement of gst_audio::AudioFilter virtual methods
|
||||||
|
impl AudioFilterImpl for Lowpass {}
|
||||||
|
|
||||||
|
// Implement of IirFilter virtual methods
|
||||||
|
impl IirFilterImpl for Lowpass {
|
||||||
|
fn set_rate(&self, rate: u32) {
|
||||||
|
// Could call
|
||||||
|
// self.parent_set_rate(rate);
|
||||||
|
// here but chaining up is not necessary if the base class doesn't require that
|
||||||
|
// or if the behaviour of the parent class should be completely overridden.
|
||||||
|
|
||||||
|
self.state.lock().unwrap().rate = Some(rate);
|
||||||
|
self.calculate_coeffs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lowpass {
|
||||||
|
fn calculate_coeffs(&self) {
|
||||||
|
use std::f64;
|
||||||
|
|
||||||
|
let Some(rate) = self.state.lock().unwrap().rate else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let cutoff = self.settings.lock().unwrap().cutoff;
|
||||||
|
|
||||||
|
// See Audio EQ Cookbook
|
||||||
|
// https://www.w3.org/TR/audio-eq-cookbook
|
||||||
|
let cutoff = cutoff as f64 / rate as f64;
|
||||||
|
|
||||||
|
let omega = 2.0 * f64::consts::PI * cutoff;
|
||||||
|
let q = 1.0;
|
||||||
|
|
||||||
|
let alpha = f64::sin(omega) / (2.0 * q);
|
||||||
|
|
||||||
|
let mut b = vec![
|
||||||
|
(1.0 - f64::cos(omega)) / 2.0,
|
||||||
|
1.0 - f64::cos(omega),
|
||||||
|
(1.0 - f64::cos(omega) / 2.0),
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut a = vec![1.0 + alpha, -2.0 * f64::cos(omega), 1.0 - alpha];
|
||||||
|
|
||||||
|
let a0 = a[0];
|
||||||
|
for a in &mut a {
|
||||||
|
*a /= a0;
|
||||||
|
}
|
||||||
|
for b in &mut b {
|
||||||
|
*b /= a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.obj().set_coeffs(a, b);
|
||||||
|
}
|
||||||
|
}
|
15
examples/src/bin/subclass_vfuncs/lowpass/mod.rs
Normal file
15
examples/src/bin/subclass_vfuncs/lowpass/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
mod imp;
|
||||||
|
|
||||||
|
// This here defines the public interface of our element and implements
|
||||||
|
// the corresponding traits so that it behaves like any other gst::Element
|
||||||
|
//
|
||||||
|
// GObject
|
||||||
|
// ╰──GstObject
|
||||||
|
// ╰──GstElement
|
||||||
|
// ╰──GstBaseTransform
|
||||||
|
// ╰──GstAudioFilter
|
||||||
|
// ╰──IirFilter
|
||||||
|
// ╰──Lowpass
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct Lowpass(ObjectSubclass<imp::Lowpass>) @extends crate::iirfilter::IirFilter, gst_audio::AudioFilter, gst_base::BaseTransform, gst::Element, gst::Object;
|
||||||
|
}
|
66
examples/src/bin/subclass_vfuncs/main.rs
Normal file
66
examples/src/bin/subclass_vfuncs/main.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// This example implements a baseclass IirFilter, and a subclass Lowpass of that.
|
||||||
|
//
|
||||||
|
// The example shows how to provide and implement virtual methods, and how to provide non-virtual
|
||||||
|
// methods on the base class.
|
||||||
|
|
||||||
|
use gst::prelude::*;
|
||||||
|
|
||||||
|
mod iirfilter;
|
||||||
|
mod lowpass;
|
||||||
|
|
||||||
|
#[path = "../../examples-common.rs"]
|
||||||
|
mod examples_common;
|
||||||
|
|
||||||
|
fn example_main() {
|
||||||
|
gst::init().unwrap();
|
||||||
|
|
||||||
|
let pipeline = gst::Pipeline::new();
|
||||||
|
let src = gst::ElementFactory::make("audiotestsrc")
|
||||||
|
.property_from_str("wave", "white-noise")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let filter = glib::Object::builder::<lowpass::Lowpass>()
|
||||||
|
.property("cutoff", 4000.0f32)
|
||||||
|
.build();
|
||||||
|
let conv = gst::ElementFactory::make("audioconvert").build().unwrap();
|
||||||
|
let sink = gst::ElementFactory::make("autoaudiosink").build().unwrap();
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.add_many([&src, filter.as_ref(), &conv, &sink])
|
||||||
|
.unwrap();
|
||||||
|
gst::Element::link_many([&src, filter.as_ref(), &conv, &sink]).unwrap();
|
||||||
|
|
||||||
|
let bus = pipeline.bus().unwrap();
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Playing)
|
||||||
|
.expect("Unable to set the pipeline to the `Playing` state");
|
||||||
|
|
||||||
|
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
|
match msg.view() {
|
||||||
|
MessageView::Eos(..) => break,
|
||||||
|
MessageView::Error(err) => {
|
||||||
|
println!(
|
||||||
|
"Error from {:?}: {} ({:?})",
|
||||||
|
err.src().map(|s| s.path_string()),
|
||||||
|
err.error(),
|
||||||
|
err.debug()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Null)
|
||||||
|
.expect("Unable to set the pipeline to the `Null` state");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// tutorials_common::run is only required to set up the application environment on macOS
|
||||||
|
// (but not necessary in normal Cocoa applications where this is set up automatically)
|
||||||
|
examples_common::run(example_main);
|
||||||
|
}
|
|
@ -42,7 +42,7 @@ fn example_main() -> Result<(), Error> {
|
||||||
|
|
||||||
// Parse the pipeline we want to probe from a static in-line string.
|
// Parse the pipeline we want to probe from a static in-line string.
|
||||||
let mut context = gst::ParseContext::new();
|
let mut context = gst::ParseContext::new();
|
||||||
let pipeline = match gst::parse_launch_full(
|
let pipeline = match gst::parse::launch_full(
|
||||||
"audiotestsrc wave=white-noise num-buffers=100 ! flacenc ! filesink location=test.flac",
|
"audiotestsrc wave=white-noise num-buffers=100 ! flacenc ! filesink location=test.flac",
|
||||||
Some(&mut context),
|
Some(&mut context),
|
||||||
gst::ParseFlags::empty(),
|
gst::ParseFlags::empty(),
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
use gst::{element_error, prelude::*};
|
use gst::{element_error, prelude::*};
|
||||||
|
use gst_video::prelude::*;
|
||||||
|
|
||||||
#[path = "../examples-common.rs"]
|
#[path = "../examples-common.rs"]
|
||||||
mod examples_common;
|
mod examples_common;
|
||||||
|
@ -26,7 +27,7 @@ fn create_pipeline(uri: String, out_path: std::path::PathBuf) -> Result<gst::Pip
|
||||||
gst::init()?;
|
gst::init()?;
|
||||||
|
|
||||||
// Create our pipeline from a pipeline description string.
|
// Create our pipeline from a pipeline description string.
|
||||||
let pipeline = gst::parse_launch(&format!(
|
let pipeline = gst::parse::launch(&format!(
|
||||||
"uridecodebin uri={uri} ! videoconvert ! appsink name=sink"
|
"uridecodebin uri={uri} ! videoconvert ! appsink name=sink"
|
||||||
))?
|
))?
|
||||||
.downcast::<gst::Pipeline>()
|
.downcast::<gst::Pipeline>()
|
||||||
|
|
|
@ -34,8 +34,8 @@ fn example_main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let decodebin = gst::ElementFactory::make("decodebin").build().unwrap();
|
let decodebin = gst::ElementFactory::make("decodebin").build().unwrap();
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &decodebin]).unwrap();
|
pipeline.add_many([&src, &decodebin]).unwrap();
|
||||||
gst::Element::link_many(&[&src, &decodebin]).unwrap();
|
gst::Element::link_many([&src, &decodebin]).unwrap();
|
||||||
|
|
||||||
// Need to move a new reference into the closure.
|
// Need to move a new reference into the closure.
|
||||||
// !!ATTENTION!!:
|
// !!ATTENTION!!:
|
||||||
|
@ -52,9 +52,8 @@ fn example_main() {
|
||||||
decodebin.connect_pad_added(move |_, src_pad| {
|
decodebin.connect_pad_added(move |_, src_pad| {
|
||||||
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
|
||||||
// we moved into this callback.
|
// we moved into this callback.
|
||||||
let pipeline = match pipeline_weak.upgrade() {
|
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||||
Some(pipeline) => pipeline,
|
return;
|
||||||
None => return,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// In this example, we are only interested about parsing the ToC, so
|
// In this example, we are only interested about parsing the ToC, so
|
||||||
|
|
|
@ -66,7 +66,7 @@ fn example_main() -> Result<(), Error> {
|
||||||
// Increase the queue capacity to 100MB to avoid a stalling pipeline
|
// Increase the queue capacity to 100MB to avoid a stalling pipeline
|
||||||
|
|
||||||
pipeline
|
pipeline
|
||||||
.add_many(&[&src, &typefinder, &queue, &muxer, &sink])
|
.add_many([&src, &typefinder, &queue, &muxer, &sink])
|
||||||
.expect("failed to add elements to pipeline");
|
.expect("failed to add elements to pipeline");
|
||||||
|
|
||||||
src.link(&typefinder)?;
|
src.link(&typefinder)?;
|
||||||
|
|
|
@ -11,7 +11,7 @@ fn example_main() {
|
||||||
gst::init().unwrap();
|
gst::init().unwrap();
|
||||||
|
|
||||||
// This creates a pipeline by parsing the gst-launch pipeline syntax.
|
// This creates a pipeline by parsing the gst-launch pipeline syntax.
|
||||||
let pipeline = gst::parse_launch(
|
let pipeline = gst::parse::launch(
|
||||||
"videotestsrc name=src ! video/x-raw,width=640,height=480 ! compositor0.sink_0 \
|
"videotestsrc name=src ! video/x-raw,width=640,height=480 ! compositor0.sink_0 \
|
||||||
compositor ! video/x-raw,width=1280,height=720 ! videoconvert ! autovideosink",
|
compositor ! video/x-raw,width=1280,height=720 ! videoconvert ! autovideosink",
|
||||||
)
|
)
|
||||||
|
|
228
examples/src/bin/zoom.rs
Normal file
228
examples/src/bin/zoom.rs
Normal file
|
@ -0,0 +1,228 @@
|
||||||
|
// Zoom example using navigation events and a compositor
|
||||||
|
|
||||||
|
// Use can change the video player zoom using the next keys:
|
||||||
|
// * +: Zoom in
|
||||||
|
// * -: Zoom out
|
||||||
|
// * Up/Down/Right/Left: Move the frame
|
||||||
|
// * r: reset the zoom
|
||||||
|
// Also mouse navigation events can be used for a better UX.
|
||||||
|
|
||||||
|
use gst::prelude::*;
|
||||||
|
use gst_video::video_event::NavigationEvent;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
#[path = "../examples-common.rs"]
|
||||||
|
mod examples_common;
|
||||||
|
|
||||||
|
const WIDTH: i32 = 1280;
|
||||||
|
const HEIGHT: i32 = 720;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MouseState {
|
||||||
|
clicked: bool,
|
||||||
|
clicked_x: f64,
|
||||||
|
clicked_y: f64,
|
||||||
|
clicked_xpos: i32,
|
||||||
|
clicked_ypos: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn zoom(mixer_sink_pad: gst::Pad, x: i32, y: i32, zoom_in: bool) {
|
||||||
|
let xpos = mixer_sink_pad.property::<i32>("xpos");
|
||||||
|
let ypos = mixer_sink_pad.property::<i32>("ypos");
|
||||||
|
let width = mixer_sink_pad.property::<i32>("width");
|
||||||
|
let height = mixer_sink_pad.property::<i32>("height");
|
||||||
|
|
||||||
|
let (width_offset, height_offset) = if zoom_in {
|
||||||
|
(WIDTH / 10, HEIGHT / 10)
|
||||||
|
} else {
|
||||||
|
(-WIDTH / 10, -HEIGHT / 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
if width_offset + width <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mixer_sink_pad.set_property("width", width + width_offset);
|
||||||
|
mixer_sink_pad.set_property("height", height + height_offset);
|
||||||
|
|
||||||
|
let xpos_offset = ((x as f32 / WIDTH as f32) * width_offset as f32) as i32;
|
||||||
|
let new_xpos = xpos - xpos_offset;
|
||||||
|
let ypos_offset = ((y as f32 / HEIGHT as f32) * height_offset as f32) as i32;
|
||||||
|
let new_ypos = ypos - ypos_offset;
|
||||||
|
|
||||||
|
if new_xpos != xpos {
|
||||||
|
mixer_sink_pad.set_property("xpos", new_xpos);
|
||||||
|
}
|
||||||
|
if new_ypos != ypos {
|
||||||
|
mixer_sink_pad.set_property("ypos", new_ypos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_zoom(mixer_sink_pad: gst::Pad) {
|
||||||
|
let xpos = mixer_sink_pad.property::<i32>("xpos");
|
||||||
|
let ypos = mixer_sink_pad.property::<i32>("ypos");
|
||||||
|
let width = mixer_sink_pad.property::<i32>("width");
|
||||||
|
let height = mixer_sink_pad.property::<i32>("height");
|
||||||
|
|
||||||
|
if 0 != xpos {
|
||||||
|
mixer_sink_pad.set_property("xpos", 0);
|
||||||
|
}
|
||||||
|
if 0 != ypos {
|
||||||
|
mixer_sink_pad.set_property("ypos", 0);
|
||||||
|
}
|
||||||
|
if WIDTH != width {
|
||||||
|
mixer_sink_pad.set_property("width", WIDTH);
|
||||||
|
}
|
||||||
|
if HEIGHT != height {
|
||||||
|
mixer_sink_pad.set_property("height", HEIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn example_main() {
|
||||||
|
let clicked = Mutex::new(MouseState::default());
|
||||||
|
|
||||||
|
gst::init().unwrap();
|
||||||
|
|
||||||
|
let pipeline = gst::parse::launch(&format!(
|
||||||
|
"compositor name=mix background=1 sink_0::xpos=0 sink_0::ypos=0 sink_0::zorder=0 sink_0::width={WIDTH} sink_0::height={HEIGHT} ! xvimagesink \
|
||||||
|
videotestsrc name=src ! video/x-raw,framerate=30/1,width={WIDTH},height={HEIGHT},pixel-aspect-ratio=1/1 ! queue ! mix.sink_0"
|
||||||
|
)).unwrap().downcast::<gst::Pipeline>().unwrap();
|
||||||
|
|
||||||
|
let mixer = pipeline.by_name("mix").unwrap();
|
||||||
|
let mixer_src_pad = mixer.static_pad("src").unwrap();
|
||||||
|
let mixer_sink_pad_weak = mixer.static_pad("sink_0").unwrap().downgrade();
|
||||||
|
|
||||||
|
// Probe added in the sink pad to get direct navigation events w/o transformation done by the mixer
|
||||||
|
mixer_src_pad.add_probe(gst::PadProbeType::EVENT_UPSTREAM, move |_, probe_info| {
|
||||||
|
let mixer_sink_pad = mixer_sink_pad_weak.upgrade().unwrap();
|
||||||
|
|
||||||
|
let Some(ev) = probe_info.event() else {
|
||||||
|
return gst::PadProbeReturn::Ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ev.type_() != gst::EventType::Navigation {
|
||||||
|
return gst::PadProbeReturn::Ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(nav_event) = NavigationEvent::parse(ev) else {
|
||||||
|
return gst::PadProbeReturn::Ok;
|
||||||
|
};
|
||||||
|
|
||||||
|
match nav_event {
|
||||||
|
NavigationEvent::KeyPress { key, .. } => match key.as_str() {
|
||||||
|
"Left" => {
|
||||||
|
let xpos = mixer_sink_pad.property::<i32>("xpos");
|
||||||
|
mixer_sink_pad.set_property("xpos", xpos - 10);
|
||||||
|
}
|
||||||
|
"Right" => {
|
||||||
|
let xpos = mixer_sink_pad.property::<i32>("xpos");
|
||||||
|
mixer_sink_pad.set_property("xpos", xpos + 10);
|
||||||
|
}
|
||||||
|
"Up" => {
|
||||||
|
let ypos = mixer_sink_pad.property::<i32>("ypos");
|
||||||
|
mixer_sink_pad.set_property("ypos", ypos - 10);
|
||||||
|
}
|
||||||
|
"Down" => {
|
||||||
|
let ypos = mixer_sink_pad.property::<i32>("ypos");
|
||||||
|
mixer_sink_pad.set_property("ypos", ypos + 10);
|
||||||
|
}
|
||||||
|
"plus" => {
|
||||||
|
zoom(mixer_sink_pad, WIDTH / 2, HEIGHT / 2, true);
|
||||||
|
}
|
||||||
|
"minus" => {
|
||||||
|
zoom(mixer_sink_pad, WIDTH / 2, HEIGHT / 2, false);
|
||||||
|
}
|
||||||
|
"r" => {
|
||||||
|
reset_zoom(mixer_sink_pad);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
NavigationEvent::MouseMove { x, y, .. } => {
|
||||||
|
let state = clicked.lock().unwrap();
|
||||||
|
if state.clicked {
|
||||||
|
let xpos = mixer_sink_pad.property::<i32>("xpos");
|
||||||
|
let ypos = mixer_sink_pad.property::<i32>("ypos");
|
||||||
|
|
||||||
|
let new_xpos = state.clicked_xpos + (x - state.clicked_x) as i32;
|
||||||
|
let new_ypos = state.clicked_ypos + (y - state.clicked_y) as i32;
|
||||||
|
|
||||||
|
if new_xpos != xpos {
|
||||||
|
mixer_sink_pad.set_property("xpos", new_xpos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_ypos != ypos {
|
||||||
|
mixer_sink_pad.set_property("ypos", new_ypos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NavigationEvent::MouseButtonPress { button, x, y, .. } => {
|
||||||
|
if button == 1 || button == 272 {
|
||||||
|
let mut state = clicked.lock().unwrap();
|
||||||
|
state.clicked = true;
|
||||||
|
state.clicked_x = x;
|
||||||
|
state.clicked_y = y;
|
||||||
|
state.clicked_xpos = mixer_sink_pad.property("xpos");
|
||||||
|
state.clicked_ypos = mixer_sink_pad.property("ypos");
|
||||||
|
} else if button == 2 || button == 3 || button == 274 || button == 273 {
|
||||||
|
reset_zoom(mixer_sink_pad);
|
||||||
|
} else if button == 4 {
|
||||||
|
zoom(mixer_sink_pad, x as i32, y as i32, true);
|
||||||
|
} else if button == 5 {
|
||||||
|
zoom(mixer_sink_pad, x as i32, y as i32, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NavigationEvent::MouseButtonRelease { button, .. } => {
|
||||||
|
if button == 1 || button == 272 {
|
||||||
|
let mut state = clicked.lock().unwrap();
|
||||||
|
state.clicked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NavigationEvent::MouseScroll { x, y, delta_y, .. } => {
|
||||||
|
if delta_y > 0.0 {
|
||||||
|
zoom(mixer_sink_pad, x as i32, y as i32, true);
|
||||||
|
} else if delta_y < 0.0 {
|
||||||
|
zoom(mixer_sink_pad, x as i32, y as i32, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
gst::PadProbeReturn::Ok
|
||||||
|
});
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Playing)
|
||||||
|
.expect("Unable to set the pipeline to the `Playing` state");
|
||||||
|
|
||||||
|
let bus = pipeline.bus().unwrap();
|
||||||
|
for msg in bus.iter_timed(gst::ClockTime::NONE) {
|
||||||
|
use gst::MessageView;
|
||||||
|
|
||||||
|
match msg.view() {
|
||||||
|
MessageView::Eos(..) => {
|
||||||
|
println!("received eos");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
MessageView::Error(err) => {
|
||||||
|
println!(
|
||||||
|
"Error from {:?}: {} ({:?})",
|
||||||
|
err.src().map(|s| s.path_string()),
|
||||||
|
err.error(),
|
||||||
|
err.debug()
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline
|
||||||
|
.set_state(gst::State::Null)
|
||||||
|
.expect("Unable to set the pipeline to the `Null` state");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// tutorials_common::run is only required to set up the application environment on macOS
|
||||||
|
// (but not necessary in normal Cocoa applications where this is set up automatically)
|
||||||
|
examples_common::run(example_main);
|
||||||
|
}
|
|
@ -17,13 +17,47 @@ pub fn run<T, F: FnOnce() -> T + Send + 'static>(main: F) -> T
|
||||||
where
|
where
|
||||||
T: Send + 'static,
|
T: Send + 'static,
|
||||||
{
|
{
|
||||||
use std::thread;
|
use std::{
|
||||||
|
ffi::c_void,
|
||||||
|
sync::mpsc::{channel, Sender},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
use cocoa::appkit::NSApplication;
|
use cocoa::{
|
||||||
|
appkit::{NSApplication, NSWindow},
|
||||||
|
base::id,
|
||||||
|
delegate,
|
||||||
|
};
|
||||||
|
use objc::{
|
||||||
|
msg_send,
|
||||||
|
runtime::{Object, Sel},
|
||||||
|
sel, sel_impl,
|
||||||
|
};
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let app = cocoa::appkit::NSApp();
|
let app = cocoa::appkit::NSApp();
|
||||||
let t = thread::spawn(|| {
|
let (send, recv) = channel::<()>();
|
||||||
|
|
||||||
|
extern "C" fn on_finish_launching(this: &Object, _cmd: Sel, _notification: id) {
|
||||||
|
let send = unsafe {
|
||||||
|
let send_pointer = *this.get_ivar::<*const c_void>("send");
|
||||||
|
let boxed = Box::from_raw(send_pointer as *mut Sender<()>);
|
||||||
|
*boxed
|
||||||
|
};
|
||||||
|
send.send(()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let delegate = delegate!("AppDelegate", {
|
||||||
|
app: id = app,
|
||||||
|
send: *const c_void = Box::into_raw(Box::new(send)) as *const c_void,
|
||||||
|
(applicationDidFinishLaunching:) => on_finish_launching as extern fn(&Object, Sel, id)
|
||||||
|
});
|
||||||
|
app.setDelegate_(delegate);
|
||||||
|
|
||||||
|
let t = thread::spawn(move || {
|
||||||
|
// Wait for the NSApp to launch to avoid possibly calling stop_() too early
|
||||||
|
recv.recv().unwrap();
|
||||||
|
|
||||||
let res = main();
|
let res = main();
|
||||||
|
|
||||||
let app = cocoa::appkit::NSApp();
|
let app = cocoa::appkit::NSApp();
|
||||||
|
|
|
@ -1,15 +1,31 @@
|
||||||
// This example demonstrates how to output GL textures, within an
|
//! This example demonstrates how to output GL textures, within an EGL/X11 context provided by the
|
||||||
// EGL/X11 context provided by the application, and render those
|
//! application, and render those textures in the GL application.
|
||||||
// textures in the GL application.
|
//!
|
||||||
|
//! This example follow common patterns from `glutin`:
|
||||||
|
//! <https://github.com/rust-windowing/glutin/blob/master/glutin_examples/src/lib.rs>
|
||||||
|
|
||||||
// {videotestsrc} - { glsinkbin }
|
// {videotestsrc} - { glsinkbin }
|
||||||
|
|
||||||
use std::{ffi::CStr, mem, ptr, sync};
|
use std::{
|
||||||
|
ffi::{CStr, CString},
|
||||||
|
mem,
|
||||||
|
num::NonZeroU32,
|
||||||
|
ptr,
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Error;
|
use anyhow::{Context, Result};
|
||||||
use derive_more::{Display, Error};
|
use derive_more::{Display, Error};
|
||||||
|
use glutin::{
|
||||||
|
config::GetGlConfig as _,
|
||||||
|
context::AsRawContext as _,
|
||||||
|
display::{AsRawDisplay as _, GetGlDisplay as _},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use glutin_winit::GlWindow as _;
|
||||||
use gst::element_error;
|
use gst::element_error;
|
||||||
use gst_gl::prelude::*;
|
use gst_gl::prelude::*;
|
||||||
|
use raw_window_handle::HasRawWindowHandle as _;
|
||||||
|
|
||||||
#[derive(Debug, Display, Error)]
|
#[derive(Debug, Display, Error)]
|
||||||
#[display(fmt = "Received error from {src}: {error} (debug: {debug:?})")]
|
#[display(fmt = "Received error from {src}: {error} (debug: {debug:?})")]
|
||||||
|
@ -66,6 +82,7 @@ void main() {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[allow(clippy::manual_non_exhaustive)]
|
#[allow(clippy::manual_non_exhaustive)]
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
#[allow(clippy::missing_transmute_annotations)]
|
||||||
pub(crate) mod gl {
|
pub(crate) mod gl {
|
||||||
pub use self::Gles2 as Gl;
|
pub use self::Gles2 as Gl;
|
||||||
include!(concat!(env!("OUT_DIR"), "/test_gl_bindings.rs"));
|
include!(concat!(env!("OUT_DIR"), "/test_gl_bindings.rs"));
|
||||||
|
@ -171,7 +188,7 @@ impl Gl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(&self, size: glutin::dpi::PhysicalSize<u32>) {
|
fn resize(&self, size: winit::dpi::PhysicalSize<u32>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
self.gl
|
self.gl
|
||||||
.Viewport(0, 0, size.width as i32, size.height as i32);
|
.Viewport(0, 0, size.width as i32, size.height as i32);
|
||||||
|
@ -179,14 +196,17 @@ impl Gl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
|
fn load(gl_display: &impl glutin::display::GlDisplay) -> Gl {
|
||||||
let gl = gl::Gl::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _);
|
let gl = gl::Gl::load_with(|symbol| {
|
||||||
|
let symbol = CString::new(symbol).unwrap();
|
||||||
|
gl_display.get_proc_address(&symbol).cast()
|
||||||
|
});
|
||||||
|
|
||||||
let version = unsafe {
|
let version = unsafe {
|
||||||
let data = CStr::from_ptr(gl.GetString(gl::VERSION) as *const _)
|
let version = gl.GetString(gl::VERSION);
|
||||||
.to_bytes()
|
assert!(!version.is_null());
|
||||||
.to_vec();
|
let version = CStr::from_ptr(version.cast());
|
||||||
String::from_utf8(data).unwrap()
|
version.to_string_lossy()
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("OpenGL version {version}");
|
println!("OpenGL version {version}");
|
||||||
|
@ -206,9 +226,10 @@ fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
|
||||||
gl.LinkProgram(program);
|
gl.LinkProgram(program);
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut success: gl::types::GLint = 1;
|
let mut success = 1;
|
||||||
gl.GetProgramiv(fs, gl::LINK_STATUS, &mut success);
|
gl.GetProgramiv(program, gl::LINK_STATUS, &mut success);
|
||||||
assert!(success != 0);
|
assert_ne!(success, 0);
|
||||||
|
assert_eq!(gl.GetError(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let attr_position = gl.GetAttribLocation(program, b"a_position\0".as_ptr() as *const _);
|
let attr_position = gl.GetAttribLocation(program, b"a_position\0".as_ptr() as *const _);
|
||||||
|
@ -279,6 +300,8 @@ fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
|
||||||
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
||||||
gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||||
|
|
||||||
|
assert_eq!(gl.GetError(), 0);
|
||||||
|
|
||||||
(
|
(
|
||||||
program,
|
program,
|
||||||
attr_position,
|
attr_position,
|
||||||
|
@ -310,154 +333,205 @@ pub(crate) struct App {
|
||||||
pipeline: gst::Pipeline,
|
pipeline: gst::Pipeline,
|
||||||
appsink: gst_app::AppSink,
|
appsink: gst_app::AppSink,
|
||||||
bus: gst::Bus,
|
bus: gst::Bus,
|
||||||
event_loop: glutin::event_loop::EventLoop<Message>,
|
event_loop: winit::event_loop::EventLoop<Message>,
|
||||||
windowed_context: glutin::WindowedContext<glutin::PossiblyCurrent>,
|
window: Option<winit::window::Window>,
|
||||||
|
not_current_gl_context: Option<glutin::context::NotCurrentContext>,
|
||||||
shared_context: gst_gl::GLContext,
|
shared_context: gst_gl::GLContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result<App, Error> {
|
pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result<App> {
|
||||||
gst::init()?;
|
gst::init()?;
|
||||||
|
|
||||||
let (pipeline, appsink) = App::create_pipeline(gl_element)?;
|
let (pipeline, appsink) = App::create_pipeline(gl_element)?;
|
||||||
let bus = pipeline
|
let bus = pipeline
|
||||||
.bus()
|
.bus()
|
||||||
.expect("Pipeline without bus. Shouldn't happen!");
|
.context("Pipeline without bus. Shouldn't happen!")?;
|
||||||
|
|
||||||
let event_loop = glutin::event_loop::EventLoopBuilder::with_user_event().build();
|
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build()?;
|
||||||
let window = glutin::window::WindowBuilder::new().with_title("GL rendering");
|
|
||||||
let windowed_context = glutin::ContextBuilder::new()
|
|
||||||
.with_vsync(true)
|
|
||||||
.build_windowed(window, &event_loop)?;
|
|
||||||
|
|
||||||
let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, err)| err)? };
|
// Only Windows requires the window to be present before creating a `glutin::Display`. Other
|
||||||
|
// platforms don't really need one (and on Android, none exists until `Event::Resumed`).
|
||||||
|
let window_builder = cfg!(windows).then(|| {
|
||||||
|
winit::window::WindowBuilder::new()
|
||||||
|
.with_transparent(true)
|
||||||
|
.with_title("GL rendering")
|
||||||
|
});
|
||||||
|
|
||||||
#[cfg(any(feature = "gst-gl-x11", feature = "gst-gl-wayland"))]
|
let display_builder =
|
||||||
let inner_window = windowed_context.window();
|
glutin_winit::DisplayBuilder::new().with_window_builder(window_builder);
|
||||||
|
// XXX on macOS/cgl only one config can be queried at a time. If transparency is needed,
|
||||||
|
// add .with_transparency(true) to ConfigTemplateBuilder. EGL on X11 doesn't support
|
||||||
|
// transparency at all.
|
||||||
|
let template = glutin::config::ConfigTemplateBuilder::new().with_alpha_size(8);
|
||||||
|
let (window, gl_config) = display_builder
|
||||||
|
.build(&event_loop, template, |configs| {
|
||||||
|
configs
|
||||||
|
.reduce(|current, new_config| {
|
||||||
|
let prefer_transparency =
|
||||||
|
new_config.supports_transparency().unwrap_or(false)
|
||||||
|
& !current.supports_transparency().unwrap_or(false);
|
||||||
|
|
||||||
let shared_context: gst_gl::GLContext;
|
if prefer_transparency || new_config.num_samples() > current.num_samples() {
|
||||||
if cfg!(target_os = "linux") {
|
new_config
|
||||||
#[cfg(any(feature = "gst-gl-x11", feature = "gst-gl-wayland"))]
|
} else {
|
||||||
use glutin::platform::unix::WindowExtUnix;
|
current
|
||||||
use glutin::platform::{unix::RawHandle, ContextTraitExt};
|
|
||||||
|
|
||||||
let api = App::map_gl_api(windowed_context.get_api());
|
|
||||||
|
|
||||||
let (gl_context, gl_display, platform) = match unsafe { windowed_context.raw_handle() }
|
|
||||||
{
|
|
||||||
#[cfg(any(feature = "gst-gl-egl", feature = "gst-gl-wayland"))]
|
|
||||||
RawHandle::Egl(egl_context) => {
|
|
||||||
let mut gl_display = None;
|
|
||||||
|
|
||||||
#[cfg(feature = "gst-gl-egl")]
|
|
||||||
if let Some(display) = unsafe { windowed_context.get_egl_display() } {
|
|
||||||
gl_display = Some(
|
|
||||||
unsafe { gst_gl_egl::GLDisplayEGL::with_egl_display(display as usize) }
|
|
||||||
.unwrap()
|
|
||||||
.upcast::<gst_gl::GLDisplay>(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "gst-gl-wayland")]
|
|
||||||
if let Some(display) = inner_window.wayland_display() {
|
|
||||||
gl_display = Some(
|
|
||||||
unsafe {
|
|
||||||
gst_gl_wayland::GLDisplayWayland::with_display(display as usize)
|
|
||||||
}
|
|
||||||
.unwrap()
|
|
||||||
.upcast::<gst_gl::GLDisplay>(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
egl_context as usize,
|
|
||||||
gl_display.expect("Could not retrieve GLDisplay through EGL context and/or Wayland display"),
|
|
||||||
gst_gl::GLPlatform::EGL,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
#[cfg(feature = "gst-gl-x11")]
|
|
||||||
RawHandle::Glx(glx_context) => {
|
|
||||||
let gl_display = if let Some(display) = inner_window.xlib_display() {
|
|
||||||
unsafe { gst_gl_x11::GLDisplayX11::with_display(display as usize) }.unwrap()
|
|
||||||
} else {
|
|
||||||
panic!("X11 window without X Display");
|
|
||||||
};
|
|
||||||
|
|
||||||
(
|
|
||||||
glx_context as usize,
|
|
||||||
gl_display.upcast::<gst_gl::GLDisplay>(),
|
|
||||||
gst_gl::GLPlatform::GLX,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
handler => panic!("Unsupported platform: {handler:?}."),
|
|
||||||
};
|
|
||||||
|
|
||||||
shared_context =
|
|
||||||
unsafe { gst_gl::GLContext::new_wrapped(&gl_display, gl_context, platform, api) }
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
shared_context
|
|
||||||
.activate(true)
|
|
||||||
.expect("Couldn't activate wrapped GL context");
|
|
||||||
|
|
||||||
shared_context.fill_info()?;
|
|
||||||
|
|
||||||
let gl_context = shared_context.clone();
|
|
||||||
let event_proxy = sync::Mutex::new(event_loop.create_proxy());
|
|
||||||
|
|
||||||
#[allow(clippy::single_match)]
|
|
||||||
bus.set_sync_handler(move |_, msg| {
|
|
||||||
match msg.view() {
|
|
||||||
gst::MessageView::NeedContext(ctxt) => {
|
|
||||||
let context_type = ctxt.context_type();
|
|
||||||
if context_type == *gst_gl::GL_DISPLAY_CONTEXT_TYPE {
|
|
||||||
if let Some(el) =
|
|
||||||
msg.src().map(|s| s.downcast_ref::<gst::Element>().unwrap())
|
|
||||||
{
|
|
||||||
let context = gst::Context::new(context_type, true);
|
|
||||||
context.set_gl_display(&gl_display);
|
|
||||||
el.set_context(&context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if context_type == "gst.gl.app_context" {
|
})
|
||||||
if let Some(el) =
|
.unwrap()
|
||||||
msg.src().map(|s| s.downcast_ref::<gst::Element>().unwrap())
|
})
|
||||||
{
|
.expect("Failed to build display");
|
||||||
let mut context = gst::Context::new(context_type, true);
|
println!(
|
||||||
{
|
"Picked a config with {} samples and transparency {}. Pixel format: {:?}",
|
||||||
let context = context.get_mut().unwrap();
|
gl_config.num_samples(),
|
||||||
let s = context.structure_mut();
|
gl_config.supports_transparency().unwrap_or(false),
|
||||||
s.set("context", &gl_context);
|
gl_config.color_buffer_type()
|
||||||
}
|
);
|
||||||
el.set_context(&context);
|
println!("Config supports GL API(s) {:?}", gl_config.api());
|
||||||
}
|
|
||||||
|
// XXX The display could be obtained from any object created by it, so we can query it from
|
||||||
|
// the config.
|
||||||
|
let gl_display = gl_config.display();
|
||||||
|
let raw_gl_display = gl_display.raw_display();
|
||||||
|
|
||||||
|
println!("Using raw display connection {:?}", raw_gl_display);
|
||||||
|
|
||||||
|
let raw_window_handle = window.as_ref().map(|window| window.raw_window_handle());
|
||||||
|
|
||||||
|
// The context creation part. It can be created before surface and that's how
|
||||||
|
// it's expected in multithreaded + multiwindow operation mode, since you
|
||||||
|
// can send NotCurrentContext, but not Surface.
|
||||||
|
let context_attributes =
|
||||||
|
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
||||||
|
|
||||||
|
// Since glutin by default tries to create OpenGL core context, which may not be
|
||||||
|
// present we should try gles.
|
||||||
|
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||||
|
.with_context_api(glutin::context::ContextApi::Gles(None))
|
||||||
|
.build(raw_window_handle);
|
||||||
|
|
||||||
|
// There are also some old devices that support neither modern OpenGL nor GLES.
|
||||||
|
// To support these we can try and create a 2.1 context.
|
||||||
|
let legacy_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||||
|
.with_context_api(glutin::context::ContextApi::OpenGl(Some(
|
||||||
|
glutin::context::Version::new(2, 1),
|
||||||
|
)))
|
||||||
|
.build(raw_window_handle);
|
||||||
|
|
||||||
|
let not_current_gl_context = unsafe {
|
||||||
|
gl_display
|
||||||
|
.create_context(&gl_config, &context_attributes)
|
||||||
|
.or_else(|_| {
|
||||||
|
gl_display
|
||||||
|
.create_context(&gl_config, &fallback_context_attributes)
|
||||||
|
.or_else(|_| {
|
||||||
|
gl_display.create_context(&gl_config, &legacy_context_attributes)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.context("failed to create context")?;
|
||||||
|
|
||||||
|
let raw_gl_context = not_current_gl_context.raw_context();
|
||||||
|
|
||||||
|
println!("Using raw GL context {:?}", raw_gl_context);
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", windows)))]
|
||||||
|
compile_error!("This example only has Linux and Windows support");
|
||||||
|
|
||||||
|
let api = App::map_gl_api(gl_config.api());
|
||||||
|
|
||||||
|
let (raw_gl_context, gst_gl_display, platform) = match (raw_gl_display, raw_gl_context) {
|
||||||
|
#[cfg(feature = "gst-gl-egl")]
|
||||||
|
(
|
||||||
|
glutin::display::RawDisplay::Egl(egl_display),
|
||||||
|
glutin::context::RawContext::Egl(egl_context),
|
||||||
|
) => {
|
||||||
|
let gl_display =
|
||||||
|
unsafe { gst_gl_egl::GLDisplayEGL::with_egl_display(egl_display as usize) }
|
||||||
|
.context("Failed to create GLDisplayEGL from raw `EGLDisplay`")?
|
||||||
|
.upcast::<gst_gl::GLDisplay>();
|
||||||
|
(egl_context as usize, gl_display, gst_gl::GLPlatform::EGL)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "gst-gl-x11")]
|
||||||
|
(
|
||||||
|
glutin::display::RawDisplay::Glx(glx_display),
|
||||||
|
glutin::context::RawContext::Glx(glx_context),
|
||||||
|
) => {
|
||||||
|
let gl_display =
|
||||||
|
unsafe { gst_gl_x11::GLDisplayX11::with_display(glx_display as usize) }
|
||||||
|
.context("Failed to create GLDisplayX11 from raw X11 `Display`")?
|
||||||
|
.upcast::<gst_gl::GLDisplay>();
|
||||||
|
(glx_context as usize, gl_display, gst_gl::GLPlatform::GLX)
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
(glutin::display::RawDisplay::Wgl, glutin::context::RawContext::Wgl(wgl_context)) => {
|
||||||
|
let gl_display = gst_gl::GLDisplay::new();
|
||||||
|
(wgl_context as usize, gl_display, gst_gl::GLPlatform::WGL)
|
||||||
|
}
|
||||||
|
#[allow(unreachable_patterns)]
|
||||||
|
handler => anyhow::bail!("Unsupported platform: {handler:?}."),
|
||||||
|
};
|
||||||
|
|
||||||
|
let shared_context = unsafe {
|
||||||
|
gst_gl::GLContext::new_wrapped(&gst_gl_display, raw_gl_context, platform, api)
|
||||||
|
}
|
||||||
|
.context("Couldn't wrap GL context")?;
|
||||||
|
|
||||||
|
let gl_context = shared_context.clone();
|
||||||
|
// FIXME: Once MSRV is 1.72 the Mutex is not necessary anymore because
|
||||||
|
// std::sync::mpsc::Sender is Sync by itself
|
||||||
|
let event_proxy = Mutex::new(event_loop.create_proxy());
|
||||||
|
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
bus.set_sync_handler(move |_, msg| {
|
||||||
|
match msg.view() {
|
||||||
|
gst::MessageView::NeedContext(ctxt) => {
|
||||||
|
let context_type = ctxt.context_type();
|
||||||
|
if context_type == *gst_gl::GL_DISPLAY_CONTEXT_TYPE {
|
||||||
|
if let Some(el) =
|
||||||
|
msg.src().map(|s| s.downcast_ref::<gst::Element>().unwrap())
|
||||||
|
{
|
||||||
|
let context = gst::Context::new(context_type, true);
|
||||||
|
context.set_gl_display(&gst_gl_display);
|
||||||
|
el.set_context(&context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if context_type == "gst.gl.app_context" {
|
||||||
|
if let Some(el) =
|
||||||
|
msg.src().map(|s| s.downcast_ref::<gst::Element>().unwrap())
|
||||||
|
{
|
||||||
|
let mut context = gst::Context::new(context_type, true);
|
||||||
|
{
|
||||||
|
let context = context.get_mut().unwrap();
|
||||||
|
let s = context.structure_mut();
|
||||||
|
s.set("context", &gl_context);
|
||||||
|
}
|
||||||
|
el.set_context(&context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
if let Err(e) = event_proxy.lock().unwrap().send_event(Message::BusEvent) {
|
if let Err(e) = event_proxy.lock().unwrap().send_event(Message::BusEvent) {
|
||||||
eprintln!("Failed to send BusEvent to event proxy: {e}")
|
eprintln!("Failed to send BusEvent to event proxy: {e}")
|
||||||
}
|
}
|
||||||
|
|
||||||
gst::BusSyncReply::Pass
|
gst::BusSyncReply::Pass
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
panic!("This example only has Linux support");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(App {
|
Ok(App {
|
||||||
pipeline,
|
pipeline,
|
||||||
appsink,
|
appsink,
|
||||||
bus,
|
bus,
|
||||||
event_loop,
|
event_loop,
|
||||||
windowed_context,
|
window,
|
||||||
|
not_current_gl_context: Some(not_current_gl_context),
|
||||||
shared_context,
|
shared_context,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&self, event_loop: &glutin::event_loop::EventLoop<Message>) -> Result<(), Error> {
|
fn setup(&self, event_loop: &winit::event_loop::EventLoop<Message>) -> Result<()> {
|
||||||
let event_proxy = event_loop.create_proxy();
|
let event_proxy = event_loop.create_proxy();
|
||||||
self.appsink.set_callbacks(
|
self.appsink.set_callbacks(
|
||||||
gst_app::AppSinkCallbacks::builder()
|
gst_app::AppSinkCallbacks::builder()
|
||||||
|
@ -521,22 +595,33 @@ impl App {
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self.pipeline.set_state(gst::State::Playing)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_gl_api(api: glutin::Api) -> gst_gl::GLAPI {
|
/// Converts from <https://docs.rs/glutin/latest/glutin/config/struct.Api.html> to
|
||||||
match api {
|
/// <https://gstreamer.freedesktop.org/documentation/gl/gstglapi.html?gi-language=c#GstGLAPI>.
|
||||||
glutin::Api::OpenGl => gst_gl::GLAPI::OPENGL3,
|
fn map_gl_api(api: glutin::config::Api) -> gst_gl::GLAPI {
|
||||||
glutin::Api::OpenGlEs => gst_gl::GLAPI::GLES2,
|
use glutin::config::Api;
|
||||||
_ => gst_gl::GLAPI::empty(),
|
use gst_gl::GLAPI;
|
||||||
}
|
|
||||||
|
let mut gst_gl_api = GLAPI::empty();
|
||||||
|
// In gstreamer:
|
||||||
|
// GLAPI::OPENGL: Desktop OpenGL up to and including 3.1. The compatibility profile when the OpenGL version is >= 3.2
|
||||||
|
// GLAPI::OPENGL3: Desktop OpenGL >= 3.2 core profile
|
||||||
|
// In glutin, API::OPENGL is set for every context API, except EGL where it is set based on
|
||||||
|
// EGL_RENDERABLE_TYPE containing EGL_OPENGL_BIT:
|
||||||
|
// https://registry.khronos.org/EGL/sdk/docs/man/html/eglChooseConfig.xhtml
|
||||||
|
gst_gl_api.set(GLAPI::OPENGL | GLAPI::OPENGL3, api.contains(Api::OPENGL));
|
||||||
|
gst_gl_api.set(GLAPI::GLES1, api.contains(Api::GLES1));
|
||||||
|
// OpenGL ES 2.x and 3.x
|
||||||
|
gst_gl_api.set(GLAPI::GLES2, api.intersects(Api::GLES2 | Api::GLES3));
|
||||||
|
|
||||||
|
gst_gl_api
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_pipeline(
|
fn create_pipeline(
|
||||||
gl_element: Option<&gst::Element>,
|
gl_element: Option<&gst::Element>,
|
||||||
) -> Result<(gst::Pipeline, gst_app::AppSink), Error> {
|
) -> Result<(gst::Pipeline, gst_app::AppSink)> {
|
||||||
let pipeline = gst::Pipeline::default();
|
let pipeline = gst::Pipeline::default();
|
||||||
let src = gst::ElementFactory::make("videotestsrc").build()?;
|
let src = gst::ElementFactory::make("videotestsrc").build()?;
|
||||||
|
|
||||||
|
@ -555,7 +640,7 @@ impl App {
|
||||||
if let Some(gl_element) = gl_element {
|
if let Some(gl_element) = gl_element {
|
||||||
let glupload = gst::ElementFactory::make("glupload").build()?;
|
let glupload = gst::ElementFactory::make("glupload").build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &glupload])?;
|
pipeline.add_many([&src, &glupload])?;
|
||||||
pipeline.add(gl_element)?;
|
pipeline.add(gl_element)?;
|
||||||
pipeline.add(&appsink)?;
|
pipeline.add(&appsink)?;
|
||||||
|
|
||||||
|
@ -569,14 +654,14 @@ impl App {
|
||||||
.property("sink", &appsink)
|
.property("sink", &appsink)
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
pipeline.add_many(&[&src, &sink])?;
|
pipeline.add_many([&src, &sink])?;
|
||||||
src.link(&sink)?;
|
src.link(&sink)?;
|
||||||
|
|
||||||
Ok((pipeline, appsink))
|
Ok((pipeline, appsink))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_messages(bus: &gst::Bus) -> Result<(), Error> {
|
fn handle_messages(bus: &gst::Bus) -> Result<()> {
|
||||||
use gst::MessageView;
|
use gst::MessageView;
|
||||||
|
|
||||||
for msg in bus.iter() {
|
for msg in bus.iter() {
|
||||||
|
@ -601,77 +686,143 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn main_loop(app: App) -> Result<(), Error> {
|
pub(crate) fn main_loop(app: App) -> Result<()> {
|
||||||
app.setup(&app.event_loop)?;
|
app.setup(&app.event_loop)?;
|
||||||
|
|
||||||
println!(
|
|
||||||
"Pixel format of the window's GL context {:?}",
|
|
||||||
app.windowed_context.get_pixel_format()
|
|
||||||
);
|
|
||||||
|
|
||||||
let gl = load(&app.windowed_context);
|
|
||||||
|
|
||||||
let mut curr_frame: Option<gst_video::VideoFrame<gst_video::video_frame::Readable>> = None;
|
|
||||||
|
|
||||||
let App {
|
let App {
|
||||||
|
pipeline,
|
||||||
bus,
|
bus,
|
||||||
event_loop,
|
event_loop,
|
||||||
pipeline,
|
mut window,
|
||||||
|
mut not_current_gl_context,
|
||||||
shared_context,
|
shared_context,
|
||||||
windowed_context,
|
|
||||||
..
|
..
|
||||||
} = app;
|
} = app;
|
||||||
|
|
||||||
event_loop.run(move |event, _, cf| {
|
let mut curr_frame: Option<gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>> = None;
|
||||||
*cf = glutin::event_loop::ControlFlow::Wait;
|
|
||||||
|
let mut running_state = None::<(
|
||||||
|
Gl,
|
||||||
|
glutin::context::PossiblyCurrentContext,
|
||||||
|
glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||||
|
)>;
|
||||||
|
|
||||||
|
Ok(event_loop.run(move |event, window_target| {
|
||||||
|
window_target.set_control_flow(winit::event_loop::ControlFlow::Wait);
|
||||||
|
|
||||||
let mut needs_redraw = false;
|
let mut needs_redraw = false;
|
||||||
match event {
|
match event {
|
||||||
glutin::event::Event::LoopDestroyed => {
|
winit::event::Event::LoopExiting => {
|
||||||
pipeline.send_event(gst::event::Eos::new());
|
pipeline.send_event(gst::event::Eos::new());
|
||||||
pipeline.set_state(gst::State::Null).unwrap();
|
pipeline.set_state(gst::State::Null).unwrap();
|
||||||
}
|
}
|
||||||
glutin::event::Event::WindowEvent { event, .. } => match event {
|
winit::event::Event::WindowEvent { event, .. } => match event {
|
||||||
glutin::event::WindowEvent::CloseRequested
|
winit::event::WindowEvent::CloseRequested
|
||||||
| glutin::event::WindowEvent::KeyboardInput {
|
| winit::event::WindowEvent::KeyboardInput {
|
||||||
input:
|
event:
|
||||||
glutin::event::KeyboardInput {
|
winit::event::KeyEvent {
|
||||||
state: glutin::event::ElementState::Released,
|
state: winit::event::ElementState::Released,
|
||||||
virtual_keycode: Some(glutin::event::VirtualKeyCode::Escape),
|
logical_key:
|
||||||
|
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
} => *cf = glutin::event_loop::ControlFlow::Exit,
|
} => window_target.exit(),
|
||||||
glutin::event::WindowEvent::Resized(physical_size) => {
|
winit::event::WindowEvent::Resized(size) => {
|
||||||
windowed_context.resize(physical_size);
|
// Some platforms like EGL require resizing GL surface to update the size
|
||||||
gl.resize(physical_size);
|
// Notable platforms here are Wayland and macOS, other don't require it
|
||||||
|
// and the function is no-op, but it's wise to resize it for portability
|
||||||
|
// reasons.
|
||||||
|
if let Some((gl, gl_context, gl_surface)) = &running_state {
|
||||||
|
gl_surface.resize(
|
||||||
|
gl_context,
|
||||||
|
// XXX Ignore minimizing
|
||||||
|
NonZeroU32::new(size.width).unwrap(),
|
||||||
|
NonZeroU32::new(size.height).unwrap(),
|
||||||
|
);
|
||||||
|
gl.resize(size);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
winit::event::WindowEvent::RedrawRequested => needs_redraw = true,
|
||||||
_ => (),
|
_ => (),
|
||||||
},
|
},
|
||||||
glutin::event::Event::RedrawRequested(_) => needs_redraw = true,
|
|
||||||
// Receive a frame
|
// Receive a frame
|
||||||
glutin::event::Event::UserEvent(Message::Frame(info, buffer)) => {
|
winit::event::Event::UserEvent(Message::Frame(info, buffer)) => {
|
||||||
if let Ok(frame) = gst_video::VideoFrame::from_buffer_readable_gl(buffer, &info) {
|
if let Ok(frame) = gst_gl::GLVideoFrame::from_buffer_readable(buffer, &info) {
|
||||||
curr_frame = Some(frame);
|
curr_frame = Some(frame);
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Handle all pending messages when we are awaken by set_sync_handler
|
// Handle all pending messages when we are awaken by set_sync_handler
|
||||||
glutin::event::Event::UserEvent(Message::BusEvent) => {
|
winit::event::Event::UserEvent(Message::BusEvent) => {
|
||||||
App::handle_messages(&bus).unwrap();
|
App::handle_messages(&bus).unwrap();
|
||||||
}
|
}
|
||||||
|
winit::event::Event::Resumed => {
|
||||||
|
let not_current_gl_context = not_current_gl_context
|
||||||
|
.take()
|
||||||
|
.expect("There must be a NotCurrentContext prior to Event::Resumed");
|
||||||
|
|
||||||
|
let gl_config = not_current_gl_context.config();
|
||||||
|
let gl_display = gl_config.display();
|
||||||
|
|
||||||
|
let window = window.get_or_insert_with(|| {
|
||||||
|
let window_builder = winit::window::WindowBuilder::new().with_transparent(true);
|
||||||
|
glutin_winit::finalize_window(window_target, window_builder, &gl_config)
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
let attrs = window.build_surface_attributes(<_>::default());
|
||||||
|
let gl_surface = unsafe {
|
||||||
|
gl_config
|
||||||
|
.display()
|
||||||
|
.create_window_surface(&gl_config, &attrs)
|
||||||
|
.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make it current.
|
||||||
|
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
|
||||||
|
|
||||||
|
// Tell GStreamer that the context has been made current (for borrowed contexts,
|
||||||
|
// this does not try to make it current again)
|
||||||
|
shared_context.activate(true).unwrap();
|
||||||
|
|
||||||
|
shared_context
|
||||||
|
.fill_info()
|
||||||
|
.expect("Couldn't fill context info");
|
||||||
|
|
||||||
|
// The context needs to be current for the Renderer to set up shaders and buffers.
|
||||||
|
// It also performs function loading, which needs a current context on WGL.
|
||||||
|
let gl = load(&gl_display);
|
||||||
|
|
||||||
|
// Try setting vsync.
|
||||||
|
if let Err(res) = gl_surface.set_swap_interval(
|
||||||
|
&gl_context,
|
||||||
|
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap()),
|
||||||
|
) {
|
||||||
|
eprintln!("Error setting vsync: {res:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pipeline.set_state(gst::State::Playing).unwrap();
|
||||||
|
|
||||||
|
assert!(running_state
|
||||||
|
.replace((gl, gl_context, gl_surface))
|
||||||
|
.is_none());
|
||||||
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
if needs_redraw {
|
if needs_redraw {
|
||||||
if let Some(frame) = curr_frame.as_ref() {
|
if let Some((gl, gl_context, gl_surface)) = &running_state {
|
||||||
let sync_meta = frame.buffer().meta::<gst_gl::GLSyncMeta>().unwrap();
|
if let Some(frame) = curr_frame.as_ref() {
|
||||||
sync_meta.wait(&shared_context);
|
let sync_meta = frame.buffer().meta::<gst_gl::GLSyncMeta>().unwrap();
|
||||||
if let Some(texture) = frame.texture_id(0) {
|
sync_meta.wait(&shared_context);
|
||||||
gl.draw_frame(texture as gl::types::GLuint);
|
if let Ok(texture) = frame.texture_id(0) {
|
||||||
|
gl.draw_frame(texture as gl::types::GLuint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gl_surface.swap_buffers(gl_context).unwrap();
|
||||||
}
|
}
|
||||||
windowed_context.swap_buffers().unwrap();
|
|
||||||
}
|
}
|
||||||
})
|
})?)
|
||||||
}
|
}
|
||||||
|
|
2
gir
2
gir
|
@ -1 +1 @@
|
||||||
Subproject commit 425f84d5af7ff4e599b2528bb0e2f53657feb5cf
|
Subproject commit a11b11f2e403d615edd94497b6fe5b3c5283145b
|
|
@ -1 +1 @@
|
||||||
Subproject commit 4eaad6a722bf41028ea9210d2c356b7078b25008
|
Subproject commit 62da9eb7c4bd5d6091a0eaab0d5e97a51f59fd6d
|
|
@ -1 +1 @@
|
||||||
Subproject commit ae0d1447f5204ddf882ff08d2163b299db5da228
|
Subproject commit db97a3ad67f5d3e46d88ddfb498344cbd3e08731
|
|
@ -1,36 +1,38 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gstreamer-allocators"
|
name = "gstreamer-allocators"
|
||||||
version = "0.20.0"
|
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
categories = ["api-bindings", "multimedia"]
|
|
||||||
description = "Rust bindings for GStreamer Allocators library"
|
description = "Rust bindings for GStreamer Allocators library"
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
homepage = "https://gstreamer.freedesktop.org"
|
|
||||||
documentation = "https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_allocators/"
|
documentation = "https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_allocators/"
|
||||||
keywords = ["gstreamer", "multimedia", "audio", "video", "gnome"]
|
keywords = ["gstreamer", "multimedia", "audio", "video", "gnome"]
|
||||||
edition = "2021"
|
version.workspace = true
|
||||||
rust-version = "1.64"
|
categories.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
bitflags = "1.0"
|
gstreamer-allocators-sys.workspace = true
|
||||||
ffi = { package = "gstreamer-allocators-sys", path = "sys" }
|
glib.workspace = true
|
||||||
glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
gst.workspace = true
|
||||||
gst = { package = "gstreamer", path = "../gstreamer" }
|
once_cell = "1"
|
||||||
once_cell = "1.0"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gir-format-check = "0.1"
|
gir-format-check = "0.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
v1_16 = ["gst/v1_16", "ffi/v1_16"]
|
v1_16 = ["gst/v1_16", "gstreamer-allocators-sys/v1_16"]
|
||||||
v1_18 = ["gst/v1_18", "ffi/v1_18", "v1_16"]
|
v1_18 = ["gst/v1_18", "gstreamer-allocators-sys/v1_18", "v1_16"]
|
||||||
v1_20 = ["gst/v1_20", "ffi/v1_20", "v1_18"]
|
v1_20 = ["gst/v1_20", "gstreamer-allocators-sys/v1_20", "v1_18"]
|
||||||
v1_22 = ["gst/v1_22", "ffi/v1_22", "v1_20"]
|
v1_22 = ["gst/v1_22", "gstreamer-allocators-sys/v1_22", "v1_20"]
|
||||||
dox = ["ffi/dox", "glib/dox", "gst/dox"]
|
v1_24 = ["gst/v1_24", "gstreamer-allocators-sys/v1_24", "v1_22"]
|
||||||
|
v1_26 = ["gst/v1_26", "gstreamer-allocators-sys/v1_26", "v1_24"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["dox"]
|
all-features = true
|
||||||
|
rustc-args = ["--cfg", "docsrs"]
|
||||||
|
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
|
||||||
|
|
|
@ -52,6 +52,15 @@ status = "generate"
|
||||||
[[object.function]]
|
[[object.function]]
|
||||||
name = "phys_memory_get_phys_addr"
|
name = "phys_memory_get_phys_addr"
|
||||||
manual = true
|
manual = true
|
||||||
|
[[object.function]]
|
||||||
|
name = "is_drm_dumb_memory"
|
||||||
|
manual = true
|
||||||
|
[[object.function]]
|
||||||
|
name = "drm_dumb_memory_get_handle"
|
||||||
|
manual = true
|
||||||
|
[[object.function]]
|
||||||
|
name = "drm_dumb_memory_export_dmabuf"
|
||||||
|
manual = true
|
||||||
|
|
||||||
[[object]]
|
[[object]]
|
||||||
name = "GstAllocators.DmaBufAllocator"
|
name = "GstAllocators.DmaBufAllocator"
|
||||||
|
@ -64,9 +73,36 @@ cfg_condition = "target_os = \"linux\""
|
||||||
name = "alloc_with_flags"
|
name = "alloc_with_flags"
|
||||||
manual = true
|
manual = true
|
||||||
|
|
||||||
|
[[object]]
|
||||||
|
name = "GstAllocators.DRMDumbAllocator"
|
||||||
|
status = "generate"
|
||||||
|
cfg_condition = "target_os = \"linux\""
|
||||||
|
[[object.function]]
|
||||||
|
name = "alloc"
|
||||||
|
manual = true
|
||||||
|
[[object.function]]
|
||||||
|
name = "new_with_fd"
|
||||||
|
manual = true
|
||||||
|
[[object.function]]
|
||||||
|
name = "new_with_device_path"
|
||||||
|
[object.function.return]
|
||||||
|
nullable_return_is_error = "Failed to create allocator"
|
||||||
|
|
||||||
[[object]]
|
[[object]]
|
||||||
name = "GstAllocators.FdAllocator"
|
name = "GstAllocators.FdAllocator"
|
||||||
status = "generate"
|
status = "generate"
|
||||||
[[object.function]]
|
[[object.function]]
|
||||||
name = "alloc"
|
name = "alloc"
|
||||||
manual = true
|
manual = true
|
||||||
|
|
||||||
|
[[object]]
|
||||||
|
name = "GstAllocators.ShmAllocator"
|
||||||
|
status = "generate"
|
||||||
|
cfg_condition = "unix"
|
||||||
|
[[object.function]]
|
||||||
|
name = "get"
|
||||||
|
manual = true
|
||||||
|
[[object.function]]
|
||||||
|
name = "init_once"
|
||||||
|
manual = true
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
use glib::GStr;
|
use glib::GStr;
|
||||||
|
|
||||||
#[doc(alias = "GST_ALLOCATOR_DMABUF")]
|
#[doc(alias = "GST_ALLOCATOR_DMABUF")]
|
||||||
|
@ -11,6 +12,11 @@ pub static ALLOCATOR_DMABUF: &GStr =
|
||||||
#[doc(alias = "GST_ALLOCATOR_FD")]
|
#[doc(alias = "GST_ALLOCATOR_FD")]
|
||||||
pub static ALLOCATOR_FD: &GStr =
|
pub static ALLOCATOR_FD: &GStr =
|
||||||
unsafe { GStr::from_utf8_with_nul_unchecked(ffi::GST_ALLOCATOR_FD) };
|
unsafe { GStr::from_utf8_with_nul_unchecked(ffi::GST_ALLOCATOR_FD) };
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
#[doc(alias = "GST_ALLOCATOR_SHM")]
|
||||||
|
pub static ALLOCATOR_SHM: &GStr =
|
||||||
|
unsafe { GStr::from_utf8_with_nul_unchecked(ffi::GST_ALLOCATOR_SHM) };
|
||||||
#[doc(alias = "GST_CAPS_FEATURE_MEMORY_DMABUF")]
|
#[doc(alias = "GST_CAPS_FEATURE_MEMORY_DMABUF")]
|
||||||
pub static CAPS_FEATURE_MEMORY_DMABUF: &GStr =
|
pub static CAPS_FEATURE_MEMORY_DMABUF: &GStr =
|
||||||
unsafe { GStr::from_utf8_with_nul_unchecked(ffi::GST_CAPS_FEATURE_MEMORY_DMABUF) };
|
unsafe { GStr::from_utf8_with_nul_unchecked(ffi::GST_CAPS_FEATURE_MEMORY_DMABUF) };
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
use crate::FdAllocator;
|
use crate::{ffi, FdAllocator};
|
||||||
use glib::{prelude::*, translate::*};
|
use glib::{prelude::*, translate::*};
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
|
61
gstreamer-allocators/src/auto/drm_dumb_allocator.rs
Normal file
61
gstreamer-allocators/src/auto/drm_dumb_allocator.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// This file was generated by gir (https://github.com/gtk-rs/gir)
|
||||||
|
// from gir-files (https://github.com/gtk-rs/gir-files)
|
||||||
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
|
// DO NOT EDIT
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
|
use glib::{prelude::*, translate::*};
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
#[doc(alias = "GstDRMDumbAllocator")]
|
||||||
|
pub struct DRMDumbAllocator(Object<ffi::GstDRMDumbAllocator, ffi::GstDRMDumbAllocatorClass>) @extends gst::Allocator;
|
||||||
|
|
||||||
|
match fn {
|
||||||
|
type_ => || ffi::gst_drm_dumb_allocator_get_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DRMDumbAllocator {
|
||||||
|
#[doc(alias = "gst_drm_dumb_allocator_new_with_device_path")]
|
||||||
|
#[doc(alias = "new_with_device_path")]
|
||||||
|
pub fn with_device_path(
|
||||||
|
drm_device_path: impl AsRef<std::path::Path>,
|
||||||
|
) -> Result<DRMDumbAllocator, glib::BoolError> {
|
||||||
|
assert_initialized_main_thread!();
|
||||||
|
unsafe {
|
||||||
|
Option::<gst::Allocator>::from_glib_full(
|
||||||
|
ffi::gst_drm_dumb_allocator_new_with_device_path(
|
||||||
|
drm_device_path.as_ref().to_glib_none().0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map(|o| o.unsafe_cast())
|
||||||
|
.ok_or_else(|| glib::bool_error!("Failed to create allocator"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(alias = "gst_drm_dumb_allocator_has_prime_export")]
|
||||||
|
pub fn has_prime_export(&self) -> bool {
|
||||||
|
unsafe {
|
||||||
|
from_glib(ffi::gst_drm_dumb_allocator_has_prime_export(
|
||||||
|
self.to_glib_none().0,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
#[doc(alias = "drm-device-path")]
|
||||||
|
pub fn drm_device_path(&self) -> Option<std::path::PathBuf> {
|
||||||
|
ObjectExt::property(self, "drm-device-path")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
#[doc(alias = "drm-fd")]
|
||||||
|
pub fn drm_fd(&self) -> i32 {
|
||||||
|
ObjectExt::property(self, "drm-fd")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for DRMDumbAllocator {}
|
||||||
|
unsafe impl Sync for DRMDumbAllocator {}
|
|
@ -3,6 +3,7 @@
|
||||||
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
use glib::{prelude::*, translate::*};
|
use glib::{prelude::*, translate::*};
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
use bitflags::bitflags;
|
use crate::ffi;
|
||||||
use glib::translate::*;
|
use glib::{bitflags::bitflags, translate::*};
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
#[doc(alias = "GstFdMemoryFlags")]
|
#[doc(alias = "GstFdMemoryFlags")]
|
||||||
pub struct FdMemoryFlags: u32 {
|
pub struct FdMemoryFlags: u32 {
|
||||||
#[doc(alias = "GST_FD_MEMORY_FLAG_NONE")]
|
#[doc(alias = "GST_FD_MEMORY_FLAG_NONE")]
|
||||||
|
|
|
@ -3,11 +3,22 @@
|
||||||
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", feature = "dox"))]
|
#[cfg(target_os = "linux")]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
|
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
mod drm_dumb_allocator;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub use self::drm_dumb_allocator::DRMDumbAllocator;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
|
||||||
mod dma_buf_allocator;
|
mod dma_buf_allocator;
|
||||||
#[cfg(any(target_os = "linux", feature = "dox"))]
|
#[cfg(target_os = "linux")]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
|
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
|
||||||
pub use self::dma_buf_allocator::DmaBufAllocator;
|
pub use self::dma_buf_allocator::DmaBufAllocator;
|
||||||
|
|
||||||
mod fd_allocator;
|
mod fd_allocator;
|
||||||
|
@ -16,17 +27,30 @@ pub use self::fd_allocator::FdAllocator;
|
||||||
mod phys_memory_allocator;
|
mod phys_memory_allocator;
|
||||||
pub use self::phys_memory_allocator::PhysMemoryAllocator;
|
pub use self::phys_memory_allocator::PhysMemoryAllocator;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(unix)))]
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
mod shm_allocator;
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(unix)))]
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub use self::shm_allocator::ShmAllocator;
|
||||||
|
|
||||||
mod flags;
|
mod flags;
|
||||||
pub use self::flags::FdMemoryFlags;
|
pub use self::flags::FdMemoryFlags;
|
||||||
|
|
||||||
pub mod functions;
|
pub(crate) mod functions;
|
||||||
|
|
||||||
mod constants;
|
mod constants;
|
||||||
pub use self::constants::ALLOCATOR_DMABUF;
|
pub use self::constants::ALLOCATOR_DMABUF;
|
||||||
pub use self::constants::ALLOCATOR_FD;
|
pub use self::constants::ALLOCATOR_FD;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub use self::constants::ALLOCATOR_SHM;
|
||||||
pub use self::constants::CAPS_FEATURE_MEMORY_DMABUF;
|
pub use self::constants::CAPS_FEATURE_MEMORY_DMABUF;
|
||||||
|
|
||||||
#[doc(hidden)]
|
pub(crate) mod traits {
|
||||||
pub mod traits {
|
|
||||||
pub use super::phys_memory_allocator::PhysMemoryAllocatorExt;
|
pub use super::phys_memory_allocator::PhysMemoryAllocatorExt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
use glib::prelude::*;
|
use glib::prelude::*;
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
@ -21,6 +22,11 @@ impl PhysMemoryAllocator {
|
||||||
unsafe impl Send for PhysMemoryAllocator {}
|
unsafe impl Send for PhysMemoryAllocator {}
|
||||||
unsafe impl Sync for PhysMemoryAllocator {}
|
unsafe impl Sync for PhysMemoryAllocator {}
|
||||||
|
|
||||||
pub trait PhysMemoryAllocatorExt: 'static {}
|
mod sealed {
|
||||||
|
pub trait Sealed {}
|
||||||
|
impl<T: super::IsA<super::PhysMemoryAllocator>> Sealed for T {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait PhysMemoryAllocatorExt: IsA<PhysMemoryAllocator> + sealed::Sealed + 'static {}
|
||||||
|
|
||||||
impl<O: IsA<PhysMemoryAllocator>> PhysMemoryAllocatorExt for O {}
|
impl<O: IsA<PhysMemoryAllocator>> PhysMemoryAllocatorExt for O {}
|
||||||
|
|
20
gstreamer-allocators/src/auto/shm_allocator.rs
Normal file
20
gstreamer-allocators/src/auto/shm_allocator.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// This file was generated by gir (https://github.com/gtk-rs/gir)
|
||||||
|
// from gir-files (https://github.com/gtk-rs/gir-files)
|
||||||
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
|
// DO NOT EDIT
|
||||||
|
|
||||||
|
use crate::{ffi, FdAllocator};
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
#[doc(alias = "GstShmAllocator")]
|
||||||
|
pub struct ShmAllocator(Object<ffi::GstShmAllocator, ffi::GstShmAllocatorClass>) @extends FdAllocator, gst::Allocator;
|
||||||
|
|
||||||
|
match fn {
|
||||||
|
type_ => || ffi::gst_shm_allocator_get_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShmAllocator {}
|
||||||
|
|
||||||
|
unsafe impl Send for ShmAllocator {}
|
||||||
|
unsafe impl Sync for ShmAllocator {}
|
|
@ -1,3 +1,3 @@
|
||||||
Generated by gir (https://github.com/gtk-rs/gir @ 425f84d5af7f)
|
Generated by gir (https://github.com/gtk-rs/gir @ a11b11f2e403)
|
||||||
from gir-files (https://github.com/gtk-rs/gir-files @ 4eaad6a722bf)
|
from gir-files (https://github.com/gtk-rs/gir-files @ 62da9eb7c4bd)
|
||||||
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ ae0d1447f520)
|
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ db97a3ad67f5)
|
||||||
|
|
|
@ -3,13 +3,13 @@ use std::{
|
||||||
os::unix::prelude::{IntoRawFd, RawFd},
|
os::unix::prelude::{IntoRawFd, RawFd},
|
||||||
};
|
};
|
||||||
|
|
||||||
use glib::{translate::*, Cast};
|
use glib::{prelude::*, translate::*};
|
||||||
use gst::{Memory, MemoryRef};
|
use gst::{Memory, MemoryRef};
|
||||||
|
|
||||||
#[cfg(any(feature = "v1_16", feature = "dox"))]
|
#[cfg(feature = "v1_16")]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
use crate::FdMemoryFlags;
|
use crate::FdMemoryFlags;
|
||||||
use crate::{DmaBufAllocator, FdMemory, FdMemoryRef};
|
use crate::{ffi, DmaBufAllocator, FdMemory, FdMemoryRef};
|
||||||
|
|
||||||
gst::memory_object_wrapper!(
|
gst::memory_object_wrapper!(
|
||||||
DmaBufMemory,
|
DmaBufMemory,
|
||||||
|
@ -58,8 +58,8 @@ impl DmaBufAllocator {
|
||||||
.ok_or_else(|| glib::bool_error!("Failed to allocate memory"))
|
.ok_or_else(|| glib::bool_error!("Failed to allocate memory"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(feature = "v1_16", feature = "dox"))]
|
#[cfg(feature = "v1_16")]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
#[doc(alias = "gst_dmabuf_allocator_alloc_with_flags")]
|
#[doc(alias = "gst_dmabuf_allocator_alloc_with_flags")]
|
||||||
pub unsafe fn alloc_with_flags(
|
pub unsafe fn alloc_with_flags(
|
||||||
&self,
|
&self,
|
||||||
|
|
81
gstreamer-allocators/src/drm_dumb_allocator.rs
Normal file
81
gstreamer-allocators/src/drm_dumb_allocator.rs
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
use std::{fmt, mem, os::unix::prelude::IntoRawFd};
|
||||||
|
|
||||||
|
use glib::{prelude::*, translate::*};
|
||||||
|
use gst::{Memory, MemoryRef};
|
||||||
|
|
||||||
|
use crate::{ffi, DRMDumbAllocator, DmaBufMemory};
|
||||||
|
|
||||||
|
gst::memory_object_wrapper!(
|
||||||
|
DRMDumbMemory,
|
||||||
|
DRMDumbMemoryRef,
|
||||||
|
gst::ffi::GstMemory,
|
||||||
|
|mem: &gst::MemoryRef| { unsafe { from_glib(ffi::gst_is_drm_dumb_memory(mem.as_mut_ptr())) } },
|
||||||
|
Memory,
|
||||||
|
MemoryRef
|
||||||
|
);
|
||||||
|
|
||||||
|
impl fmt::Debug for DRMDumbMemory {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
DRMDumbMemoryRef::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for DRMDumbMemoryRef {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
MemoryRef::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DRMDumbMemoryRef {
|
||||||
|
#[doc(alias = "gst_drm_dumb_memory_get_handle")]
|
||||||
|
pub fn fd(&self) -> u32 {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
unsafe { ffi::gst_drm_dumb_memory_get_handle(self.as_mut_ptr()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(alias = "gst_drm_dumb_memory_export_dmabuf")]
|
||||||
|
pub fn export_dmabuf(&self) -> Result<DmaBufMemory, glib::BoolError> {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
unsafe {
|
||||||
|
Option::<DmaBufMemory>::from_glib_full(ffi::gst_drm_dumb_memory_export_dmabuf(
|
||||||
|
self.as_mut_ptr(),
|
||||||
|
))
|
||||||
|
.ok_or_else(|| glib::bool_error!("Failed to export as dmabuf"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DRMDumbAllocator {
|
||||||
|
#[doc(alias = "gst_drm_dumb_allocator_new_with_fd")]
|
||||||
|
#[doc(alias = "new_with_fd")]
|
||||||
|
pub fn with_fd<A: IntoRawFd>(drm_fd: A) -> Result<DRMDumbAllocator, glib::BoolError> {
|
||||||
|
assert_initialized_main_thread!();
|
||||||
|
unsafe {
|
||||||
|
Option::<gst::Allocator>::from_glib_full(ffi::gst_drm_dumb_allocator_new_with_fd(
|
||||||
|
drm_fd.into_raw_fd(),
|
||||||
|
))
|
||||||
|
.map(|o| o.unsafe_cast())
|
||||||
|
.ok_or_else(|| glib::bool_error!("Failed to create allocator"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(alias = "gst_drm_dumb_allocator_alloc")]
|
||||||
|
pub unsafe fn alloc(
|
||||||
|
&self,
|
||||||
|
drm_fourcc: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> Result<(gst::Memory, u32), glib::BoolError> {
|
||||||
|
skip_assert_initialized!();
|
||||||
|
let mut out_pitch = mem::MaybeUninit::uninit();
|
||||||
|
Option::<_>::from_glib_full(ffi::gst_drm_dumb_allocator_alloc(
|
||||||
|
self.to_glib_none().0,
|
||||||
|
drm_fourcc,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
out_pitch.as_mut_ptr(),
|
||||||
|
))
|
||||||
|
.ok_or_else(|| glib::bool_error!("Failed to allocate memory"))
|
||||||
|
.map(|mem| (mem, unsafe { out_pitch.assume_init() }))
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
use std::{fmt, os::unix::prelude::RawFd};
|
use std::{fmt, os::unix::prelude::RawFd};
|
||||||
|
|
||||||
use glib::{translate::*, Cast};
|
use glib::{prelude::*, translate::*};
|
||||||
use gst::{Memory, MemoryRef};
|
use gst::{Memory, MemoryRef};
|
||||||
|
|
||||||
use crate::{FdAllocator, FdMemoryFlags};
|
use crate::{ffi, FdAllocator, FdMemoryFlags};
|
||||||
|
|
||||||
gst::memory_object_wrapper!(
|
gst::memory_object_wrapper!(
|
||||||
FdMemory,
|
FdMemory,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// Take a look at the license at the top of the repository in the LICENSE file.
|
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||||
|
|
||||||
#![cfg_attr(feature = "dox", feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
#![allow(clippy::missing_safety_doc)]
|
#![allow(clippy::missing_safety_doc)]
|
||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
pub use ffi;
|
|
||||||
pub use glib;
|
pub use glib;
|
||||||
pub use gst;
|
pub use gst;
|
||||||
|
pub use gstreamer_allocators_sys as ffi;
|
||||||
|
|
||||||
macro_rules! assert_initialized_main_thread {
|
macro_rules! assert_initialized_main_thread {
|
||||||
() => {
|
() => {
|
||||||
|
@ -30,13 +30,24 @@ pub use crate::caps_features::CAPS_FEATURES_MEMORY_DMABUF;
|
||||||
mod fd_allocator;
|
mod fd_allocator;
|
||||||
pub use fd_allocator::*;
|
pub use fd_allocator::*;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", feature = "dox"))]
|
#[cfg(any(target_os = "linux", docsrs))]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
|
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
|
||||||
mod dma_buf_allocator;
|
mod dma_buf_allocator;
|
||||||
#[cfg(any(target_os = "linux", feature = "dox"))]
|
#[cfg(any(target_os = "linux", docsrs))]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
|
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
|
||||||
pub use dma_buf_allocator::*;
|
pub use dma_buf_allocator::*;
|
||||||
|
|
||||||
|
#[cfg(any(all(feature = "v1_24", target_os = "linux"), docsrs))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(all(feature = "v1_24", target_os = "linux"))))]
|
||||||
|
mod drm_dumb_allocator;
|
||||||
|
#[cfg(any(all(feature = "v1_24", target_os = "linux"), docsrs))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(all(feature = "v1_24", target_os = "linux"))))]
|
||||||
|
pub use drm_dumb_allocator::*;
|
||||||
|
|
||||||
|
#[cfg(any(all(feature = "v1_24", unix), docsrs))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(all(feature = "v1_24", unix))))]
|
||||||
|
mod shm_allocator;
|
||||||
|
|
||||||
mod phys_memory;
|
mod phys_memory;
|
||||||
pub use phys_memory::*;
|
pub use phys_memory::*;
|
||||||
|
|
||||||
|
@ -45,6 +56,8 @@ pub use phys_memory::*;
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use gst::prelude::*;
|
pub use gst::prelude::*;
|
||||||
|
|
||||||
|
pub use crate::auto::traits::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod subclass;
|
pub mod subclass;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::ffi;
|
||||||
use glib::translate::*;
|
use glib::translate::*;
|
||||||
use gst::{Memory, MemoryRef};
|
use gst::{Memory, MemoryRef};
|
||||||
|
|
||||||
|
|
14
gstreamer-allocators/src/shm_allocator.rs
Normal file
14
gstreamer-allocators/src/shm_allocator.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use glib::translate::*;
|
||||||
|
|
||||||
|
use crate::{ffi, ShmAllocator};
|
||||||
|
|
||||||
|
impl ShmAllocator {
|
||||||
|
#[doc(alias = "gst_shm_allocator_get")]
|
||||||
|
pub fn get() -> Option<gst::Allocator> {
|
||||||
|
assert_initialized_main_thread!();
|
||||||
|
unsafe {
|
||||||
|
ffi::gst_shm_allocator_init_once();
|
||||||
|
from_glib_full(ffi::gst_shm_allocator_get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
#[cfg(any(target_os = "linux", feature = "dox"))]
|
#[cfg(any(target_os = "linux", docsrs))]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
|
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
|
||||||
mod dma_buf_allocator;
|
mod dma_buf_allocator;
|
||||||
mod fd_allocator;
|
mod fd_allocator;
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use gst::subclass::prelude::*;
|
pub use gst::subclass::prelude::*;
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", feature = "dox"))]
|
#[cfg(any(target_os = "linux", docsrs))]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
|
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
|
||||||
pub use super::dma_buf_allocator::DmaBufAllocatorImpl;
|
pub use super::dma_buf_allocator::DmaBufAllocatorImpl;
|
||||||
pub use super::fd_allocator::FdAllocatorImpl;
|
pub use super::fd_allocator::FdAllocatorImpl;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,29 @@
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
system-deps = "6"
|
system-deps = "7"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
[dependencies.glib]
|
[dependencies.glib-sys]
|
||||||
git = "https://github.com/gtk-rs/gtk-rs-core"
|
workspace = true
|
||||||
package = "glib-sys"
|
|
||||||
|
|
||||||
[dependencies.gobject]
|
[dependencies.gobject-sys]
|
||||||
git = "https://github.com/gtk-rs/gtk-rs-core"
|
workspace = true
|
||||||
package = "gobject-sys"
|
|
||||||
|
|
||||||
[dependencies.gst]
|
[dependencies.gstreamer-sys]
|
||||||
package = "gstreamer-sys"
|
workspace = true
|
||||||
path = "../../gstreamer/sys"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
shell-words = "1.0.0"
|
shell-words = "1.0.0"
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
dox = ["glib/dox", "gobject/dox", "gst/dox"]
|
|
||||||
v1_16 = []
|
v1_16 = []
|
||||||
v1_18 = ["v1_16"]
|
v1_18 = ["v1_16"]
|
||||||
v1_20 = ["v1_18"]
|
v1_20 = ["v1_18"]
|
||||||
v1_22 = ["v1_20"]
|
v1_22 = ["v1_20"]
|
||||||
|
v1_24 = ["v1_22"]
|
||||||
|
v1_26 = ["v1_24"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstreamer_allocators_sys"
|
name = "gstreamer_allocators_sys"
|
||||||
|
@ -35,18 +33,33 @@ authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
description = "FFI bindings to libgstallocators-1.0"
|
description = "FFI bindings to libgstallocators-1.0"
|
||||||
documentation = "https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_allocators_sys/"
|
documentation = "https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_allocators_sys/"
|
||||||
edition = "2021"
|
|
||||||
homepage = "https://gstreamer.freedesktop.org"
|
|
||||||
keywords = ["ffi", "gstreamer", "gnome", "multimedia"]
|
keywords = ["ffi", "gstreamer", "gnome", "multimedia"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
name = "gstreamer-allocators-sys"
|
name = "gstreamer-allocators-sys"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
|
||||||
rust-version = "1.64"
|
[package.version]
|
||||||
version = "0.20.0"
|
workspace = true
|
||||||
|
|
||||||
|
[package.categories]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[package.repository]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[package.homepage]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[package.edition]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[package.rust-version]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
features = ["dox"]
|
all-features = true
|
||||||
|
rustc-args = ["--cfg", "docsrs"]
|
||||||
|
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
|
||||||
|
|
||||||
[package.metadata.system-deps.gstreamer_allocators_1_0]
|
[package.metadata.system-deps.gstreamer_allocators_1_0]
|
||||||
name = "gstreamer-allocators-1.0"
|
name = "gstreamer-allocators-1.0"
|
||||||
|
@ -63,3 +76,9 @@ version = "1.20"
|
||||||
|
|
||||||
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_22]
|
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_22]
|
||||||
version = "1.22"
|
version = "1.22"
|
||||||
|
|
||||||
|
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_24]
|
||||||
|
version = "1.24"
|
||||||
|
|
||||||
|
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_26]
|
||||||
|
version = "1.25"
|
||||||
|
|
|
@ -7,6 +7,7 @@ work_mode = "sys"
|
||||||
single_version_file = true
|
single_version_file = true
|
||||||
|
|
||||||
extra_versions = [
|
extra_versions = [
|
||||||
|
"1.24",
|
||||||
"1.22",
|
"1.22",
|
||||||
"1.20",
|
"1.20",
|
||||||
"1.18",
|
"1.18",
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
#[cfg(not(feature = "dox"))]
|
#[cfg(not(docsrs))]
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
#[cfg(feature = "dox")]
|
#[cfg(docsrs)]
|
||||||
fn main() {} // prevent linking libraries to avoid documentation failure
|
fn main() {} // prevent linking libraries to avoid documentation failure
|
||||||
|
|
||||||
#[cfg(not(feature = "dox"))]
|
#[cfg(not(docsrs))]
|
||||||
fn main() {
|
fn main() {
|
||||||
if let Err(s) = system_deps::Config::new().probe() {
|
if let Err(s) = system_deps::Config::new().probe() {
|
||||||
println!("cargo:warning={s}");
|
println!("cargo:warning={s}");
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
Generated by gir (https://github.com/gtk-rs/gir @ 425f84d5af7f)
|
Generated by gir (https://github.com/gtk-rs/gir @ a11b11f2e403)
|
||||||
from gir-files (https://github.com/gtk-rs/gir-files @ 4eaad6a722bf)
|
from gir-files (https://github.com/gtk-rs/gir-files @ 62da9eb7c4bd)
|
||||||
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ ae0d1447f520)
|
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ db97a3ad67f5)
|
||||||
|
|
|
@ -10,12 +10,20 @@
|
||||||
clippy::unreadable_literal,
|
clippy::unreadable_literal,
|
||||||
clippy::upper_case_acronyms
|
clippy::upper_case_acronyms
|
||||||
)]
|
)]
|
||||||
#![cfg_attr(feature = "dox", feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
|
use glib_sys as glib;
|
||||||
|
use gobject_sys as gobject;
|
||||||
|
use gstreamer_sys as gst;
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use libc::{
|
use libc::{dev_t, gid_t, pid_t, socklen_t, uid_t};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use libc::{intptr_t, off_t, size_t, ssize_t, time_t, uintptr_t, FILE};
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use std::ffi::{
|
||||||
c_char, c_double, c_float, c_int, c_long, c_short, c_uchar, c_uint, c_ulong, c_ushort, c_void,
|
c_char, c_double, c_float, c_int, c_long, c_short, c_uchar, c_uint, c_ulong, c_ushort, c_void,
|
||||||
intptr_t, size_t, ssize_t, uintptr_t, FILE,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
|
@ -24,6 +32,7 @@ use glib::{gboolean, gconstpointer, gpointer, GType};
|
||||||
// Constants
|
// Constants
|
||||||
pub const GST_ALLOCATOR_DMABUF: &[u8] = b"dmabuf\0";
|
pub const GST_ALLOCATOR_DMABUF: &[u8] = b"dmabuf\0";
|
||||||
pub const GST_ALLOCATOR_FD: &[u8] = b"fd\0";
|
pub const GST_ALLOCATOR_FD: &[u8] = b"fd\0";
|
||||||
|
pub const GST_ALLOCATOR_SHM: &[u8] = b"shm\0";
|
||||||
pub const GST_CAPS_FEATURE_MEMORY_DMABUF: &[u8] = b"memory:DMABuf\0";
|
pub const GST_CAPS_FEATURE_MEMORY_DMABUF: &[u8] = b"memory:DMABuf\0";
|
||||||
|
|
||||||
// Flags
|
// Flags
|
||||||
|
@ -34,6 +43,20 @@ pub const GST_FD_MEMORY_FLAG_MAP_PRIVATE: GstFdMemoryFlags = 2;
|
||||||
pub const GST_FD_MEMORY_FLAG_DONT_CLOSE: GstFdMemoryFlags = 4;
|
pub const GST_FD_MEMORY_FLAG_DONT_CLOSE: GstFdMemoryFlags = 4;
|
||||||
|
|
||||||
// Records
|
// Records
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct GstDRMDumbAllocatorClass {
|
||||||
|
pub parent_class: gst::GstAllocatorClass,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Debug for GstDRMDumbAllocatorClass {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
f.debug_struct(&format!("GstDRMDumbAllocatorClass @ {self:p}"))
|
||||||
|
.field("parent_class", &self.parent_class)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct GstDmaBufAllocatorClass {
|
pub struct GstDmaBufAllocatorClass {
|
||||||
|
@ -79,7 +102,35 @@ impl ::std::fmt::Debug for GstPhysMemoryAllocatorInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct GstShmAllocatorClass {
|
||||||
|
pub parent_class: GstFdAllocatorClass,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Debug for GstShmAllocatorClass {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
f.debug_struct(&format!("GstShmAllocatorClass @ {self:p}"))
|
||||||
|
.field("parent_class", &self.parent_class)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Classes
|
// Classes
|
||||||
|
#[repr(C)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct GstDRMDumbAllocator {
|
||||||
|
_data: [u8; 0],
|
||||||
|
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Debug for GstDRMDumbAllocator {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
f.debug_struct(&format!("GstDRMDumbAllocator @ {self:p}"))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct GstDmaBufAllocator {
|
pub struct GstDmaBufAllocator {
|
||||||
|
@ -109,8 +160,23 @@ impl ::std::fmt::Debug for GstFdAllocator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub struct GstShmAllocator {
|
||||||
|
_data: [u8; 0],
|
||||||
|
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::fmt::Debug for GstShmAllocator {
|
||||||
|
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
|
||||||
|
f.debug_struct(&format!("GstShmAllocator @ {self:p}"))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Interfaces
|
// Interfaces
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct GstPhysMemoryAllocator {
|
pub struct GstPhysMemoryAllocator {
|
||||||
_data: [u8; 0],
|
_data: [u8; 0],
|
||||||
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
|
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
|
||||||
|
@ -122,9 +188,36 @@ impl ::std::fmt::Debug for GstPhysMemoryAllocator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[link(name = "gstallocators-1.0")]
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// GstDRMDumbAllocator
|
||||||
|
//=========================================================================
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_drm_dumb_allocator_get_type() -> GType;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_drm_dumb_allocator_new_with_device_path(
|
||||||
|
drm_device_path: *const c_char,
|
||||||
|
) -> *mut gst::GstAllocator;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_drm_dumb_allocator_new_with_fd(drm_fd: c_int) -> *mut gst::GstAllocator;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_drm_dumb_allocator_alloc(
|
||||||
|
allocator: *mut GstDRMDumbAllocator,
|
||||||
|
drm_fourcc: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
out_pitch: *mut u32,
|
||||||
|
) -> *mut gst::GstMemory;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_drm_dumb_allocator_has_prime_export(allocator: *mut GstDRMDumbAllocator)
|
||||||
|
-> gboolean;
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// GstDmaBufAllocator
|
// GstDmaBufAllocator
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
@ -135,8 +228,8 @@ extern "C" {
|
||||||
fd: c_int,
|
fd: c_int,
|
||||||
size: size_t,
|
size: size_t,
|
||||||
) -> *mut gst::GstMemory;
|
) -> *mut gst::GstMemory;
|
||||||
#[cfg(any(feature = "v1_16", feature = "dox"))]
|
#[cfg(feature = "v1_16")]
|
||||||
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
|
||||||
pub fn gst_dmabuf_allocator_alloc_with_flags(
|
pub fn gst_dmabuf_allocator_alloc_with_flags(
|
||||||
allocator: *mut gst::GstAllocator,
|
allocator: *mut gst::GstAllocator,
|
||||||
fd: c_int,
|
fd: c_int,
|
||||||
|
@ -156,6 +249,19 @@ extern "C" {
|
||||||
flags: GstFdMemoryFlags,
|
flags: GstFdMemoryFlags,
|
||||||
) -> *mut gst::GstMemory;
|
) -> *mut gst::GstMemory;
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// GstShmAllocator
|
||||||
|
//=========================================================================
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_shm_allocator_get_type() -> GType;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_shm_allocator_get() -> *mut gst::GstAllocator;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_shm_allocator_init_once();
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// GstPhysMemoryAllocator
|
// GstPhysMemoryAllocator
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
@ -165,8 +271,17 @@ extern "C" {
|
||||||
// Other functions
|
// Other functions
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
pub fn gst_dmabuf_memory_get_fd(mem: *mut gst::GstMemory) -> c_int;
|
pub fn gst_dmabuf_memory_get_fd(mem: *mut gst::GstMemory) -> c_int;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_drm_dumb_memory_export_dmabuf(mem: *mut gst::GstMemory) -> *mut gst::GstMemory;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_drm_dumb_memory_get_handle(mem: *mut gst::GstMemory) -> u32;
|
||||||
pub fn gst_fd_memory_get_fd(mem: *mut gst::GstMemory) -> c_int;
|
pub fn gst_fd_memory_get_fd(mem: *mut gst::GstMemory) -> c_int;
|
||||||
pub fn gst_is_dmabuf_memory(mem: *mut gst::GstMemory) -> gboolean;
|
pub fn gst_is_dmabuf_memory(mem: *mut gst::GstMemory) -> gboolean;
|
||||||
|
#[cfg(feature = "v1_24")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
|
||||||
|
pub fn gst_is_drm_dumb_memory(mem: *mut gst::GstMemory) -> gboolean;
|
||||||
pub fn gst_is_fd_memory(mem: *mut gst::GstMemory) -> gboolean;
|
pub fn gst_is_fd_memory(mem: *mut gst::GstMemory) -> gboolean;
|
||||||
pub fn gst_is_phys_memory(mem: *mut gst::GstMemory) -> gboolean;
|
pub fn gst_is_phys_memory(mem: *mut gst::GstMemory) -> gboolean;
|
||||||
pub fn gst_phys_memory_get_phys_addr(mem: *mut gst::GstMemory) -> uintptr_t;
|
pub fn gst_phys_memory_get_phys_addr(mem: *mut gst::GstMemory) -> uintptr_t;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
|
||||||
// DO NOT EDIT
|
// DO NOT EDIT
|
||||||
|
|
||||||
#![cfg(target_os = "linux")]
|
#![cfg(unix)]
|
||||||
|
|
||||||
use gstreamer_allocators_sys::*;
|
use gstreamer_allocators_sys::*;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
@ -11,7 +11,7 @@ use std::error::Error;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
use std::mem::{align_of, size_of};
|
use std::mem::{align_of, size_of};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::{Command, Stdio};
|
||||||
use std::str;
|
use std::str;
|
||||||
use tempfile::Builder;
|
use tempfile::Builder;
|
||||||
|
|
||||||
|
@ -71,9 +71,11 @@ fn pkg_config_cflags(packages: &[&str]) -> Result<Vec<String>, Box<dyn Error>> {
|
||||||
let mut cmd = Command::new(pkg_config);
|
let mut cmd = Command::new(pkg_config);
|
||||||
cmd.arg("--cflags");
|
cmd.arg("--cflags");
|
||||||
cmd.args(packages);
|
cmd.args(packages);
|
||||||
|
cmd.stderr(Stdio::inherit());
|
||||||
let out = cmd.output()?;
|
let out = cmd.output()?;
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
return Err(format!("command {cmd:?} returned {}", out.status).into());
|
let (status, stdout) = (out.status, String::from_utf8_lossy(&out.stdout));
|
||||||
|
return Err(format!("command {cmd:?} failed, {status:?}\nstdout: {stdout}").into());
|
||||||
}
|
}
|
||||||
let stdout = str::from_utf8(&out.stdout)?;
|
let stdout = str::from_utf8(&out.stdout)?;
|
||||||
Ok(shell_words::split(stdout.trim())?)
|
Ok(shell_words::split(stdout.trim())?)
|
||||||
|
@ -188,16 +190,25 @@ fn get_c_output(name: &str) -> Result<String, Box<dyn Error>> {
|
||||||
let cc = Compiler::new().expect("configured compiler");
|
let cc = Compiler::new().expect("configured compiler");
|
||||||
cc.compile(&c_file, &exe)?;
|
cc.compile(&c_file, &exe)?;
|
||||||
|
|
||||||
let mut abi_cmd = Command::new(exe);
|
let mut cmd = Command::new(exe);
|
||||||
let output = abi_cmd.output()?;
|
cmd.stderr(Stdio::inherit());
|
||||||
if !output.status.success() {
|
let out = cmd.output()?;
|
||||||
return Err(format!("command {abi_cmd:?} failed, {output:?}").into());
|
if !out.status.success() {
|
||||||
|
let (status, stdout) = (out.status, String::from_utf8_lossy(&out.stdout));
|
||||||
|
return Err(format!("command {cmd:?} failed, {status:?}\nstdout: {stdout}").into());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(String::from_utf8(output.stdout)?)
|
Ok(String::from_utf8(out.stdout)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
const RUST_LAYOUTS: &[(&str, Layout)] = &[
|
const RUST_LAYOUTS: &[(&str, Layout)] = &[
|
||||||
|
(
|
||||||
|
"GstDRMDumbAllocatorClass",
|
||||||
|
Layout {
|
||||||
|
size: size_of::<GstDRMDumbAllocatorClass>(),
|
||||||
|
alignment: align_of::<GstDRMDumbAllocatorClass>(),
|
||||||
|
},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"GstDmaBufAllocator",
|
"GstDmaBufAllocator",
|
||||||
Layout {
|
Layout {
|
||||||
|
@ -240,11 +251,19 @@ const RUST_LAYOUTS: &[(&str, Layout)] = &[
|
||||||
alignment: align_of::<GstPhysMemoryAllocatorInterface>(),
|
alignment: align_of::<GstPhysMemoryAllocatorInterface>(),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"GstShmAllocatorClass",
|
||||||
|
Layout {
|
||||||
|
size: size_of::<GstShmAllocatorClass>(),
|
||||||
|
alignment: align_of::<GstShmAllocatorClass>(),
|
||||||
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
const RUST_CONSTANTS: &[(&str, &str)] = &[
|
const RUST_CONSTANTS: &[(&str, &str)] = &[
|
||||||
("GST_ALLOCATOR_DMABUF", "dmabuf"),
|
("GST_ALLOCATOR_DMABUF", "dmabuf"),
|
||||||
("GST_ALLOCATOR_FD", "fd"),
|
("GST_ALLOCATOR_FD", "fd"),
|
||||||
|
("GST_ALLOCATOR_SHM", "shm"),
|
||||||
("GST_CAPS_FEATURE_MEMORY_DMABUF", "memory:DMABuf"),
|
("GST_CAPS_FEATURE_MEMORY_DMABUF", "memory:DMABuf"),
|
||||||
("(guint) GST_FD_MEMORY_FLAG_DONT_CLOSE", "4"),
|
("(guint) GST_FD_MEMORY_FLAG_DONT_CLOSE", "4"),
|
||||||
("(guint) GST_FD_MEMORY_FLAG_KEEP_MAPPED", "1"),
|
("(guint) GST_FD_MEMORY_FLAG_KEEP_MAPPED", "1"),
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
int main() {
|
int main() {
|
||||||
PRINT_CONSTANT(GST_ALLOCATOR_DMABUF);
|
PRINT_CONSTANT(GST_ALLOCATOR_DMABUF);
|
||||||
PRINT_CONSTANT(GST_ALLOCATOR_FD);
|
PRINT_CONSTANT(GST_ALLOCATOR_FD);
|
||||||
|
PRINT_CONSTANT(GST_ALLOCATOR_SHM);
|
||||||
PRINT_CONSTANT(GST_CAPS_FEATURE_MEMORY_DMABUF);
|
PRINT_CONSTANT(GST_CAPS_FEATURE_MEMORY_DMABUF);
|
||||||
PRINT_CONSTANT((guint) GST_FD_MEMORY_FLAG_DONT_CLOSE);
|
PRINT_CONSTANT((guint) GST_FD_MEMORY_FLAG_DONT_CLOSE);
|
||||||
PRINT_CONSTANT((guint) GST_FD_MEMORY_FLAG_KEEP_MAPPED);
|
PRINT_CONSTANT((guint) GST_FD_MEMORY_FLAG_KEEP_MAPPED);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue