mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer-rs.git
synced 2024-05-19 08:48:09 +00:00
Compare commits
294 commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
|||
target/
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
|
|
|
@ -49,15 +49,14 @@ variables:
|
|||
WINDOWS_RUST_STABLE_IMAGE: "$CI_REGISTRY_IMAGE/windows:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
||||
WINDOWS_RUST_STABLE_UPSTREAM_IMAGE: "$CI_REGISTRY/$FDO_UPSTREAM_REPO/windows:$GST_RS_IMG_TAG-main-$GST_RS_STABLE"
|
||||
|
||||
RUST_DOCS_FLAGS: "--extern-html-root-url=muldiv=https://docs.rs/muldiv/1.0.0/muldiv/ -Z unstable-options --generate-link-to-definition"
|
||||
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
|
||||
# format is <branch>=<name>
|
||||
# the name is used in the URL
|
||||
# latest release must be at the top
|
||||
# (only relevant on main branch)
|
||||
RELEASES:
|
||||
0.20=0.20
|
||||
0.19=0.19
|
||||
0.22=0.22
|
||||
|
||||
stages:
|
||||
- "trigger"
|
||||
|
@ -74,6 +73,7 @@ trigger:
|
|||
stage: 'trigger'
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
tags: [ 'placeholder-job' ]
|
||||
script:
|
||||
- echo "Trigger job done, now running the pipeline."
|
||||
rules:
|
||||
|
@ -96,13 +96,6 @@ trigger:
|
|||
before_script:
|
||||
- source ./ci/env.sh
|
||||
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
||||
# If cargo exists assume we probably will want to update
|
||||
# the lockfile
|
||||
- |
|
||||
if command -v cargo; then
|
||||
cargo generate-lockfile --color=always
|
||||
cargo update --color=always
|
||||
fi
|
||||
|
||||
.debian:12-base:
|
||||
extends: .debian:12
|
||||
|
@ -149,6 +142,7 @@ trigger:
|
|||
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
|
||||
FDO_DISTRIBUTION_EXEC: >-
|
||||
bash ci/install-gst.sh &&
|
||||
bash ci/install-dav1d.sh &&
|
||||
|
@ -319,6 +313,7 @@ test nightly sys:
|
|||
rustfmt:
|
||||
extends: .img-stable
|
||||
stage: "lint"
|
||||
tags: [ 'placeholder-job' ]
|
||||
script:
|
||||
- cargo fmt --version
|
||||
- cargo fmt -- --color=always --check
|
||||
|
@ -329,6 +324,7 @@ rustfmt:
|
|||
check commits:
|
||||
extends: .img-stable
|
||||
stage: "lint"
|
||||
tags: [ 'placeholder-job' ]
|
||||
script:
|
||||
- ci-fairy check-commits --textwidth 0 --no-signed-off-by
|
||||
needs:
|
||||
|
@ -338,6 +334,7 @@ check commits:
|
|||
typos:
|
||||
extends: .img-stable
|
||||
stage: "lint"
|
||||
tags: [ 'placeholder-job' ]
|
||||
script:
|
||||
- typos
|
||||
needs:
|
||||
|
@ -366,13 +363,15 @@ deny:
|
|||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
script:
|
||||
- cargo deny --color=always check
|
||||
- cargo update --color=always
|
||||
- cargo deny --color=always --workspace --all-features check all
|
||||
|
||||
gir-checks:
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
extends: .img-stable
|
||||
stage: 'extras'
|
||||
tags: [ 'placeholder-job' ]
|
||||
needs:
|
||||
- job: 'build-stable'
|
||||
artifacts: false
|
||||
|
@ -389,6 +388,7 @@ outdated:
|
|||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
script:
|
||||
- cargo update --color=always
|
||||
- cargo outdated --color=always --root-deps-only --exit-code 1 -v
|
||||
|
||||
coverage:
|
||||
|
@ -514,7 +514,6 @@ pages:
|
|||
# We also don't need a CONTEXT_DIR var as its also
|
||||
# hardcoded to be windows-docker/
|
||||
DOCKERFILE: 'ci/windows-docker/Dockerfile'
|
||||
GST_UPSTREAM_BRANCH: 'main'
|
||||
tags:
|
||||
- 'windows'
|
||||
- 'shell'
|
||||
|
|
3068
Cargo.lock
generated
Normal file
3068
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
52
Cargo.toml
52
Cargo.toml
|
@ -1,7 +1,9 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
default-members = [
|
||||
"gstreamer/sys",
|
||||
"gstreamer-analytics/sys",
|
||||
"gstreamer-app/sys",
|
||||
"gstreamer-audio/sys",
|
||||
"gstreamer-base/sys",
|
||||
|
@ -21,6 +23,7 @@ default-members = [
|
|||
"gstreamer-video/sys",
|
||||
"gstreamer-webrtc/sys",
|
||||
"gstreamer",
|
||||
"gstreamer-analytics",
|
||||
"gstreamer-app",
|
||||
"gstreamer-audio",
|
||||
"gstreamer-base",
|
||||
|
@ -36,6 +39,7 @@ default-members = [
|
|||
"gstreamer-rtsp",
|
||||
"gstreamer-rtsp-server",
|
||||
"gstreamer-sdp",
|
||||
"gstreamer-tag",
|
||||
"gstreamer-validate",
|
||||
"gstreamer-video",
|
||||
"gstreamer-webrtc",
|
||||
|
@ -45,6 +49,7 @@ default-members = [
|
|||
|
||||
members = [
|
||||
"gstreamer/sys",
|
||||
"gstreamer-analytics/sys",
|
||||
"gstreamer-app/sys",
|
||||
"gstreamer-audio/sys",
|
||||
"gstreamer-base/sys",
|
||||
|
@ -69,6 +74,7 @@ members = [
|
|||
"gstreamer-webrtc/sys",
|
||||
"gstreamer-allocators/sys",
|
||||
"gstreamer",
|
||||
"gstreamer-analytics",
|
||||
"gstreamer-app",
|
||||
"gstreamer-audio",
|
||||
"gstreamer-base",
|
||||
|
@ -88,6 +94,7 @@ members = [
|
|||
"gstreamer-rtsp",
|
||||
"gstreamer-rtsp-server",
|
||||
"gstreamer-sdp",
|
||||
"gstreamer-tag",
|
||||
"gstreamer-validate",
|
||||
"gstreamer-video",
|
||||
"gstreamer-webrtc",
|
||||
|
@ -98,3 +105,48 @@ members = [
|
|||
]
|
||||
|
||||
exclude = ["gir"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.23.0"
|
||||
categories = ["api-bindings", "multimedia"]
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
||||
homepage = "https://gstreamer.freedesktop.org"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
|
||||
[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-audio-sys = { path = "./gstreamer-audio/sys"}
|
||||
gstreamer-base-sys = { path = "./gstreamer-base/sys"}
|
||||
gstreamer-gl-sys = { path = "./gstreamer-gl/sys"}
|
||||
gstreamer-net-sys = { path = "./gstreamer-net/sys"}
|
||||
gstreamer-pbutils-sys = { path = "./gstreamer-pbutils/sys"}
|
||||
gstreamer-rtsp-sys = { path = "./gstreamer-rtsp/sys"}
|
||||
gstreamer-sdp-sys = { path = "./gstreamer-sdp/sys"}
|
||||
gstreamer-sys = { path = "./gstreamer/sys"}
|
||||
gstreamer-video-sys = { path = "./gstreamer-video/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,7 @@
|
|||
variables:
|
||||
GST_RS_IMG_TAG: "2023-08-07.0"
|
||||
GST_RS_STABLE: "1.71.1"
|
||||
GST_RS_IMG_TAG: "2024-05-10.0"
|
||||
GST_RS_STABLE: "1.78.0"
|
||||
GST_RS_MSRV: "1.70.0"
|
||||
# The branch we use to build GStreamer from in the docker images
|
||||
# Ex. main, 1.24, my-test-branch
|
||||
GST_UPSTREAM_BRANCH: 'main'
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
set -e
|
||||
|
||||
RELEASE=1.1.0
|
||||
RELEASE=1.4.1
|
||||
|
||||
git clone https://code.videolan.org/videolan/dav1d.git --branch $RELEASE
|
||||
cd dav1d
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
set -e
|
||||
|
||||
DEFAULT_BRANCH="$GST_UPSTREAM_BRANCH"
|
||||
|
||||
pip3 install meson==1.1.1 --break-system-packages
|
||||
|
||||
# gstreamer-rs already has a 'gstreamer' directory so don't clone there
|
||||
|
@ -9,7 +11,7 @@ pushd .
|
|||
cd ..
|
||||
git clone https://gitlab.freedesktop.org/gstreamer/gstreamer.git \
|
||||
--depth 1 \
|
||||
--branch main
|
||||
--branch "$DEFAULT_BRANCH"
|
||||
|
||||
cd gstreamer
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ source ./ci/env.sh
|
|||
set -e
|
||||
export CARGO_HOME='/usr/local/cargo'
|
||||
|
||||
RUSTUP_VERSION=1.26.0
|
||||
RUSTUP_VERSION=1.27.1
|
||||
RUST_VERSION=$1
|
||||
RUST_IMAGE_FULL=$2
|
||||
RUST_ARCH="x86_64-unknown-linux-gnu"
|
||||
|
@ -26,20 +26,26 @@ if [ "$RUST_IMAGE_FULL" = "1" ]; then
|
|||
rustup component add clippy-preview
|
||||
rustup component add rustfmt
|
||||
|
||||
cargo install --force cargo-deny
|
||||
cargo install --force cargo-outdated
|
||||
cargo install --force typos-cli
|
||||
cargo install --locked --force cargo-deny
|
||||
cargo install --locked --force cargo-outdated
|
||||
cargo install --locked --force typos-cli --version "1.19.0"
|
||||
|
||||
# Coverage tools
|
||||
rustup component add llvm-tools-preview
|
||||
cargo install --force grcov
|
||||
cargo install --locked --force grcov
|
||||
fi
|
||||
|
||||
cargo install cargo-c --version 0.9.22+cargo-0.72
|
||||
if [ "$RUST_VERSION" = "nightly" ]; then
|
||||
# FIXME: Don't build cargo-c with --locked for now because otherwise a
|
||||
# version of ahash is used that doesn't build on nightly anymore
|
||||
cargo install cargo-c --version 0.9.22+cargo-0.72
|
||||
else
|
||||
cargo install --locked cargo-c --version 0.9.22+cargo-0.72
|
||||
fi
|
||||
|
||||
if [ "$RUST_VERSION" = "nightly" ]; then
|
||||
rustup component add rustfmt --toolchain nightly
|
||||
|
||||
# Documentation tools
|
||||
cargo install --force rustdoc-stripper
|
||||
cargo install --locked --force rustdoc-stripper
|
||||
fi
|
||||
|
|
|
@ -23,7 +23,7 @@ done
|
|||
if [ -n "$EXAMPLES_TUTORIALS" ]; then
|
||||
# Keep in sync with examples/Cargo.toml
|
||||
# List all features except windows/win32
|
||||
EXAMPLES_FEATURES="--features=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 --locked --color=always --manifest-path tutorials/Cargo.toml --bins --examples --all-features
|
||||
|
|
|
@ -11,13 +11,10 @@ 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_24"
|
||||
;;
|
||||
gstreamer-validate)
|
||||
echo ""
|
||||
echo "--features=serde,v1_26"
|
||||
;;
|
||||
*)
|
||||
echo "--features=v1_24"
|
||||
echo "--features=v1_26"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
@ -34,7 +31,7 @@ done
|
|||
|
||||
# Keep in sync with examples/Cargo.toml
|
||||
# List all features except windows/win32
|
||||
EXAMPLES_FEATURES="--features=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
|
||||
cargo clippy --locked --color=always --manifest-path examples/Cargo.toml --all-targets "$EXAMPLES_FEATURES" -- $CLIPPY_LINTS
|
||||
|
|
|
@ -13,11 +13,14 @@ for crate in gstreamer*/sys gstreamer-gl/*/sys; do
|
|||
done
|
||||
|
||||
for crate in gstreamer/sys \
|
||||
gstreamer-allocators/sys \
|
||||
gstreamer-analytics/sys \
|
||||
gstreamer-app/sys \
|
||||
gstreamer-audio/sys \
|
||||
gstreamer-base/sys \
|
||||
gstreamer-check/sys \
|
||||
gstreamer-controller/sys \
|
||||
gstreamer-editing-services/sys \
|
||||
gstreamer-gl/sys \
|
||||
gstreamer-gl/egl/sys \
|
||||
gstreamer-gl/wayland/sys \
|
||||
|
@ -25,11 +28,14 @@ for crate in gstreamer/sys \
|
|||
gstreamer-mpegts/sys \
|
||||
gstreamer-net/sys \
|
||||
gstreamer-pbutils/sys \
|
||||
gstreamer-play/sys \
|
||||
gstreamer-player/sys \
|
||||
gstreamer-rtp/sys \
|
||||
gstreamer-rtsp-server/sys \
|
||||
gstreamer-rtsp/sys \
|
||||
gstreamer-sdp/sys \
|
||||
gstreamer-tag/sys \
|
||||
gstreamer-validate/sys \
|
||||
gstreamer-video/sys \
|
||||
gstreamer-webrtc/sys; do
|
||||
echo "Testing $crate with --all-features)"
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
# 'gstreamer-gl/egl',
|
||||
# 'gstreamer-gl/wayland',
|
||||
# 'gstreamer-gl/x11',
|
||||
# only has sys
|
||||
# 'gstreamer-mpegts',
|
||||
'gstreamer-mpegts',
|
||||
'gstreamer-mpegts/sys',
|
||||
'gstreamer-net',
|
||||
'gstreamer-pbutils',
|
||||
|
@ -26,8 +25,7 @@
|
|||
'gstreamer-rtsp',
|
||||
'gstreamer-rtsp-server',
|
||||
'gstreamer-sdp',
|
||||
# only has sys
|
||||
# 'gstreamer-tag',
|
||||
'gstreamer-tag',
|
||||
'gstreamer-tag/sys',
|
||||
'gstreamer-video',
|
||||
'gstreamer-webrtc',
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
FROM "registry.freedesktop.org/gstreamer/gstreamer/amd64/windows:2023-07-17.0-main"
|
||||
|
||||
# Make sure any failure in PowerShell is fatal
|
||||
ENV ErrorActionPreference='Stop'
|
||||
SHELL ["powershell","-NoLogo", "-NonInteractive", "-Command"]
|
||||
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
|
||||
|
||||
ARG DEFAULT_BRANCH="main"
|
||||
ARG DEFAULT_BRANCH="1.24"
|
||||
ARG RUST_VERSION="invalid"
|
||||
|
||||
RUN choco install -y pkgconfiglite nasm llvm openssl
|
||||
|
@ -21,4 +19,4 @@ RUN C:\install_dav1d.ps1
|
|||
|
||||
RUN Invoke-WebRequest -Uri https://win.rustup.rs/x86_64 -OutFile C:\rustup-init.exe
|
||||
RUN C:\rustup-init.exe -y --profile minimal --default-toolchain $env:RUST_VERSION
|
||||
RUN cargo install cargo-c --version 0.9.22+cargo-0.72
|
||||
RUN cargo install --locked cargo-c --version 0.9.22+cargo-0.72
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
||||
|
||||
# Download gstreamer and all its subprojects
|
||||
git clone -b 1.1.0 --depth 1 https://code.videolan.org/videolan/dav1d.git C:\dav1d
|
||||
git clone -b 1.4.1 --depth 1 https://code.videolan.org/videolan/dav1d.git C:\dav1d
|
||||
if (!$?) {
|
||||
Write-Host "Failed to clone dav1d"
|
||||
Exit 1
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
|
||||
# Make sure powershell exits on errors
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Download gstreamer and all its subprojects
|
||||
git clone -b $env:DEFAULT_BRANCH --depth 1 https://gitlab.freedesktop.org/gstreamer/gstreamer.git C:\gstreamer
|
||||
if (!$?) {
|
||||
Write-Host "Failed to clone gstreamer"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
Set-Location C:\gstreamer
|
||||
|
||||
|
@ -13,6 +15,10 @@ Move-Item C:/subprojects/* C:\gstreamer\subprojects
|
|||
# Update the subprojects cache
|
||||
Write-Output "Running meson subproject reset"
|
||||
meson subprojects update --reset
|
||||
if (!$?) {
|
||||
Write-Host "Failed to update gstreamer subprojects"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$MESON_ARGS = @(`
|
||||
"--prefix=C:\gst-install", `
|
||||
|
@ -42,9 +48,24 @@ echo "subproject('gtk')" >> meson.build
|
|||
|
||||
Write-Output "Building gstreamer"
|
||||
meson setup --vsenv $MESON_ARGS _build
|
||||
if (!$?) {
|
||||
type "_build\meson-logs\meson-log.txt"
|
||||
Write-Host "Failed to run meson setup, see log above"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
Write-Output "Compiling gstreamer"
|
||||
meson compile -C _build
|
||||
if (!$?) {
|
||||
Write-Host "Failed to run meson compile"
|
||||
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
|
||||
|
|
36
deny.toml
36
deny.toml
|
@ -1,3 +1,8 @@
|
|||
exclude = [
|
||||
"examples",
|
||||
"tutorials",
|
||||
]
|
||||
|
||||
[advisories]
|
||||
db-path = "~/.cargo/advisory-db"
|
||||
db-urls = ["https://github.com/rustsec/advisory-db"]
|
||||
|
@ -8,16 +13,7 @@ ignore = []
|
|||
|
||||
[licenses]
|
||||
unlicensed = "deny"
|
||||
allow = [
|
||||
"Apache-2.0",
|
||||
]
|
||||
deny = [
|
||||
"GPL-1.0",
|
||||
"GPL-2.0",
|
||||
"GPL-3.0",
|
||||
"AGPL-1.0",
|
||||
"AGPL-3.0",
|
||||
]
|
||||
default = "deny"
|
||||
copyleft = "deny"
|
||||
allow-osi-fsf-free = "either"
|
||||
confidence-threshold = 0.8
|
||||
|
@ -27,13 +23,11 @@ multiple-versions = "deny"
|
|||
wildcards = "allow"
|
||||
highlight = "all"
|
||||
|
||||
# Various cocoa crates are in the middle of updating to newer versions
|
||||
# proc-macro-crate depends on an older version of toml_edit
|
||||
# https://github.com/bkchr/proc-macro-crate/pull/50
|
||||
[[bans.skip]]
|
||||
name = "foreign-types"
|
||||
version = "0.3"
|
||||
[[bans.skip]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1"
|
||||
name = "toml_edit"
|
||||
version = "0.21"
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
|
@ -41,13 +35,3 @@ unknown-git = "deny"
|
|||
allow-git = [
|
||||
"https://github.com/gtk-rs/gtk-rs-core",
|
||||
]
|
||||
|
||||
# Various crates depend on an older version of syn
|
||||
[[bans.skip]]
|
||||
name = "syn"
|
||||
version = "1.0"
|
||||
|
||||
# Various crates depend on an older version of bitflags
|
||||
[[bans.skip]]
|
||||
name = "bitflags"
|
||||
version = "1.0"
|
||||
|
|
|
@ -1,46 +1,51 @@
|
|||
[package]
|
||||
name = "examples"
|
||||
version = "0.21.0"
|
||||
version.workspace = true
|
||||
license = "MIT"
|
||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
||||
gst = { package = "gstreamer", path = "../gstreamer" }
|
||||
gst-gl = { package = "gstreamer-gl", path = "../gstreamer-gl", optional = true }
|
||||
gst-gl-egl = { package = "gstreamer-gl-egl", path = "../gstreamer-gl/egl", optional = true }
|
||||
gst-gl-wayland = { package = "gstreamer-gl-wayland", path = "../gstreamer-gl/wayland", optional = true }
|
||||
gst-gl-x11 = { package = "gstreamer-gl-x11", path = "../gstreamer-gl/x11", optional = true }
|
||||
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-video = { package = "gstreamer-video", path = "../gstreamer-video" }
|
||||
gst-pbutils = { package = "gstreamer-pbutils", path = "../gstreamer-pbutils" }
|
||||
gst-play = { package = "gstreamer-play", path = "../gstreamer-play", optional = true }
|
||||
gst-player = { package = "gstreamer-player", path = "../gstreamer-player", optional = true }
|
||||
ges = { package = "gstreamer-editing-services", path = "../gstreamer-editing-services", optional = true }
|
||||
gst-sdp = { package = "gstreamer-sdp", path = "../gstreamer-sdp", optional = true }
|
||||
gst-rtsp = { package = "gstreamer-rtsp", path = "../gstreamer-rtsp", optional = true }
|
||||
gst-rtsp-server = { package = "gstreamer-rtsp-server", path = "../gstreamer-rtsp-server", optional = true }
|
||||
gst-allocators = { package = "gstreamer-allocators", path = "../gstreamer-allocators", optional = true }
|
||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
||||
glib.workspace = true
|
||||
gst.workspace = true
|
||||
gst-gl = { workspace = true, optional = true }
|
||||
gst-gl-egl = { workspace = true, optional = true }
|
||||
gst-gl-x11 = { workspace = true, optional = true }
|
||||
gst-app.workspace = true
|
||||
gst-audio.workspace = true
|
||||
gst-base.workspace = true
|
||||
gst-video.workspace = true
|
||||
gst-pbutils.workspace = true
|
||||
gst-play = { workspace = true, optional = true }
|
||||
gst-player = { workspace = true, optional = true }
|
||||
ges = { workspace = true, optional = true }
|
||||
gst-sdp = { workspace = true, optional = true }
|
||||
gst-rtsp = { workspace = true, optional = true }
|
||||
gst-rtsp-server = { workspace = true, optional = true }
|
||||
gst-allocators = { workspace = true, optional = true }
|
||||
gio = { workspace = true, optional = true }
|
||||
anyhow = "1.0"
|
||||
byte-slice-cast = "1"
|
||||
cairo-rs = { workspace = true, features=["use_glib"], optional = true }
|
||||
derive_more = "0.99.5"
|
||||
futures = "0.3"
|
||||
byte-slice-cast = "1"
|
||||
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", features=["use_glib"], optional = true }
|
||||
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
||||
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
|
||||
glutin = { version = "0.29", optional = true }
|
||||
glutin = { version = "0.31", optional = true, default-features = false }
|
||||
glutin-winit = { version = "0.4", optional = true, default-features = false }
|
||||
image = { version = "0.24", optional = true, default-features = false, features = ["png", "jpeg"] }
|
||||
memmap2 = { version = "0.7", 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]
|
||||
windows = { version = "0.48", features=["Win32_Graphics_Direct3D11",
|
||||
windows = { version = "0.56", features=["Win32_Graphics_Direct3D11",
|
||||
"Win32_Foundation", "Win32_Graphics_Direct3D", "Win32_Graphics_Dxgi",
|
||||
"Win32_Graphics_Dxgi_Common", "Win32_Graphics_Direct2D",
|
||||
"Win32_Graphics_Direct2D_Common", "Win32_Graphics_DirectWrite",
|
||||
|
@ -48,6 +53,7 @@ windows = { version = "0.48", features=["Win32_Graphics_Direct3D11",
|
|||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = "0.25"
|
||||
objc = "0.2.7"
|
||||
|
||||
[build-dependencies]
|
||||
gl_generator = { version = "0.14", optional = true }
|
||||
|
@ -58,10 +64,9 @@ rtsp-server = ["gst-rtsp-server", "gst-rtsp", "gst-sdp"]
|
|||
rtsp-server-record = ["gst-rtsp-server", "gst-rtsp", "gio"]
|
||||
pango-cairo = ["pango", "pangocairo", "cairo-rs"]
|
||||
overlay-composition = ["pango", "pangocairo", "cairo-rs"]
|
||||
gl = ["gst-gl", "gl_generator", "glutin"]
|
||||
gst-gl-x11 = ["dep:gst-gl-x11"]
|
||||
gst-gl-egl = ["dep:gst-gl-egl"]
|
||||
gst-gl-wayland = ["dep:gst-gl-wayland"]
|
||||
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", "glutin-winit?/glx"] # glx turns on x11
|
||||
gst-gl-egl = ["dep:gst-gl-egl", "glutin-winit?/egl", "glutin-winit?/x11", "glutin-winit?/wayland"] # Use X11 or Wayland via EGL
|
||||
allocators = ["gst-allocators", "memmap2", "memfd", "uds"]
|
||||
|
||||
[[bin]]
|
||||
|
@ -131,6 +136,10 @@ required-features = ["rtsp-server"]
|
|||
name = "rtsp-server-subclass"
|
||||
required-features = ["rtsp-server"]
|
||||
|
||||
[[bin]]
|
||||
name = "rtsp-server-custom-auth"
|
||||
required-features = ["rtsp-server", "gst-rtsp-server/v1_22"]
|
||||
|
||||
[[bin]]
|
||||
name = "tagsetter"
|
||||
|
||||
|
@ -195,3 +204,6 @@ required-features = ["cairo-rs", "gst-video/v1_18"]
|
|||
[[bin]]
|
||||
name = "d3d11videosink"
|
||||
required-features = ["windows"]
|
||||
|
||||
[[bin]]
|
||||
name = "audio_multichannel_interleave"
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
use anyhow::Error;
|
||||
use derive_more::{Display, Error};
|
||||
use gst::prelude::*;
|
||||
use gst_video::prelude::*;
|
||||
|
||||
#[path = "../examples-common.rs"]
|
||||
mod examples_common;
|
||||
|
|
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);
|
||||
}
|
|
@ -10,7 +10,6 @@ mod examples_common;
|
|||
|
||||
// Our custom compositor element is defined in this module.
|
||||
mod cairo_compositor {
|
||||
use glib::once_cell::sync::Lazy;
|
||||
use gst_base::subclass::prelude::*;
|
||||
use gst_video::{prelude::*, subclass::prelude::*};
|
||||
|
||||
|
@ -57,15 +56,16 @@ mod cairo_compositor {
|
|||
// In this case a single property for configuring the background color of the
|
||||
// composition.
|
||||
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")
|
||||
.nick("Background Color")
|
||||
.blurb("Background color as 0xRRGGBB")
|
||||
.default_value(Settings::default().background_color)
|
||||
.build()]
|
||||
});
|
||||
|
||||
&PROPERTIES
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
// after initial registration without having to load the plugin in memory.
|
||||
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(
|
||||
"Cairo Compositor",
|
||||
"Compositor/Video",
|
||||
"Cairo based compositor",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}))
|
||||
}
|
||||
|
||||
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
|
||||
// actually creating the pads and beforehand already provide information to
|
||||
// GStreamer about all possible pads that could exist for this type.
|
||||
|
@ -148,9 +152,7 @@ mod cairo_compositor {
|
|||
)
|
||||
.unwrap(),
|
||||
]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
})
|
||||
}
|
||||
|
||||
// Notify via the child proxy interface whenever a new pad is added or removed.
|
||||
|
@ -457,7 +459,10 @@ mod cairo_compositor {
|
|||
// In this case there are various properties for defining the position and otherwise
|
||||
// the appearance of the stream corresponding to this pad.
|
||||
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::ParamSpecDouble::builder("alpha")
|
||||
.nick("Alpha")
|
||||
|
@ -495,9 +500,7 @@ mod cairo_compositor {
|
|||
.default_value(Settings::default().ypos)
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
})
|
||||
}
|
||||
|
||||
// Called by the application whenever the value of a property should be changed.
|
||||
|
|
|
@ -48,7 +48,7 @@ fn example_main() {
|
|||
let main_loop = glib::MainLoop::new(None, false);
|
||||
|
||||
// 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",
|
||||
)
|
||||
.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
|
||||
// an EOS event on the pipeline.
|
||||
sinkpad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, move |_, probe_info| {
|
||||
match probe_info.data {
|
||||
Some(gst::PadProbeData::Event(ref ev))
|
||||
if ev.type_() == gst::EventType::CustomDownstream =>
|
||||
{
|
||||
if let Some(custom_event) = ExampleCustomEvent::parse(ev) {
|
||||
if let Some(pipeline) = pipeline_weak.upgrade() {
|
||||
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... */
|
||||
println!("Got custom event with send_eos=true. Sending EOS");
|
||||
let ev = gst::event::Eos::new();
|
||||
let pipeline_weak = pipeline_weak.clone();
|
||||
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");
|
||||
}
|
||||
}
|
||||
let Some(event) = probe_info.event() else {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
};
|
||||
|
||||
let Some(custom_event) = ExampleCustomEvent::parse(event) else {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
};
|
||||
|
||||
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||
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... */
|
||||
println!("Got custom event with send_eos=true. Sending EOS");
|
||||
let ev = gst::event::Eos::new();
|
||||
let pipeline_weak = pipeline_weak.clone();
|
||||
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
|
||||
});
|
||||
|
||||
|
@ -113,9 +115,8 @@ fn example_main() {
|
|||
glib::timeout_add_seconds(2 + i as u32, 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::ControlFlow::Break,
|
||||
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||
return glib::ControlFlow::Break;
|
||||
};
|
||||
println!("Sending custom event to the pipeline with send_eos={send_eos}");
|
||||
let ev = ExampleCustomEvent::new(*send_eos);
|
||||
|
|
|
@ -71,7 +71,6 @@ mod custom_meta {
|
|||
mod imp {
|
||||
use std::{mem, ptr};
|
||||
|
||||
use glib::once_cell::sync::Lazy;
|
||||
use glib::translate::*;
|
||||
|
||||
pub(super) struct CustomMetaParams {
|
||||
|
@ -87,8 +86,10 @@ mod custom_meta {
|
|||
|
||||
// Function to register the meta API and get a type back.
|
||||
pub(super) fn custom_meta_api_get_type() -> glib::Type {
|
||||
static TYPE: Lazy<glib::Type> = Lazy::new(|| unsafe {
|
||||
let t = from_glib(gst::ffi::gst_meta_api_type_register(
|
||||
static TYPE: std::sync::OnceLock<glib::Type> = std::sync::OnceLock::new();
|
||||
|
||||
*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 _,
|
||||
// We provide no tags here as our meta is just a label and does
|
||||
// not refer to any specific aspect of the buffer.
|
||||
|
@ -98,9 +99,7 @@ mod custom_meta {
|
|||
assert_ne!(t, glib::Type::INVALID);
|
||||
|
||||
t
|
||||
});
|
||||
|
||||
*TYPE
|
||||
})
|
||||
}
|
||||
|
||||
// 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 Sync for MetaInfo {}
|
||||
|
||||
static META_INFO: Lazy<MetaInfo> = Lazy::new(|| 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"),
|
||||
)
|
||||
});
|
||||
static META_INFO: std::sync::OnceLock<MetaInfo> = std::sync::OnceLock::new();
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,7 +194,7 @@ fn main() -> Result<()> {
|
|||
let mut metrics = DWRITE_TEXT_METRICS::default();
|
||||
layout.GetMetrics(&mut metrics).unwrap();
|
||||
layout
|
||||
.GetFontSize2(0, &mut font_size, Some(&mut range))
|
||||
.GetFontSize(0, &mut font_size, Some(&mut range))
|
||||
.unwrap();
|
||||
|
||||
if metrics.widthIncludingTrailingWhitespace >= desc.Width as f32 {
|
||||
|
@ -305,19 +305,18 @@ fn main() -> Result<()> {
|
|||
let sinkpad = videosink.static_pad("sink").unwrap();
|
||||
let overlay_context_weak = Arc::downgrade(&overlay_context);
|
||||
sinkpad.add_probe(gst::PadProbeType::BUFFER, move |_, probe_info| {
|
||||
if let Some(gst::PadProbeData::Buffer(_)) = probe_info.data {
|
||||
let overlay_context = overlay_context_weak.upgrade().unwrap();
|
||||
let mut context = overlay_context.lock().unwrap();
|
||||
context.timestamp_queue.push_back(SystemTime::now());
|
||||
// Updates framerate per 10 frames
|
||||
if context.timestamp_queue.len() >= 10 {
|
||||
let now = context.timestamp_queue.back().unwrap();
|
||||
let front = context.timestamp_queue.front().unwrap();
|
||||
let duration = now.duration_since(*front).unwrap().as_millis() as f32;
|
||||
context.avg_fps = 1000f32 * (context.timestamp_queue.len() - 1) as f32 / duration;
|
||||
context.timestamp_queue.clear();
|
||||
}
|
||||
let overlay_context = overlay_context_weak.upgrade().unwrap();
|
||||
let mut context = overlay_context.lock().unwrap();
|
||||
context.timestamp_queue.push_back(SystemTime::now());
|
||||
// Updates framerate per 10 frames
|
||||
if context.timestamp_queue.len() >= 10 {
|
||||
let now = context.timestamp_queue.back().unwrap();
|
||||
let front = context.timestamp_queue.front().unwrap();
|
||||
let duration = now.duration_since(*front).unwrap().as_millis() as f32;
|
||||
context.avg_fps = 1000f32 * (context.timestamp_queue.len() - 1) as f32 / duration;
|
||||
context.timestamp_queue.clear();
|
||||
}
|
||||
|
||||
gst::PadProbeReturn::Ok
|
||||
});
|
||||
|
||||
|
|
|
@ -19,15 +19,15 @@ fn example_main() {
|
|||
|
||||
/* Disable stdout debug, then configure the debug ringbuffer and enable
|
||||
* 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 */
|
||||
gst::debug_add_ring_buffer_logger(1024, 10);
|
||||
gst::log::add_ring_buffer_logger(1024, 10);
|
||||
/* 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 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,
|
||||
Err(err) => {
|
||||
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");
|
||||
|
||||
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------------------");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,9 +90,8 @@ fn example_main() -> Result<(), Error> {
|
|||
decodebin.connect_pad_added(move |dbin, src_pad| {
|
||||
// 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,
|
||||
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Try to detect whether the raw stream decodebin provided us with
|
||||
|
|
|
@ -120,9 +120,8 @@ fn example_main() -> Result<(), Error> {
|
|||
src.connect_pad_added(move |dbin, dbin_src_pad| {
|
||||
// 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,
|
||||
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (is_audio, is_video) = {
|
||||
|
|
|
@ -30,7 +30,7 @@ fn example_main() {
|
|||
let main_loop = glib::MainLoop::new(None, false);
|
||||
|
||||
// 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 ! fakesink").unwrap();
|
||||
let bus = pipeline.bus().unwrap();
|
||||
|
||||
pipeline
|
||||
|
@ -55,9 +55,8 @@ fn example_main() {
|
|||
glib::timeout_add_seconds(5, 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::ControlFlow::Break,
|
||||
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||
return glib::ControlFlow::Break;
|
||||
};
|
||||
|
||||
println!("sending eos");
|
||||
|
|
|
@ -361,11 +361,11 @@ mod video_filter {
|
|||
use std::{mem::ManuallyDrop, os::unix::prelude::FromRawFd};
|
||||
|
||||
use anyhow::Error;
|
||||
use glib::once_cell::sync::Lazy;
|
||||
use gst::{subclass::prelude::*, PadDirection, PadPresence, PadTemplate};
|
||||
use gst_app::gst_base::subclass::BaseTransformMode;
|
||||
use gst_video::{subclass::prelude::*, VideoFrameRef};
|
||||
use gst_video::{prelude::*, subclass::prelude::*, VideoFrameRef};
|
||||
use memmap2::MmapMut;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
|
@ -430,7 +430,10 @@ mod video_filter {
|
|||
|
||||
impl ElementImpl for FdMemoryFadeInVideoFilter {
|
||||
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()
|
||||
.format(gst_video::VideoFormat::Bgra)
|
||||
.build();
|
||||
|
@ -440,9 +443,7 @@ mod video_filter {
|
|||
PadTemplate::new("src", PadDirection::Src, PadPresence::Always, &caps)
|
||||
.unwrap(),
|
||||
]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ fn example_main() {
|
|||
gst::init().unwrap();
|
||||
|
||||
// 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();
|
||||
|
||||
pipeline
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![allow(clippy::non_send_fields_in_send_ty)]
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[path = "../glupload.rs"]
|
||||
mod glupload;
|
||||
use glupload::*;
|
||||
|
@ -30,13 +32,13 @@ void main () {
|
|||
mod mirror {
|
||||
use std::sync::Mutex;
|
||||
|
||||
use glib::once_cell::sync::Lazy;
|
||||
use gst_base::subclass::BaseTransformMode;
|
||||
use gst_gl::{
|
||||
prelude::*,
|
||||
subclass::{prelude::*, GLFilterMode},
|
||||
*,
|
||||
};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::{gl, FRAGMENT_SHADER};
|
||||
|
||||
|
@ -161,14 +163,12 @@ mod mirror {
|
|||
}
|
||||
}
|
||||
|
||||
fn example_main() {
|
||||
fn example_main() -> Result<()> {
|
||||
gst::init().unwrap();
|
||||
let glfilter = mirror::GLMirrorFilter::new(Some("foo"));
|
||||
App::new(Some(glfilter.as_ref()))
|
||||
.and_then(main_loop)
|
||||
.unwrap_or_else(|e| eprintln!("Error! {e}"))
|
||||
let glfilter = mirror::GLMirrorFilter::new(Some("Mirror filter"));
|
||||
App::new(Some(glfilter.as_ref())).and_then(main_loop)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
examples_common::run(example_main);
|
||||
fn main() -> Result<()> {
|
||||
examples_common::run(example_main)
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ fn example_main() {
|
|||
gst::init().unwrap();
|
||||
|
||||
// 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();
|
||||
|
||||
pipeline
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#![allow(clippy::non_send_fields_in_send_ty)]
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
#[path = "../glupload.rs"]
|
||||
mod glupload;
|
||||
use glupload::*;
|
||||
|
@ -7,12 +9,10 @@ use glupload::*;
|
|||
#[path = "../examples-common.rs"]
|
||||
pub mod examples_common;
|
||||
|
||||
fn example_main() {
|
||||
App::new(None)
|
||||
.and_then(main_loop)
|
||||
.unwrap_or_else(|e| eprintln!("Error! {e}"))
|
||||
fn example_main() -> Result<()> {
|
||||
App::new(None).and_then(main_loop)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
examples_common::run(example_main);
|
||||
fn main() -> Result<()> {
|
||||
examples_common::run(example_main)
|
||||
}
|
||||
|
|
|
@ -26,19 +26,22 @@ fn example_main() {
|
|||
// Especially GUIs should probably handle this case, to tell users that they need to
|
||||
// install the corresponding gstreamer plugins.
|
||||
let mut context = gst::ParseContext::new();
|
||||
let pipeline =
|
||||
match gst::parse_launch_full(&pipeline_str, Some(&mut context), gst::ParseFlags::empty()) {
|
||||
Ok(pipeline) => pipeline,
|
||||
Err(err) => {
|
||||
if let Some(gst::ParseError::NoSuchElement) = err.kind::<gst::ParseError>() {
|
||||
println!("Missing element(s): {:?}", context.missing_elements());
|
||||
} else {
|
||||
println!("Failed to parse pipeline: {err}");
|
||||
}
|
||||
|
||||
process::exit(-1)
|
||||
let pipeline = match gst::parse::launch_full(
|
||||
&pipeline_str,
|
||||
Some(&mut context),
|
||||
gst::ParseFlags::empty(),
|
||||
) {
|
||||
Ok(pipeline) => pipeline,
|
||||
Err(err) => {
|
||||
if let Some(gst::ParseError::NoSuchElement) = err.kind::<gst::ParseError>() {
|
||||
println!("Missing element(s): {:?}", context.missing_elements());
|
||||
} else {
|
||||
println!("Failed to parse pipeline: {err}");
|
||||
}
|
||||
};
|
||||
|
||||
process::exit(-1)
|
||||
}
|
||||
};
|
||||
let bus = pipeline.bus().unwrap();
|
||||
|
||||
pipeline
|
||||
|
|
|
@ -24,7 +24,7 @@ fn example_main() {
|
|||
let main_loop = glib::MainLoop::new(None, false);
|
||||
|
||||
// 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();
|
||||
|
||||
pipeline
|
||||
|
|
|
@ -92,18 +92,18 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
|
|||
DWriteCreateFactory::<IDWriteFactory>(DWRITE_FACTORY_TYPE_SHARED).unwrap();
|
||||
let text_format = dwrite_factory
|
||||
.CreateTextFormat(
|
||||
windows::w!("Arial"),
|
||||
windows::core::w!("Arial"),
|
||||
None,
|
||||
DWRITE_FONT_WEIGHT_BOLD,
|
||||
DWRITE_FONT_STYLE_NORMAL,
|
||||
DWRITE_FONT_STRETCH_NORMAL,
|
||||
32f32,
|
||||
windows::w!("en-us"),
|
||||
windows::core::w!("en-us"),
|
||||
)
|
||||
.unwrap();
|
||||
let text_layout = dwrite_factory
|
||||
.CreateTextLayout(
|
||||
windows::w!("GStreamer").as_wide(),
|
||||
windows::core::w!("GStreamer").as_wide(),
|
||||
&text_format,
|
||||
// Size will be updated later on "caps-changed" signal
|
||||
800f32,
|
||||
|
|
|
@ -22,7 +22,7 @@ fn example_main() {
|
|||
// 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
|
||||
// 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",
|
||||
gst_audio::AUDIO_FORMAT_S16
|
||||
))
|
||||
|
@ -38,36 +38,38 @@ fn example_main() {
|
|||
// This handler gets called for every buffer that passes the pad we probe.
|
||||
src_pad.add_probe(gst::PadProbeType::BUFFER, |_, probe_info| {
|
||||
// Interpret the data sent over the pad as one buffer
|
||||
if let Some(gst::PadProbeData::Buffer(ref buffer)) = probe_info.data {
|
||||
// At this point, buffer is only a reference to an existing memory region somewhere.
|
||||
// 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();
|
||||
let Some(buffer) = probe_info.buffer() else {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
};
|
||||
|
||||
// We know what format the data in the memory region has, since we requested
|
||||
// it by setting the appsink's caps. So what we do here is interpret the
|
||||
// memory region we mapped as an array of signed 16 bit integers.
|
||||
let samples = if let Ok(samples) = map.as_slice_of::<i16>() {
|
||||
samples
|
||||
} else {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
};
|
||||
// At this point, buffer is only a reference to an existing memory region somewhere.
|
||||
// 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();
|
||||
|
||||
// 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}");
|
||||
}
|
||||
// We know what format the data in the memory region has, since we requested
|
||||
// it by setting the appsink's caps. So what we do here is interpret the
|
||||
// memory region we mapped as an array of signed 16 bit integers.
|
||||
let samples = if let Ok(samples) = map.as_slice_of::<i16>() {
|
||||
samples
|
||||
} else {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
};
|
||||
|
||||
// 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
|
||||
});
|
||||
|
|
|
@ -37,6 +37,11 @@ fn main_loop(uri: &str) -> Result<(), Error> {
|
|||
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())
|
||||
}
|
||||
|
||||
|
|
|
@ -36,14 +36,14 @@ fn example_main() {
|
|||
// For flags handling
|
||||
// With flags, one can configure playbin's behavior such as whether it
|
||||
// should play back contained video streams, or if it should render subtitles.
|
||||
// let flags = playbin.get_property("flags").unwrap();
|
||||
// let flags_class = FlagsClass::new(flags.type_()).unwrap();
|
||||
// let flags = playbin.property_value("flags");
|
||||
// let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
|
||||
// let flags = flags_class.builder_with_value(flags).unwrap()
|
||||
// .unset_by_nick("text")
|
||||
// .unset_by_nick("video")
|
||||
// .build()
|
||||
// .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.
|
||||
// For this, the playbin provides signals notifying about changes in the metadata.
|
||||
|
|
|
@ -28,7 +28,7 @@ fn example_main() {
|
|||
let main_loop = glib::MainLoop::new(None, false);
|
||||
|
||||
// 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();
|
||||
|
||||
pipeline
|
||||
|
@ -50,9 +50,8 @@ fn example_main() {
|
|||
let timeout_id = glib::timeout_add_seconds(1, 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::ControlFlow::Continue,
|
||||
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||
return glib::ControlFlow::Break;
|
||||
};
|
||||
|
||||
//let pos = pipeline.query_position(gst::Format::Time).unwrap_or(-1);
|
||||
|
|
|
@ -203,9 +203,8 @@ fn example_main() -> Result<(), Error> {
|
|||
|
||||
let depay_weak = depay.downgrade();
|
||||
rtpbin.connect_pad_added(move |rtpbin, src_pad| {
|
||||
let depay = match depay_weak.upgrade() {
|
||||
Some(depay) => depay,
|
||||
None => return,
|
||||
let Some(depay) = depay_weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
match connect_rtpbin_srcpad(src_pad, &depay) {
|
||||
|
@ -253,11 +252,7 @@ fn example_main() -> Result<(), Error> {
|
|||
if let Some(element) = msg.src() {
|
||||
if element == &pipeline && s.current() == gst::State::Playing {
|
||||
eprintln!("PLAYING");
|
||||
gst::debug_bin_to_dot_file(
|
||||
&pipeline,
|
||||
gst::DebugGraphDetails::all(),
|
||||
"client-playing",
|
||||
);
|
||||
pipeline.debug_to_dot_file(gst::DebugGraphDetails::all(), "client-playing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,11 +179,7 @@ fn example_main() -> Result<(), Error> {
|
|||
if let Some(element) = msg.src() {
|
||||
if element == &pipeline && s.current() == gst::State::Playing {
|
||||
eprintln!("PLAYING");
|
||||
gst::debug_bin_to_dot_file(
|
||||
&pipeline,
|
||||
gst::DebugGraphDetails::all(),
|
||||
"server-playing",
|
||||
);
|
||||
pipeline.debug_to_dot_file(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
|
||||
// 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 derive_more::{Display, Error};
|
||||
use glib::translate::*;
|
||||
use gst_rtsp_server::prelude::*;
|
||||
|
||||
#[path = "../examples-common.rs"]
|
||||
|
@ -45,10 +44,9 @@ fn main_loop() -> Result<(), Error> {
|
|||
// Here we configure a method of authentication that we want the
|
||||
// server to require from clients.
|
||||
let auth = gst_rtsp_server::RTSPAuth::new();
|
||||
let token = gst_rtsp_server::RTSPToken::new(&[(
|
||||
gst_rtsp_server::RTSP_TOKEN_MEDIA_FACTORY_ROLE,
|
||||
&"user",
|
||||
)]);
|
||||
let token = gst_rtsp_server::RTSPToken::builder()
|
||||
.field(gst_rtsp_server::RTSP_TOKEN_MEDIA_FACTORY_ROLE, "user")
|
||||
.build();
|
||||
let basic = gst_rtsp_server::RTSPAuth::make_basic("user", "password");
|
||||
// For proper authentication, we want to use encryption. And there's no
|
||||
// encryption without a certificate!
|
||||
|
@ -78,24 +76,14 @@ fn main_loop() -> Result<(), Error> {
|
|||
W535W8UBbEg=-----END PRIVATE KEY-----",
|
||||
)?;
|
||||
|
||||
// Bindable versions were added in b1f515178a363df0322d7adbd5754e1f6e2083c9
|
||||
// This declares that the user "user" (once authenticated) has a role that
|
||||
// allows them to access and construct media factories.
|
||||
unsafe {
|
||||
gst_rtsp_server::ffi::gst_rtsp_media_factory_add_role(
|
||||
factory.to_glib_none().0,
|
||||
"user".to_glib_none().0,
|
||||
gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_ACCESS
|
||||
.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>(),
|
||||
);
|
||||
}
|
||||
factory.add_role_from_structure(
|
||||
&gst::Structure::builder("user")
|
||||
.field(gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_ACCESS, true)
|
||||
.field(gst_rtsp_server::RTSP_PERM_MEDIA_FACTORY_CONSTRUCT, true)
|
||||
.build(),
|
||||
);
|
||||
|
||||
auth.set_tls_certificate(Some(&cert));
|
||||
auth.add_basic(basic.as_str(), &token);
|
||||
|
|
|
@ -19,10 +19,6 @@ mod examples_common;
|
|||
#[display(fmt = "Could not get mount points")]
|
||||
struct NoMountPoints;
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
#[display(fmt = "Usage: {_0} LAUNCH_LINE")]
|
||||
struct UsageError(#[error(not(source))] String);
|
||||
|
||||
fn main_loop() -> Result<(), Error> {
|
||||
let main_loop = glib::MainLoop::new(None, false);
|
||||
let server = server::Server::default();
|
||||
|
|
|
@ -17,8 +17,8 @@ mod examples_common;
|
|||
// Our custom FIR filter element is defined in this module
|
||||
mod fir_filter {
|
||||
use byte_slice_cast::*;
|
||||
use glib::once_cell::sync::Lazy;
|
||||
use gst_base::subclass::prelude::*;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
// The debug category we use below for our filter
|
||||
pub static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
|
@ -63,20 +63,24 @@ mod fir_filter {
|
|||
// 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: 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(
|
||||
"FIR Filter",
|
||||
"Filter/Effect/Audio",
|
||||
"A FIR audio filter",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}))
|
||||
}
|
||||
|
||||
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
|
||||
// actually creating the pads and beforehand already provide information to
|
||||
// GStreamer about all possible pads that could exist for this type.
|
||||
|
@ -107,9 +111,7 @@ mod fir_filter {
|
|||
)
|
||||
.unwrap(),
|
||||
]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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.
|
||||
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",
|
||||
Some(&mut context),
|
||||
gst::ParseFlags::empty(),
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
use anyhow::Error;
|
||||
use derive_more::{Display, Error};
|
||||
use gst::{element_error, prelude::*};
|
||||
use gst_video::prelude::*;
|
||||
|
||||
#[path = "../examples-common.rs"]
|
||||
mod examples_common;
|
||||
|
@ -26,7 +27,7 @@ fn create_pipeline(uri: String, out_path: std::path::PathBuf) -> Result<gst::Pip
|
|||
gst::init()?;
|
||||
|
||||
// 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"
|
||||
))?
|
||||
.downcast::<gst::Pipeline>()
|
||||
|
|
|
@ -52,9 +52,8 @@ fn example_main() {
|
|||
decodebin.connect_pad_added(move |_, src_pad| {
|
||||
// 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,
|
||||
let Some(pipeline) = pipeline_weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
// In this example, we are only interested about parsing the ToC, so
|
||||
|
|
|
@ -11,7 +11,7 @@ fn example_main() {
|
|||
gst::init().unwrap();
|
||||
|
||||
// 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 \
|
||||
compositor ! video/x-raw,width=1280,height=720 ! videoconvert ! autovideosink",
|
||||
)
|
||||
|
|
|
@ -83,7 +83,7 @@ fn example_main() {
|
|||
|
||||
gst::init().unwrap();
|
||||
|
||||
let pipeline = gst::parse_launch(&format!(
|
||||
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();
|
||||
|
@ -96,7 +96,7 @@ fn example_main() {
|
|||
mixer_src_pad.add_probe(gst::PadProbeType::EVENT_UPSTREAM, move |_, probe_info| {
|
||||
let mixer_sink_pad = mixer_sink_pad_weak.upgrade().unwrap();
|
||||
|
||||
let Some(gst::PadProbeData::Event(ref ev)) = probe_info.data else {
|
||||
let Some(ev) = probe_info.event() else {
|
||||
return gst::PadProbeReturn::Ok;
|
||||
};
|
||||
|
||||
|
|
|
@ -17,13 +17,47 @@ pub fn run<T, F: FnOnce() -> T + Send + 'static>(main: F) -> T
|
|||
where
|
||||
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::{
|
||||
class, msg_send,
|
||||
runtime::{Object, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
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 app = cocoa::appkit::NSApp();
|
||||
|
|
|
@ -1,15 +1,30 @@
|
|||
// This example demonstrates how to output GL textures, within an
|
||||
// EGL/X11 context provided by the application, and render those
|
||||
// textures in the GL application.
|
||||
//! This example demonstrates how to output GL textures, within an EGL/X11 context provided by the
|
||||
//! application, and render those 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 }
|
||||
|
||||
use std::{ffi::CStr, mem, ptr, sync};
|
||||
use std::{
|
||||
ffi::{CStr, CString},
|
||||
mem,
|
||||
num::NonZeroU32,
|
||||
ptr,
|
||||
};
|
||||
|
||||
use anyhow::Error;
|
||||
use anyhow::{Context, Result};
|
||||
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_gl::prelude::*;
|
||||
use raw_window_handle::HasRawWindowHandle as _;
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
#[display(fmt = "Received error from {src}: {error} (debug: {debug:?})")]
|
||||
|
@ -171,7 +186,7 @@ impl Gl {
|
|||
}
|
||||
}
|
||||
|
||||
fn resize(&self, size: glutin::dpi::PhysicalSize<u32>) {
|
||||
fn resize(&self, size: winit::dpi::PhysicalSize<u32>) {
|
||||
unsafe {
|
||||
self.gl
|
||||
.Viewport(0, 0, size.width as i32, size.height as i32);
|
||||
|
@ -179,14 +194,17 @@ impl Gl {
|
|||
}
|
||||
}
|
||||
|
||||
fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
|
||||
let gl = gl::Gl::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _);
|
||||
fn load(gl_display: &impl glutin::display::GlDisplay) -> Gl {
|
||||
let gl = gl::Gl::load_with(|symbol| {
|
||||
let symbol = CString::new(symbol).unwrap();
|
||||
gl_display.get_proc_address(&symbol).cast()
|
||||
});
|
||||
|
||||
let version = unsafe {
|
||||
let data = CStr::from_ptr(gl.GetString(gl::VERSION) as *const _)
|
||||
.to_bytes()
|
||||
.to_vec();
|
||||
String::from_utf8(data).unwrap()
|
||||
let version = gl.GetString(gl::VERSION);
|
||||
assert!(!version.is_null());
|
||||
let version = CStr::from_ptr(version.cast());
|
||||
version.to_string_lossy()
|
||||
};
|
||||
|
||||
println!("OpenGL version {version}");
|
||||
|
@ -206,9 +224,10 @@ fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
|
|||
gl.LinkProgram(program);
|
||||
|
||||
{
|
||||
let mut success: gl::types::GLint = 1;
|
||||
gl.GetProgramiv(fs, gl::LINK_STATUS, &mut success);
|
||||
assert!(success != 0);
|
||||
let mut success = 1;
|
||||
gl.GetProgramiv(program, gl::LINK_STATUS, &mut success);
|
||||
assert_ne!(success, 0);
|
||||
assert_eq!(gl.GetError(), 0);
|
||||
}
|
||||
|
||||
let attr_position = gl.GetAttribLocation(program, b"a_position\0".as_ptr() as *const _);
|
||||
|
@ -279,6 +298,8 @@ fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
|
|||
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
||||
gl.BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
|
||||
assert_eq!(gl.GetError(), 0);
|
||||
|
||||
(
|
||||
program,
|
||||
attr_position,
|
||||
|
@ -310,154 +331,199 @@ pub(crate) struct App {
|
|||
pipeline: gst::Pipeline,
|
||||
appsink: gst_app::AppSink,
|
||||
bus: gst::Bus,
|
||||
event_loop: glutin::event_loop::EventLoop<Message>,
|
||||
windowed_context: glutin::WindowedContext<glutin::PossiblyCurrent>,
|
||||
event_loop: winit::event_loop::EventLoop<Message>,
|
||||
window: Option<winit::window::Window>,
|
||||
not_current_gl_context: Option<glutin::context::NotCurrentContext>,
|
||||
shared_context: gst_gl::GLContext,
|
||||
}
|
||||
|
||||
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()?;
|
||||
|
||||
let (pipeline, appsink) = App::create_pipeline(gl_element)?;
|
||||
let bus = pipeline
|
||||
.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 window = glutin::window::WindowBuilder::new().with_title("GL rendering");
|
||||
let windowed_context = glutin::ContextBuilder::new()
|
||||
.with_vsync(true)
|
||||
.build_windowed(window, &event_loop)?;
|
||||
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build()?;
|
||||
|
||||
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 inner_window = windowed_context.window();
|
||||
let display_builder =
|
||||
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 cfg!(target_os = "linux") {
|
||||
#[cfg(any(feature = "gst-gl-x11", feature = "gst-gl-wayland"))]
|
||||
use glutin::platform::unix::WindowExtUnix;
|
||||
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 prefer_transparency || new_config.num_samples() > current.num_samples() {
|
||||
new_config
|
||||
} else {
|
||||
current
|
||||
}
|
||||
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);
|
||||
}
|
||||
})
|
||||
.unwrap()
|
||||
})
|
||||
.expect("Failed to build display");
|
||||
println!(
|
||||
"Picked a config with {} samples and transparency {}. Pixel format: {:?}",
|
||||
gl_config.num_samples(),
|
||||
gl_config.supports_transparency().unwrap_or(false),
|
||||
gl_config.color_buffer_type()
|
||||
);
|
||||
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(target_os = "linux"))]
|
||||
compile_error!("This example only has Linux 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)
|
||||
}
|
||||
#[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();
|
||||
let event_proxy = 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) {
|
||||
eprintln!("Failed to send BusEvent to event proxy: {e}")
|
||||
}
|
||||
if let Err(e) = event_proxy.send_event(Message::BusEvent) {
|
||||
eprintln!("Failed to send BusEvent to event proxy: {e}")
|
||||
}
|
||||
|
||||
gst::BusSyncReply::Pass
|
||||
});
|
||||
} else {
|
||||
panic!("This example only has Linux support");
|
||||
}
|
||||
gst::BusSyncReply::Pass
|
||||
});
|
||||
|
||||
Ok(App {
|
||||
pipeline,
|
||||
appsink,
|
||||
bus,
|
||||
event_loop,
|
||||
windowed_context,
|
||||
window,
|
||||
not_current_gl_context: Some(not_current_gl_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();
|
||||
self.appsink.set_callbacks(
|
||||
gst_app::AppSinkCallbacks::builder()
|
||||
|
@ -521,22 +587,33 @@ impl App {
|
|||
.build(),
|
||||
);
|
||||
|
||||
self.pipeline.set_state(gst::State::Playing)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn map_gl_api(api: glutin::Api) -> gst_gl::GLAPI {
|
||||
match api {
|
||||
glutin::Api::OpenGl => gst_gl::GLAPI::OPENGL3,
|
||||
glutin::Api::OpenGlEs => gst_gl::GLAPI::GLES2,
|
||||
_ => gst_gl::GLAPI::empty(),
|
||||
}
|
||||
/// Converts from <https://docs.rs/glutin/latest/glutin/config/struct.Api.html> to
|
||||
/// <https://gstreamer.freedesktop.org/documentation/gl/gstglapi.html?gi-language=c#GstGLAPI>.
|
||||
fn map_gl_api(api: glutin::config::Api) -> gst_gl::GLAPI {
|
||||
use glutin::config::Api;
|
||||
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(
|
||||
gl_element: Option<&gst::Element>,
|
||||
) -> Result<(gst::Pipeline, gst_app::AppSink), Error> {
|
||||
) -> Result<(gst::Pipeline, gst_app::AppSink)> {
|
||||
let pipeline = gst::Pipeline::default();
|
||||
let src = gst::ElementFactory::make("videotestsrc").build()?;
|
||||
|
||||
|
@ -576,7 +653,7 @@ impl App {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_messages(bus: &gst::Bus) -> Result<(), Error> {
|
||||
fn handle_messages(bus: &gst::Bus) -> Result<()> {
|
||||
use gst::MessageView;
|
||||
|
||||
for msg in bus.iter() {
|
||||
|
@ -601,77 +678,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)?;
|
||||
|
||||
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 {
|
||||
pipeline,
|
||||
bus,
|
||||
event_loop,
|
||||
pipeline,
|
||||
mut window,
|
||||
mut not_current_gl_context,
|
||||
shared_context,
|
||||
windowed_context,
|
||||
..
|
||||
} = app;
|
||||
|
||||
event_loop.run(move |event, _, cf| {
|
||||
*cf = glutin::event_loop::ControlFlow::Wait;
|
||||
let mut curr_frame: Option<gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>> = None;
|
||||
|
||||
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;
|
||||
match event {
|
||||
glutin::event::Event::LoopDestroyed => {
|
||||
winit::event::Event::LoopExiting => {
|
||||
pipeline.send_event(gst::event::Eos::new());
|
||||
pipeline.set_state(gst::State::Null).unwrap();
|
||||
}
|
||||
glutin::event::Event::WindowEvent { event, .. } => match event {
|
||||
glutin::event::WindowEvent::CloseRequested
|
||||
| glutin::event::WindowEvent::KeyboardInput {
|
||||
input:
|
||||
glutin::event::KeyboardInput {
|
||||
state: glutin::event::ElementState::Released,
|
||||
virtual_keycode: Some(glutin::event::VirtualKeyCode::Escape),
|
||||
winit::event::Event::WindowEvent { event, .. } => match event {
|
||||
winit::event::WindowEvent::CloseRequested
|
||||
| winit::event::WindowEvent::KeyboardInput {
|
||||
event:
|
||||
winit::event::KeyEvent {
|
||||
state: winit::event::ElementState::Released,
|
||||
logical_key:
|
||||
winit::keyboard::Key::Named(winit::keyboard::NamedKey::Escape),
|
||||
..
|
||||
},
|
||||
..
|
||||
} => *cf = glutin::event_loop::ControlFlow::Exit,
|
||||
glutin::event::WindowEvent::Resized(physical_size) => {
|
||||
windowed_context.resize(physical_size);
|
||||
gl.resize(physical_size);
|
||||
} => window_target.exit(),
|
||||
winit::event::WindowEvent::Resized(size) => {
|
||||
// Some platforms like EGL require resizing GL surface to update the 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
|
||||
glutin::event::Event::UserEvent(Message::Frame(info, buffer)) => {
|
||||
if let Ok(frame) = gst_video::VideoFrame::from_buffer_readable_gl(buffer, &info) {
|
||||
winit::event::Event::UserEvent(Message::Frame(info, buffer)) => {
|
||||
if let Ok(frame) = gst_gl::GLVideoFrame::from_buffer_readable(buffer, &info) {
|
||||
curr_frame = Some(frame);
|
||||
needs_redraw = true;
|
||||
}
|
||||
}
|
||||
// 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();
|
||||
}
|
||||
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 let Some(frame) = curr_frame.as_ref() {
|
||||
let sync_meta = frame.buffer().meta::<gst_gl::GLSyncMeta>().unwrap();
|
||||
sync_meta.wait(&shared_context);
|
||||
if let Some(texture) = frame.texture_id(0) {
|
||||
gl.draw_frame(texture as gl::types::GLuint);
|
||||
if let Some((gl, gl_context, gl_surface)) = &running_state {
|
||||
if let Some(frame) = curr_frame.as_ref() {
|
||||
let sync_meta = frame.buffer().meta::<gst_gl::GLSyncMeta>().unwrap();
|
||||
sync_meta.wait(&shared_context);
|
||||
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 1d1ce102e130a70684f90f411ee75b4e73f90beb
|
||||
Subproject commit 5223ce91b97a833b09d6cbd04bbeab1bf18112b7
|
|
@ -1 +1 @@
|
|||
Subproject commit 060b114d8edb20c4041abb1e95acccb2cb2302be
|
||||
Subproject commit 6cd7b656acd61172ab7f125a7059e4d0ecfc9637
|
|
@ -1 +1 @@
|
|||
Subproject commit 8ea27b3f0b3316bdc3df48581820805b18473413
|
||||
Subproject commit c988e03b5e99349efb8ffb6f502879ad4ddbc248
|
|
@ -1,23 +1,24 @@
|
|||
[package]
|
||||
name = "gstreamer-allocators"
|
||||
version = "0.21.0"
|
||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||
categories = ["api-bindings", "multimedia"]
|
||||
description = "Rust bindings for GStreamer Allocators library"
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
homepage = "https://gstreamer.freedesktop.org"
|
||||
documentation = "https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_allocators/"
|
||||
keywords = ["gstreamer", "multimedia", "audio", "video", "gnome"]
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
version.workspace = true
|
||||
categories.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
ffi = { package = "gstreamer-allocators-sys", path = "sys" }
|
||||
glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
||||
gst = { package = "gstreamer", path = "../gstreamer" }
|
||||
glib.workspace = true
|
||||
gst.workspace = true
|
||||
once_cell = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
gir-format-check = "0.1"
|
||||
|
@ -29,6 +30,7 @@ v1_18 = ["gst/v1_18", "ffi/v1_18", "v1_16"]
|
|||
v1_20 = ["gst/v1_20", "ffi/v1_20", "v1_18"]
|
||||
v1_22 = ["gst/v1_22", "ffi/v1_22", "v1_20"]
|
||||
v1_24 = ["gst/v1_24", "ffi/v1_24", "v1_22"]
|
||||
v1_26 = ["gst/v1_26", "ffi/v1_26", "v1_24"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
|
|
|
@ -94,3 +94,15 @@ status = "generate"
|
|||
[[object.function]]
|
||||
name = "alloc"
|
||||
manual = true
|
||||
|
||||
[[object]]
|
||||
name = "GstAllocators.ShmAllocator"
|
||||
status = "generate"
|
||||
cfg_condition = "unix"
|
||||
[[object.function]]
|
||||
name = "get"
|
||||
manual = true
|
||||
[[object.function]]
|
||||
name = "init_once"
|
||||
manual = true
|
||||
|
||||
|
|
|
@ -11,6 +11,11 @@ pub static ALLOCATOR_DMABUF: &GStr =
|
|||
#[doc(alias = "GST_ALLOCATOR_FD")]
|
||||
pub static ALLOCATOR_FD: &GStr =
|
||||
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")]
|
||||
pub static CAPS_FEATURE_MEMORY_DMABUF: &GStr =
|
||||
unsafe { GStr::from_utf8_with_nul_unchecked(ffi::GST_CAPS_FEATURE_MEMORY_DMABUF) };
|
||||
|
|
|
@ -27,17 +27,30 @@ pub use self::fd_allocator::FdAllocator;
|
|||
mod phys_memory_allocator;
|
||||
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;
|
||||
pub use self::flags::FdMemoryFlags;
|
||||
|
||||
pub mod functions;
|
||||
pub(crate) mod functions;
|
||||
|
||||
mod constants;
|
||||
pub use self::constants::ALLOCATOR_DMABUF;
|
||||
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;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub mod traits {
|
||||
pub(crate) mod traits {
|
||||
pub use super::phys_memory_allocator::PhysMemoryAllocatorExt;
|
||||
}
|
||||
|
|
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::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 @ 1d1ce102e130)
|
||||
from gir-files (https://github.com/gtk-rs/gir-files @ 060b114d8edb)
|
||||
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ 8ea27b3f0b33)
|
||||
Generated by gir (https://github.com/gtk-rs/gir @ 5223ce91b97a)
|
||||
from gir-files (https://github.com/gtk-rs/gir-files @ 6cd7b656acd6)
|
||||
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ c988e03b5e99)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
use glib::once_cell::sync::Lazy;
|
||||
use gst::CapsFeatures;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static CAPS_FEATURES_MEMORY_DMABUF: Lazy<CapsFeatures> =
|
||||
Lazy::new(|| CapsFeatures::new([crate::CAPS_FEATURE_MEMORY_DMABUF]));
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::{
|
|||
os::unix::prelude::{IntoRawFd, RawFd},
|
||||
};
|
||||
|
||||
use glib::{translate::*, Cast};
|
||||
use glib::{prelude::*, translate::*};
|
||||
use gst::{Memory, MemoryRef};
|
||||
|
||||
#[cfg(feature = "v1_16")]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{fmt, mem, os::unix::prelude::IntoRawFd};
|
||||
|
||||
use glib::{translate::*, Cast};
|
||||
use glib::{prelude::*, translate::*};
|
||||
use gst::{Memory, MemoryRef};
|
||||
|
||||
use crate::{DRMDumbAllocator, DmaBufMemory};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{fmt, os::unix::prelude::RawFd};
|
||||
|
||||
use glib::{translate::*, Cast};
|
||||
use glib::{prelude::*, translate::*};
|
||||
use gst::{Memory, MemoryRef};
|
||||
|
||||
use crate::{FdAllocator, FdMemoryFlags};
|
||||
|
|
|
@ -44,6 +44,10 @@ mod drm_dumb_allocator;
|
|||
#[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;
|
||||
pub use phys_memory::*;
|
||||
|
||||
|
@ -52,6 +56,8 @@ pub use phys_memory::*;
|
|||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use gst::prelude::*;
|
||||
|
||||
pub use crate::auto::traits::*;
|
||||
}
|
||||
|
||||
pub mod subclass;
|
||||
|
|
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::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())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,17 +4,14 @@ system-deps = "6"
|
|||
[dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[dependencies.glib]
|
||||
git = "https://github.com/gtk-rs/gtk-rs-core"
|
||||
package = "glib-sys"
|
||||
[dependencies.glib-sys]
|
||||
workspace = true
|
||||
|
||||
[dependencies.gobject]
|
||||
git = "https://github.com/gtk-rs/gtk-rs-core"
|
||||
package = "gobject-sys"
|
||||
[dependencies.gobject-sys]
|
||||
workspace = true
|
||||
|
||||
[dependencies.gst]
|
||||
package = "gstreamer-sys"
|
||||
path = "../../gstreamer/sys"
|
||||
[dependencies.gstreamer-sys]
|
||||
workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
shell-words = "1.0.0"
|
||||
|
@ -26,6 +23,7 @@ v1_18 = ["v1_16"]
|
|||
v1_20 = ["v1_18"]
|
||||
v1_22 = ["v1_20"]
|
||||
v1_24 = ["v1_22"]
|
||||
v1_26 = ["v1_24"]
|
||||
|
||||
[lib]
|
||||
name = "gstreamer_allocators_sys"
|
||||
|
@ -35,15 +33,28 @@ authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
|||
build = "build.rs"
|
||||
description = "FFI bindings to libgstallocators-1.0"
|
||||
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"]
|
||||
license = "MIT"
|
||||
name = "gstreamer-allocators-sys"
|
||||
readme = "README.md"
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
||||
rust-version = "1.70"
|
||||
version = "0.21.0"
|
||||
|
||||
[package.version]
|
||||
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]
|
||||
all-features = true
|
||||
|
@ -67,4 +78,7 @@ version = "1.20"
|
|||
version = "1.22"
|
||||
|
||||
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_24]
|
||||
version = "1.23"
|
||||
version = "1.24"
|
||||
|
||||
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_26]
|
||||
version = "1.25"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
Generated by gir (https://github.com/gtk-rs/gir @ 1d1ce102e130)
|
||||
from gir-files (https://github.com/gtk-rs/gir-files @ 060b114d8edb)
|
||||
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ 8ea27b3f0b33)
|
||||
Generated by gir (https://github.com/gtk-rs/gir @ 5223ce91b97a)
|
||||
from gir-files (https://github.com/gtk-rs/gir-files @ 6cd7b656acd6)
|
||||
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ c988e03b5e99)
|
||||
|
|
|
@ -12,11 +12,18 @@
|
|||
)]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
use glib_sys as glib;
|
||||
use gobject_sys as gobject;
|
||||
use gstreamer_sys as gst;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use libc::{
|
||||
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,
|
||||
intptr_t, off_t, size_t, ssize_t, time_t, uintptr_t, FILE,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
#[allow(unused_imports)]
|
||||
use libc::{dev_t, gid_t, pid_t, socklen_t, uid_t};
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use glib::{gboolean, gconstpointer, gpointer, GType};
|
||||
|
@ -24,6 +31,7 @@ use glib::{gboolean, gconstpointer, gpointer, GType};
|
|||
// Constants
|
||||
pub const GST_ALLOCATOR_DMABUF: &[u8] = b"dmabuf\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";
|
||||
|
||||
// Flags
|
||||
|
@ -93,6 +101,20 @@ 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
|
||||
#[repr(C)]
|
||||
pub struct GstDRMDumbAllocator {
|
||||
|
@ -136,6 +158,19 @@ impl ::std::fmt::Debug for GstFdAllocator {
|
|||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
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
|
||||
#[repr(C)]
|
||||
pub struct GstPhysMemoryAllocator {
|
||||
|
@ -211,6 +246,19 @@ extern "C" {
|
|||
flags: GstFdMemoryFlags,
|
||||
) -> *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
|
||||
//=========================================================================
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::error::Error;
|
|||
use std::ffi::OsString;
|
||||
use std::mem::{align_of, size_of};
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::str;
|
||||
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);
|
||||
cmd.arg("--cflags");
|
||||
cmd.args(packages);
|
||||
cmd.stderr(Stdio::inherit());
|
||||
let out = cmd.output()?;
|
||||
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)?;
|
||||
Ok(shell_words::split(stdout.trim())?)
|
||||
|
@ -188,13 +190,15 @@ fn get_c_output(name: &str) -> Result<String, Box<dyn Error>> {
|
|||
let cc = Compiler::new().expect("configured compiler");
|
||||
cc.compile(&c_file, &exe)?;
|
||||
|
||||
let mut abi_cmd = Command::new(exe);
|
||||
let output = abi_cmd.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(format!("command {abi_cmd:?} failed, {output:?}").into());
|
||||
let mut cmd = Command::new(exe);
|
||||
cmd.stderr(Stdio::inherit());
|
||||
let out = cmd.output()?;
|
||||
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)] = &[
|
||||
|
@ -247,11 +251,19 @@ const RUST_LAYOUTS: &[(&str, Layout)] = &[
|
|||
alignment: align_of::<GstPhysMemoryAllocatorInterface>(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"GstShmAllocatorClass",
|
||||
Layout {
|
||||
size: size_of::<GstShmAllocatorClass>(),
|
||||
alignment: align_of::<GstShmAllocatorClass>(),
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
const RUST_CONSTANTS: &[(&str, &str)] = &[
|
||||
("GST_ALLOCATOR_DMABUF", "dmabuf"),
|
||||
("GST_ALLOCATOR_FD", "fd"),
|
||||
("GST_ALLOCATOR_SHM", "shm"),
|
||||
("GST_CAPS_FEATURE_MEMORY_DMABUF", "memory:DMABuf"),
|
||||
("(guint) GST_FD_MEMORY_FLAG_DONT_CLOSE", "4"),
|
||||
("(guint) GST_FD_MEMORY_FLAG_KEEP_MAPPED", "1"),
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
int main() {
|
||||
PRINT_CONSTANT(GST_ALLOCATOR_DMABUF);
|
||||
PRINT_CONSTANT(GST_ALLOCATOR_FD);
|
||||
PRINT_CONSTANT(GST_ALLOCATOR_SHM);
|
||||
PRINT_CONSTANT(GST_CAPS_FEATURE_MEMORY_DMABUF);
|
||||
PRINT_CONSTANT((guint) GST_FD_MEMORY_FLAG_DONT_CLOSE);
|
||||
PRINT_CONSTANT((guint) GST_FD_MEMORY_FLAG_KEEP_MAPPED);
|
||||
|
|
|
@ -15,5 +15,6 @@ int main() {
|
|||
printf("%s;%zu;%zu\n", "GstFdAllocatorClass", sizeof(GstFdAllocatorClass), alignof(GstFdAllocatorClass));
|
||||
printf("%s;%zu;%zu\n", "GstFdMemoryFlags", sizeof(GstFdMemoryFlags), alignof(GstFdMemoryFlags));
|
||||
printf("%s;%zu;%zu\n", "GstPhysMemoryAllocatorInterface", sizeof(GstPhysMemoryAllocatorInterface), alignof(GstPhysMemoryAllocatorInterface));
|
||||
printf("%s;%zu;%zu\n", "GstShmAllocatorClass", sizeof(GstShmAllocatorClass), alignof(GstShmAllocatorClass));
|
||||
return 0;
|
||||
}
|
||||
|
|
1
gstreamer-analytics/CHANGELOG.md
Symbolic link
1
gstreamer-analytics/CHANGELOG.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../gstreamer/CHANGELOG.md
|
1
gstreamer-analytics/COPYRIGHT
Symbolic link
1
gstreamer-analytics/COPYRIGHT
Symbolic link
|
@ -0,0 +1 @@
|
|||
../COPYRIGHT
|
32
gstreamer-analytics/Cargo.toml
Normal file
32
gstreamer-analytics/Cargo.toml
Normal file
|
@ -0,0 +1,32 @@
|
|||
[package]
|
||||
name = "gstreamer-analytics"
|
||||
authors = ["Olivier Crête <olivier.crete@collabora.com>"]
|
||||
description = "Rust bindings for GStreamer Analytics library"
|
||||
license = "MIT OR Apache-2.0"
|
||||
readme = "README.md"
|
||||
documentation = "https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_analytics/"
|
||||
keywords = ["gstreamer", "multimedia", "audio", "video", "gnome"]
|
||||
version.workspace = true
|
||||
categories.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
ffi = { package = "gstreamer-analytics-sys", path = "sys" }
|
||||
glib.workspace = true
|
||||
gst.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gir-format-check = "0.1"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
v1_26 = ["gst/v1_26", "ffi/v1_26"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustc-args = ["--cfg", "docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
|
34
gstreamer-analytics/Gir.toml
Normal file
34
gstreamer-analytics/Gir.toml
Normal file
|
@ -0,0 +1,34 @@
|
|||
[options]
|
||||
girs_directories = ["../gir-files", "../gst-gir-files"]
|
||||
library = "GstAnalytics"
|
||||
version = "1.0"
|
||||
min_cfg_version = "1.24"
|
||||
work_mode = "normal"
|
||||
concurrency = "send+sync"
|
||||
generate_safety_asserts = true
|
||||
single_version_file = true
|
||||
generate_display_trait = false
|
||||
trust_return_value_nullability = true
|
||||
|
||||
external_libraries = [
|
||||
"GLib",
|
||||
"GObject",
|
||||
"Gst",
|
||||
]
|
||||
|
||||
generate = [
|
||||
"GstAnalytics.RelTypes"
|
||||
]
|
||||
|
||||
manual = [
|
||||
"GObject.Object",
|
||||
"Gst.Element",
|
||||
"Gst.MiniObject",
|
||||
"Gst.Object"
|
||||
]
|
||||
|
||||
|
||||
[[object]]
|
||||
name = "Gst.Buffer"
|
||||
status = "manual"
|
||||
ref_mode = "ref"
|
1
gstreamer-analytics/LICENSE-APACHE
Symbolic link
1
gstreamer-analytics/LICENSE-APACHE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../LICENSE-APACHE
|
1
gstreamer-analytics/LICENSE-MIT
Symbolic link
1
gstreamer-analytics/LICENSE-MIT
Symbolic link
|
@ -0,0 +1 @@
|
|||
../LICENSE-MIT
|
214
gstreamer-analytics/README.md
Normal file
214
gstreamer-analytics/README.md
Normal file
|
@ -0,0 +1,214 @@
|
|||
# gstreamer-rs [![crates.io](https://img.shields.io/crates/v/gstreamer-app.svg)](https://crates.io/crates/gstreamer-app) [![pipeline status](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/badges/main/pipeline.svg)](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/commits/main)
|
||||
|
||||
[GStreamer](https://gstreamer.freedesktop.org/) (App library) bindings for Rust.
|
||||
Documentation can be found [here](https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_app/).
|
||||
|
||||
These bindings are providing a safe API that can be used to interface with
|
||||
GStreamer, e.g. for writing GStreamer-based applications and GStreamer plugins.
|
||||
|
||||
The bindings are mostly autogenerated with [gir](https://github.com/gtk-rs/gir/)
|
||||
based on the [GObject-Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection/)
|
||||
API metadata provided by the GStreamer project.
|
||||
|
||||
## Table of Contents
|
||||
1. [Installation](#installation)
|
||||
1. [Linux/BSDs](#installation-linux)
|
||||
1. [macOS](#installation-macos)
|
||||
1. [Windows](#installation-windows)
|
||||
1. [Getting Started](#getting-started)
|
||||
1. [License](#license)
|
||||
1. [Contribution](#contribution)
|
||||
|
||||
<a name="installation"/>
|
||||
|
||||
## Installation
|
||||
|
||||
To build the GStreamer bindings or anything depending on them, you need to
|
||||
have at least GStreamer 1.14 and gst-plugins-base 1.14 installed. In addition,
|
||||
some of the examples/tutorials require various GStreamer plugins to be
|
||||
available, which can be found in gst-plugins-base, gst-plugins-good,
|
||||
gst-plugins-bad, gst-plugins-ugly and/or gst-libav.
|
||||
|
||||
<a name="installation-linux"/>
|
||||
|
||||
### Linux/BSDs
|
||||
|
||||
You need to install the above mentioned packages with your distributions
|
||||
package manager, or build them from source.
|
||||
|
||||
On Debian/Ubuntu they can be installed with
|
||||
|
||||
```console
|
||||
$ apt-get install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
|
||||
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
|
||||
gstreamer1.0-libav libgstrtspserver-1.0-dev libges-1.0-dev
|
||||
```
|
||||
|
||||
The minimum required version of the above libraries is >= 1.14. If you
|
||||
build the gstreamer-player sub-crate, or any of the examples that
|
||||
depend on gstreamer-player, you must ensure that in addition to the above
|
||||
packages, `libgstreamer-plugins-bad1.0-dev` is installed. See the `Cargo.toml`
|
||||
files for the full details,
|
||||
|
||||
```console
|
||||
$ apt-get install libgstreamer-plugins-bad1.0-dev
|
||||
```
|
||||
|
||||
Package names on other distributions should be similar.
|
||||
Please submit a pull request with instructions for yours.
|
||||
|
||||
<a name="installation-macos"/>
|
||||
|
||||
### macOS
|
||||
|
||||
You can install GStreamer and the plugins via [Homebrew](https://brew.sh/) or
|
||||
by installing the [binaries](https://gstreamer.freedesktop.org/data/pkg/osx/)
|
||||
provided by the GStreamer project.
|
||||
|
||||
We recommend using the official GStreamer binaries over Homebrew, especially
|
||||
as GStreamer in Homebrew is [currently broken](https://github.com/orgs/Homebrew/discussions/3740#discussioncomment-3804964).
|
||||
|
||||
#### GStreamer Binaries
|
||||
|
||||
You need to download the *two* `.pkg` files from the GStreamer website and
|
||||
install them, e.g. `gstreamer-1.0-1.20.4-universal.pkg` and
|
||||
`gstreamer-1.0-devel-1.20.4-universal.pkg`.
|
||||
|
||||
After installation, you also need to set the `PATH` environment variable as
|
||||
follows
|
||||
|
||||
```console
|
||||
$ export PATH="/Library/Frameworks/GStreamer.framework/Versions/1.0/bin${PATH:+:$PATH}"
|
||||
```
|
||||
|
||||
Also note that the `pkg-config` from GStreamer should be the first one in
|
||||
the `PATH` as other versions have all kinds of quirks that will cause
|
||||
problems.
|
||||
|
||||
#### Homebrew
|
||||
|
||||
Homebrew only installs various plugins if explicitly enabled, so some extra
|
||||
`--with-*` flags may be required.
|
||||
|
||||
```console
|
||||
$ brew install gstreamer gst-plugins-base gst-plugins-good \
|
||||
gst-plugins-bad gst-plugins-ugly gst-libav gst-rtsp-server \
|
||||
gst-editing-services --with-orc --with-libogg --with-opus \
|
||||
--with-pango --with-theora --with-libvorbis --with-libvpx \
|
||||
--enable-gtk3
|
||||
```
|
||||
|
||||
Make sure the version of these libraries is >= 1.14.
|
||||
|
||||
<a name="installation-windows"/>
|
||||
|
||||
### Windows
|
||||
|
||||
You can install GStreamer and the plugins via [MSYS2](http://www.msys2.org/)
|
||||
with `pacman` or by installing the
|
||||
[binaries](https://gstreamer.freedesktop.org/data/pkg/windows/) provided by
|
||||
the GStreamer project.
|
||||
|
||||
We recommend using the official GStreamer binaries over MSYS2.
|
||||
|
||||
#### GStreamer Binaries
|
||||
|
||||
You need to download the *two* `.msi` files for your platform from the
|
||||
GStreamer website and install them, e.g. `gstreamer-1.0-x86_64-1.20.4.msi` and
|
||||
`gstreamer-1.0-devel-x86_64-1.20.4.msi`. Make sure to select the version that
|
||||
matches your Rust toolchain, i.e. MinGW or MSVC.
|
||||
|
||||
After installation set the ``PATH` environment variable as follows:
|
||||
|
||||
```console
|
||||
# For a UNIX-style shell:
|
||||
$ export PATH="c:/gstreamer/1.0/msvc_x86_64/bin${PATH:+:$PATH}"
|
||||
|
||||
# For cmd.exe:
|
||||
$ set PATH=C:\gstreamer\1.0\msvc_x86_64\bin;%PATH%
|
||||
```
|
||||
|
||||
Make sure to update the path to where you have actually installed GStreamer
|
||||
and for the corresponding toolchain.
|
||||
|
||||
Also note that the `pkg-config.exe` from GStreamer should be the first one in
|
||||
the `PATH` as other versions have all kinds of quirks that will cause
|
||||
problems.
|
||||
|
||||
#### MSYS2 / pacman
|
||||
|
||||
```console
|
||||
$ pacman -S glib2-devel pkg-config \
|
||||
mingw-w64-x86_64-gstreamer mingw-w64-x86_64-gst-plugins-base \
|
||||
mingw-w64-x86_64-gst-plugins-good mingw-w64-x86_64-gst-plugins-bad \
|
||||
mingw-w64-x86_64-gst-plugins-ugly mingw-w64-x86_64-gst-libav \
|
||||
mingw-w64-x86_64-gst-rtsp-server
|
||||
```
|
||||
|
||||
Make sure the version of these libraries is >= 1.14.
|
||||
|
||||
Note that the version of `pkg-config` included in `MSYS2` is
|
||||
[known to have problems](https://github.com/rust-lang/pkg-config-rs/issues/51#issuecomment-346300858)
|
||||
compiling GStreamer, so you may need to install another version. One option
|
||||
would be [`pkg-config-lite`](https://sourceforge.net/projects/pkgconfiglite/).
|
||||
|
||||
<a name="getting-started"/>
|
||||
|
||||
## Getting Started
|
||||
|
||||
The API reference can be found
|
||||
[here](https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer/), however it is
|
||||
only the Rust API reference and does not explain any of the concepts.
|
||||
|
||||
For getting started with GStreamer development, the best would be to follow
|
||||
the [documentation](https://gstreamer.freedesktop.org/documentation/) on the
|
||||
GStreamer website, especially the [Application Development
|
||||
Manual](https://gstreamer.freedesktop.org/documentation/application-development/).
|
||||
While being C-centric, it explains all the fundamental concepts of GStreamer
|
||||
and the code examples should be relatively easily translatable to Rust. The
|
||||
API is basically the same, function/struct names are the same and everything
|
||||
is only more convenient (hopefully) and safer.
|
||||
|
||||
In addition there are
|
||||
[tutorials](https://gstreamer.freedesktop.org/documentation/tutorials/) on the
|
||||
GStreamer website. Many of them were ported to Rust already and the code can
|
||||
be found in the
|
||||
[tutorials](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/tree/main/tutorials)
|
||||
directory.
|
||||
|
||||
Some further examples for various aspects of GStreamer and how to use it from
|
||||
Rust can be found in the
|
||||
[examples](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/tree/main/examples)
|
||||
directory.
|
||||
|
||||
Various GStreamer plugins written in Rust can be found in the
|
||||
[gst-plugins-rs](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs)
|
||||
repository.
|
||||
|
||||
<a name="license"/>
|
||||
|
||||
## LICENSE
|
||||
|
||||
gstreamer-rs and all crates contained in here are licensed under either of
|
||||
|
||||
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||
http://www.apache.org/licenses/LICENSE-2.0)
|
||||
* MIT license ([LICENSE-MIT](LICENSE-MIT) or
|
||||
http://opensource.org/licenses/MIT)
|
||||
|
||||
at your option.
|
||||
|
||||
GStreamer itself is licensed under the Lesser General Public License version
|
||||
2.1 or (at your option) any later version:
|
||||
https://www.gnu.org/licenses/lgpl-2.1.html
|
||||
|
||||
<a name="contribution"/>
|
||||
|
||||
## Contribution
|
||||
|
||||
Any kinds of contributions are welcome as a pull request.
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in gstreamer-rs by you, as defined in the Apache-2.0 license, shall be
|
||||
dual licensed as above, without any additional terms or conditions.
|
44
gstreamer-analytics/src/auto/flags.rs
Normal file
44
gstreamer-analytics/src/auto/flags.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
// 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 glib::{bitflags::bitflags, translate::*};
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[doc(alias = "GstAnalyticsRelTypes")]
|
||||
pub struct RelTypes: u32 {
|
||||
#[doc(alias = "GST_ANALYTICS_REL_TYPE_NONE")]
|
||||
const NONE = ffi::GST_ANALYTICS_REL_TYPE_NONE as _;
|
||||
#[doc(alias = "GST_ANALYTICS_REL_TYPE_IS_PART_OF")]
|
||||
const IS_PART_OF = ffi::GST_ANALYTICS_REL_TYPE_IS_PART_OF as _;
|
||||
#[doc(alias = "GST_ANALYTICS_REL_TYPE_CONTAIN")]
|
||||
const CONTAIN = ffi::GST_ANALYTICS_REL_TYPE_CONTAIN as _;
|
||||
#[doc(alias = "GST_ANALYTICS_REL_TYPE_RELATE_TO")]
|
||||
const RELATE_TO = ffi::GST_ANALYTICS_REL_TYPE_RELATE_TO as _;
|
||||
#[doc(alias = "GST_ANALYTICS_REL_TYPE_LAST")]
|
||||
const LAST = ffi::GST_ANALYTICS_REL_TYPE_LAST as _;
|
||||
#[doc(alias = "GST_ANALYTICS_REL_TYPE_ANY")]
|
||||
const ANY = ffi::GST_ANALYTICS_REL_TYPE_ANY as _;
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl IntoGlib for RelTypes {
|
||||
type GlibType = ffi::GstAnalyticsRelTypes;
|
||||
|
||||
#[inline]
|
||||
fn into_glib(self) -> ffi::GstAnalyticsRelTypes {
|
||||
self.bits()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromGlib<ffi::GstAnalyticsRelTypes> for RelTypes {
|
||||
#[inline]
|
||||
unsafe fn from_glib(value: ffi::GstAnalyticsRelTypes) -> Self {
|
||||
skip_assert_initialized!();
|
||||
Self::from_bits_truncate(value)
|
||||
}
|
||||
}
|
7
gstreamer-analytics/src/auto/mod.rs
Normal file
7
gstreamer-analytics/src/auto/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
// 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
|
||||
|
||||
mod flags;
|
||||
pub use self::flags::RelTypes;
|
3
gstreamer-analytics/src/auto/versions.txt
Normal file
3
gstreamer-analytics/src/auto/versions.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
Generated by gir (https://github.com/gtk-rs/gir @ 5223ce91b97a)
|
||||
from gir-files (https://github.com/gtk-rs/gir-files @ 6cd7b656acd6)
|
||||
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ c988e03b5e99)
|
212
gstreamer-analytics/src/classification.rs
Normal file
212
gstreamer-analytics/src/classification.rs
Normal file
|
@ -0,0 +1,212 @@
|
|||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
use glib::translate::*;
|
||||
|
||||
use crate::relation_meta::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AnalyticsClassificationMtd {}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl<T: super::AnalyticsRelationMetaClassificationExt> Sealed for T {}
|
||||
}
|
||||
|
||||
pub trait AnalyticsRelationMetaClassificationExt: sealed::Sealed {
|
||||
fn add_one_cls_mtd(
|
||||
&mut self,
|
||||
confidence_level: f32,
|
||||
class_quark: glib::Quark,
|
||||
) -> Result<AnalyticsMtdRef<AnalyticsClassificationMtd>, glib::BoolError>;
|
||||
|
||||
fn add_cls_mtd(
|
||||
&mut self,
|
||||
confidence_levels: &[f32],
|
||||
class_quarks: &[glib::Quark],
|
||||
) -> Result<AnalyticsMtdRef<AnalyticsClassificationMtd>, glib::BoolError>;
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsRelationMetaClassificationExt
|
||||
for gst::MetaRefMut<'a, AnalyticsRelationMeta, gst::meta::Standalone>
|
||||
{
|
||||
#[doc(alias = "gst_analytics_relation_meta_add_one_cls_mtd")]
|
||||
fn add_one_cls_mtd(
|
||||
&mut self,
|
||||
confidence_level: f32,
|
||||
class_quark: glib::Quark,
|
||||
) -> Result<AnalyticsMtdRef<'a, AnalyticsClassificationMtd>, glib::BoolError> {
|
||||
unsafe {
|
||||
let mut mtd = std::mem::MaybeUninit::uninit();
|
||||
let ret = from_glib(ffi::gst_analytics_relation_meta_add_one_cls_mtd(
|
||||
self.as_mut_ptr(),
|
||||
confidence_level,
|
||||
class_quark.into_glib(),
|
||||
mtd.as_mut_ptr(),
|
||||
));
|
||||
let id = mtd.assume_init().id;
|
||||
|
||||
if ret {
|
||||
Ok(AnalyticsMtdRef::from_meta(self.as_ref(), id))
|
||||
} else {
|
||||
Err(glib::bool_error!("Couldn't add more data"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_relation_meta_add_cls_mtd")]
|
||||
fn add_cls_mtd(
|
||||
&mut self,
|
||||
confidence_levels: &[f32],
|
||||
class_quarks: &[glib::Quark],
|
||||
) -> Result<AnalyticsMtdRef<'a, AnalyticsClassificationMtd>, glib::BoolError> {
|
||||
let length = std::cmp::min(confidence_levels.len(), class_quarks.len());
|
||||
unsafe {
|
||||
let mut mtd = std::mem::MaybeUninit::uninit();
|
||||
let ret = from_glib(ffi::gst_analytics_relation_meta_add_cls_mtd(
|
||||
self.as_mut_ptr(),
|
||||
length,
|
||||
mut_override(confidence_levels.as_ptr()),
|
||||
class_quarks.as_ptr() as *mut _,
|
||||
mtd.as_mut_ptr(),
|
||||
));
|
||||
let id = mtd.assume_init().id;
|
||||
|
||||
if ret {
|
||||
Ok(AnalyticsMtdRef::from_meta(self.as_ref(), id))
|
||||
} else {
|
||||
Err(glib::bool_error!("Couldn't add more data"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl AnalyticsMtd for AnalyticsClassificationMtd {
|
||||
#[doc(alias = "gst_analytics_cls_mtd_get_mtd_type")]
|
||||
fn mtd_type() -> ffi::GstAnalyticsMtdType {
|
||||
unsafe { ffi::gst_analytics_cls_mtd_get_mtd_type() }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn from(t: ffi::GstAnalyticsMtd) -> ffi::GstAnalyticsClsMtd {
|
||||
std::mem::transmute(t)
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsMtdRef<'a, AnalyticsClassificationMtd> {
|
||||
#[doc(alias = "gst_analytics_cls_mtd_get_length")]
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
ffi::gst_analytics_cls_mtd_get_length(&mut mtd)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_cls_mtd_get_level")]
|
||||
pub fn level(&self, index: usize) -> f32 {
|
||||
assert!(index < self.len());
|
||||
|
||||
unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
ffi::gst_analytics_cls_mtd_get_level(&mut mtd, index)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_cls_mtd_get_quark")]
|
||||
pub fn quark(&self, index: usize) -> glib::Quark {
|
||||
assert!(index < self.len());
|
||||
|
||||
unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
from_glib(ffi::gst_analytics_cls_mtd_get_quark(&mut mtd, index))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iterate(&self) -> AnalyticsClassificationIterator {
|
||||
AnalyticsClassificationIterator {
|
||||
mtd: self,
|
||||
index: 0,
|
||||
length: self.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AnalyticsClassificationIterator<'a> {
|
||||
mtd: &'a AnalyticsMtdRef<'a, AnalyticsClassificationMtd>,
|
||||
index: usize,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for AnalyticsClassificationIterator<'a> {
|
||||
type Item = (glib::Quark, f32);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.index == self.length {
|
||||
None
|
||||
} else {
|
||||
let ret = Some((self.mtd.quark(self.index), self.mtd.level(self.index)));
|
||||
self.index += 1;
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn one_class() {
|
||||
gst::init().unwrap();
|
||||
|
||||
assert_eq!(AnalyticsClassificationMtd::type_name(), "classification");
|
||||
|
||||
let mut buf = gst::Buffer::new();
|
||||
let mut meta = AnalyticsRelationMeta::add(buf.make_mut());
|
||||
|
||||
assert!(meta.is_empty());
|
||||
|
||||
let cls = meta
|
||||
.add_one_cls_mtd(0.7, glib::Quark::from_str("class1"))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(cls.len(), 1);
|
||||
assert_eq!(cls.level(0), 0.7);
|
||||
assert_eq!(cls.quark(0), glib::Quark::from_str("class1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_classes() {
|
||||
gst::init().unwrap();
|
||||
|
||||
assert_eq!(AnalyticsClassificationMtd::type_name(), "classification");
|
||||
|
||||
let mut buf = gst::Buffer::new();
|
||||
let mut meta = AnalyticsRelationMeta::add(buf.make_mut());
|
||||
|
||||
assert!(meta.is_empty());
|
||||
|
||||
let classes = [
|
||||
glib::Quark::from_str("a"),
|
||||
glib::Quark::from_str("b"),
|
||||
glib::Quark::from_str("c"),
|
||||
glib::Quark::from_str("d"),
|
||||
];
|
||||
let levels = [0.1, 0.2, 0.3, 0.4];
|
||||
|
||||
let cls = meta.add_cls_mtd(&levels, &classes).unwrap();
|
||||
|
||||
assert_eq!(cls.len(), 4);
|
||||
for i in 0..4usize {
|
||||
assert_eq!(cls.level(i), levels[i]);
|
||||
assert_eq!(cls.quark(i), classes[i]);
|
||||
}
|
||||
|
||||
for (i, (q, l)) in cls.iterate().enumerate() {
|
||||
assert_eq!(l, levels[i]);
|
||||
assert_eq!(q, classes[i]);
|
||||
}
|
||||
}
|
||||
}
|
36
gstreamer-analytics/src/lib.rs
Normal file
36
gstreamer-analytics/src/lib.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub use ffi;
|
||||
pub use glib;
|
||||
pub use gst;
|
||||
|
||||
macro_rules! skip_assert_initialized {
|
||||
() => {};
|
||||
}
|
||||
|
||||
mod auto;
|
||||
pub use crate::auto::*;
|
||||
|
||||
mod relation_meta;
|
||||
pub use crate::relation_meta::*;
|
||||
|
||||
mod object_detection;
|
||||
pub use crate::object_detection::*;
|
||||
|
||||
mod tracking;
|
||||
pub use crate::tracking::*;
|
||||
|
||||
mod classification;
|
||||
pub use crate::classification::*;
|
||||
|
||||
// Re-export all the traits in a prelude module, so that applications
|
||||
// can always "use gst_app::prelude::*" without getting conflicts
|
||||
pub mod prelude {
|
||||
pub use crate::classification::AnalyticsRelationMetaClassificationExt;
|
||||
pub use crate::object_detection::AnalyticsRelationMetaODExt;
|
||||
pub use crate::tracking::AnalyticsRelationMetaTrackingExt;
|
||||
}
|
175
gstreamer-analytics/src/object_detection.rs
Normal file
175
gstreamer-analytics/src/object_detection.rs
Normal file
|
@ -0,0 +1,175 @@
|
|||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
use glib::translate::*;
|
||||
|
||||
use crate::relation_meta::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AnalyticsODMtd {}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl<T: super::AnalyticsRelationMetaODExt> Sealed for T {}
|
||||
}
|
||||
|
||||
pub trait AnalyticsRelationMetaODExt: sealed::Sealed {
|
||||
fn add_od_mtd(
|
||||
&mut self,
|
||||
type_: glib::Quark,
|
||||
x: i32,
|
||||
y: i32,
|
||||
w: i32,
|
||||
h: i32,
|
||||
loc_conf_lvl: f32,
|
||||
) -> Result<AnalyticsMtdRef<AnalyticsODMtd>, glib::BoolError>;
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsRelationMetaODExt
|
||||
for gst::MetaRefMut<'a, AnalyticsRelationMeta, gst::meta::Standalone>
|
||||
{
|
||||
#[doc(alias = "gst_analytics_relation_meta_add_od_mtd")]
|
||||
fn add_od_mtd(
|
||||
&mut self,
|
||||
type_: glib::Quark,
|
||||
x: i32,
|
||||
y: i32,
|
||||
w: i32,
|
||||
h: i32,
|
||||
loc_conf_lvl: f32,
|
||||
) -> Result<AnalyticsMtdRef<AnalyticsODMtd>, glib::BoolError> {
|
||||
unsafe {
|
||||
let mut mtd = std::mem::MaybeUninit::uninit();
|
||||
let ret = from_glib(ffi::gst_analytics_relation_meta_add_od_mtd(
|
||||
self.as_mut_ptr(),
|
||||
type_.into_glib(),
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
loc_conf_lvl,
|
||||
mtd.as_mut_ptr(),
|
||||
));
|
||||
let id = mtd.assume_init().id;
|
||||
|
||||
if ret {
|
||||
Ok(AnalyticsMtdRef::from_meta(self.as_ref(), id))
|
||||
} else {
|
||||
Err(glib::bool_error!("Couldn't add more data"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct AnalyticsODLocation {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: i32,
|
||||
pub h: i32,
|
||||
pub loc_conf_lvl: f32,
|
||||
}
|
||||
|
||||
unsafe impl AnalyticsMtd for AnalyticsODMtd {
|
||||
#[doc(alias = "gst_analytics_od_mtd_get_mtd_type")]
|
||||
fn mtd_type() -> ffi::GstAnalyticsMtdType {
|
||||
unsafe { ffi::gst_analytics_od_mtd_get_mtd_type() }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn from(t: ffi::GstAnalyticsMtd) -> ffi::GstAnalyticsODMtd {
|
||||
std::mem::transmute(t)
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsMtdRef<'a, AnalyticsODMtd> {
|
||||
#[doc(alias = "gst_analytics_od_mtd_get_obj_type")]
|
||||
pub fn obj_type(&self) -> Option<glib::Quark> {
|
||||
unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
let type_ = ffi::gst_analytics_od_mtd_get_obj_type(&mut mtd);
|
||||
if type_ == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(glib::Quark::from_glib(type_))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_od_mtd_get_location")]
|
||||
pub fn location(&self) -> Result<AnalyticsODLocation, glib::BoolError> {
|
||||
let mut loc = AnalyticsODLocation::default();
|
||||
|
||||
let success = unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
ffi::gst_analytics_od_mtd_get_location(
|
||||
&mut mtd,
|
||||
&mut loc.x,
|
||||
&mut loc.y,
|
||||
&mut loc.w,
|
||||
&mut loc.h,
|
||||
&mut loc.loc_conf_lvl,
|
||||
)
|
||||
};
|
||||
|
||||
if success != 0 {
|
||||
Ok(loc)
|
||||
} else {
|
||||
Err(glib::bool_error!("Could retrieve location"))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_od_mtd_get_confidence_lvl")]
|
||||
pub fn confidence_level(&self) -> f32 {
|
||||
unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
let mut lvl: f32 = 0.0;
|
||||
ffi::gst_analytics_od_mtd_get_confidence_lvl(&mut mtd, &mut lvl);
|
||||
lvl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn object_detection() {
|
||||
gst::init().unwrap();
|
||||
|
||||
assert_eq!(AnalyticsODMtd::type_name(), "object-detection");
|
||||
|
||||
let mut buf = gst::Buffer::new();
|
||||
let mut meta = AnalyticsRelationMeta::add(buf.make_mut());
|
||||
|
||||
assert!(meta.is_empty());
|
||||
|
||||
let od = meta
|
||||
.add_od_mtd(glib::Quark::from_str("blb"), 0, 1, 10, 20, 0.8)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(od.obj_type().unwrap(), glib::Quark::from_str("blb"));
|
||||
|
||||
let loc = od.location().unwrap();
|
||||
|
||||
assert_eq!(loc.x, 0);
|
||||
assert_eq!(loc.y, 1);
|
||||
assert_eq!(loc.w, 10);
|
||||
assert_eq!(loc.h, 20);
|
||||
assert_eq!(loc.loc_conf_lvl, 0.8);
|
||||
let meta = buf.meta::<AnalyticsRelationMeta>().unwrap();
|
||||
|
||||
assert!(meta.mtd::<AnalyticsODMtd>(1).is_none());
|
||||
|
||||
let meta2 = buf.meta::<AnalyticsRelationMeta>().unwrap();
|
||||
let od2 = meta2.mtd::<AnalyticsODMtd>(0).unwrap();
|
||||
|
||||
assert_eq!(od2.obj_type().unwrap(), glib::Quark::from_str("blb"));
|
||||
let loc = od2.location().unwrap();
|
||||
|
||||
assert_eq!(loc.x, 0);
|
||||
assert_eq!(loc.y, 1);
|
||||
assert_eq!(loc.w, 10);
|
||||
assert_eq!(loc.h, 20);
|
||||
assert_eq!(loc.loc_conf_lvl, 0.8);
|
||||
}
|
||||
}
|
706
gstreamer-analytics/src/relation_meta.rs
Normal file
706
gstreamer-analytics/src/relation_meta.rs
Normal file
|
@ -0,0 +1,706 @@
|
|||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
use glib::translate::*;
|
||||
use gst::prelude::*;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::RelTypes;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[doc(alias = "GstAnalyticsRelationMeta")]
|
||||
pub struct AnalyticsRelationMeta(ffi::GstAnalyticsRelationMeta);
|
||||
|
||||
unsafe impl Send for AnalyticsRelationMeta {}
|
||||
unsafe impl Sync for AnalyticsRelationMeta {}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[doc(alias = "GstAnalyticsRelationMetaInitParams")]
|
||||
pub struct AnalyticsRelationMetaInitParams(ffi::GstAnalyticsRelationMetaInitParams);
|
||||
|
||||
impl Default for AnalyticsRelationMetaInitParams {
|
||||
fn default() -> Self {
|
||||
Self(ffi::GstAnalyticsRelationMetaInitParams {
|
||||
initial_relation_order: 0,
|
||||
initial_buf_size: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl AnalyticsRelationMetaInitParams {
|
||||
pub fn new(initial_relation_order: usize, initial_buf_size: usize) -> Self {
|
||||
skip_assert_initialized!();
|
||||
Self(ffi::GstAnalyticsRelationMetaInitParams {
|
||||
initial_relation_order,
|
||||
initial_buf_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AnalyticsMtdRef<'a, T: AnalyticsMtd> {
|
||||
id: u32,
|
||||
meta: gst::MetaRef<'a, AnalyticsRelationMeta>,
|
||||
mtd_type: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnalyticsMtdRefMut<'a, T: AnalyticsMtd> {
|
||||
id: u32,
|
||||
meta: &'a mut gst::MetaRefMut<'a, AnalyticsRelationMeta, gst::meta::Standalone>,
|
||||
mtd_type: PhantomData<&'a T>,
|
||||
}
|
||||
|
||||
pub struct AnalyticsRelationPath {
|
||||
garray: *mut glib::ffi::GArray,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AnalyticsRelationMeta {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.debug_struct("AnalyticsRelationMeta")
|
||||
.field("len", &self.len())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AnalyticsRelationMeta {
|
||||
#[doc(alias = "gst_buffer_add_analytics_relation_meta")]
|
||||
pub fn add(buffer: &mut gst::BufferRef) -> gst::MetaRefMut<Self, gst::meta::Standalone> {
|
||||
skip_assert_initialized!();
|
||||
|
||||
unsafe {
|
||||
let meta_ptr = ffi::gst_buffer_add_analytics_relation_meta(buffer.as_mut_ptr());
|
||||
Self::from_mut_ptr(buffer, meta_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_buffer_add_analytics_relation_meta_full")]
|
||||
pub fn add_full<'a>(
|
||||
buffer: &'a mut gst::BufferRef,
|
||||
init_params: &AnalyticsRelationMetaInitParams,
|
||||
) -> gst::MetaRefMut<'a, Self, gst::meta::Standalone> {
|
||||
skip_assert_initialized!();
|
||||
|
||||
unsafe {
|
||||
let meta_ptr = ffi::gst_buffer_add_analytics_relation_meta_full(
|
||||
buffer.as_mut_ptr(),
|
||||
mut_override(&init_params.0),
|
||||
);
|
||||
Self::from_mut_ptr(buffer, meta_ptr)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_relation_get_length")]
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe { ffi::gst_analytics_relation_get_length(self.as_mut_ptr()) }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_relation_meta_set_relation")]
|
||||
pub fn set_relation(
|
||||
&mut self,
|
||||
type_: crate::RelTypes,
|
||||
an_meta_first_id: u32,
|
||||
an_meta_second_id: u32,
|
||||
) -> Result<(), glib::BoolError> {
|
||||
let ret = unsafe {
|
||||
from_glib(ffi::gst_analytics_relation_meta_set_relation(
|
||||
self.as_mut_ptr(),
|
||||
type_.into_glib(),
|
||||
an_meta_first_id,
|
||||
an_meta_second_id,
|
||||
))
|
||||
};
|
||||
|
||||
if ret {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(glib::bool_error!(
|
||||
"Could not set relation {:}->{:} of type {:?}",
|
||||
an_meta_first_id,
|
||||
an_meta_second_id,
|
||||
type_
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_relation_meta_get_relation")]
|
||||
pub fn relation(&self, an_meta_first_id: u32, an_meta_second_id: u32) -> crate::RelTypes {
|
||||
unsafe {
|
||||
from_glib(ffi::gst_analytics_relation_meta_get_relation(
|
||||
self.as_mut_ptr(),
|
||||
an_meta_first_id,
|
||||
an_meta_second_id,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_relation_meta_exist")]
|
||||
pub fn exist(
|
||||
&self,
|
||||
an_meta_first_id: u32,
|
||||
an_meta_second_id: u32,
|
||||
relation_span: i32,
|
||||
cond_types: crate::RelTypes,
|
||||
) -> bool {
|
||||
unsafe {
|
||||
from_glib(ffi::gst_analytics_relation_meta_exist(
|
||||
self.as_mut_ptr(),
|
||||
an_meta_first_id,
|
||||
an_meta_second_id,
|
||||
relation_span,
|
||||
cond_types.into_glib(),
|
||||
std::ptr::null_mut(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_relation_meta_exist")]
|
||||
pub fn exist_path(
|
||||
&self,
|
||||
an_meta_first_id: u32,
|
||||
an_meta_second_id: u32,
|
||||
relation_span: i32,
|
||||
cond_types: crate::RelTypes,
|
||||
) -> Result<AnalyticsRelationPath, glib::BoolError> {
|
||||
let mut array = std::ptr::null_mut::<glib::ffi::GArray>();
|
||||
let ret = unsafe {
|
||||
from_glib(ffi::gst_analytics_relation_meta_exist(
|
||||
self.as_mut_ptr(),
|
||||
an_meta_first_id,
|
||||
an_meta_second_id,
|
||||
relation_span,
|
||||
cond_types.into_glib(),
|
||||
&mut array,
|
||||
))
|
||||
};
|
||||
|
||||
if ret {
|
||||
Ok(AnalyticsRelationPath { garray: array })
|
||||
} else {
|
||||
Err(glib::bool_error!("Such relation doesn't exist"))
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn as_mut_ptr(&self) -> *mut ffi::GstAnalyticsRelationMeta {
|
||||
mut_override(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl UnsafeFrom<&AnalyticsRelationMeta> for ffi::GstAnalyticsMtd {
|
||||
unsafe fn unsafe_from(t: &AnalyticsRelationMeta) -> Self {
|
||||
ffi::GstAnalyticsMtd {
|
||||
id: 0,
|
||||
meta: t.as_mut_ptr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnalyticsRelationPath {
|
||||
pub fn as_slice(&self) -> &[u32] {
|
||||
unsafe {
|
||||
std::slice::from_raw_parts(
|
||||
(*self.garray).data as *const u32,
|
||||
(*self.garray).len as usize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AnalyticsRelationPath {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
glib::ffi::g_array_free(self.garray, glib::ffi::GTRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl<T> Sealed for T {}
|
||||
}
|
||||
|
||||
pub trait AnalyticsMetaRefExt<'a>: sealed::Sealed {
|
||||
#[doc(alias = "gst_analytics_relation_meta_get_mtd")]
|
||||
fn mtd<T: AnalyticsMtd>(&self, an_meta_id: u32) -> Option<AnalyticsMtdRef<'a, T>>;
|
||||
fn iter<T: AnalyticsMtd>(&'a self) -> AnalyticsMtdIter<T>;
|
||||
fn iter_direct_related<T: AnalyticsMtd>(
|
||||
&'a self,
|
||||
an_meta_id: u32,
|
||||
rel_type: RelTypes,
|
||||
) -> AnalyticsMtdIter<T>;
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsMetaRefExt<'a> for gst::MetaRef<'a, AnalyticsRelationMeta> {
|
||||
fn mtd<T: AnalyticsMtd>(&self, an_meta_id: u32) -> Option<AnalyticsMtdRef<'a, T>> {
|
||||
unsafe {
|
||||
let mut mtd = std::mem::MaybeUninit::uninit();
|
||||
let ret = from_glib(ffi::gst_analytics_relation_meta_get_mtd(
|
||||
self.as_mut_ptr(),
|
||||
an_meta_id,
|
||||
T::mtd_type(),
|
||||
mtd.as_mut_ptr(),
|
||||
));
|
||||
let id = mtd.assume_init().id;
|
||||
|
||||
if ret {
|
||||
Some(AnalyticsMtdRef::from_meta(self, id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn iter<T: AnalyticsMtd>(&'a self) -> AnalyticsMtdIter<'a, T> {
|
||||
AnalyticsMtdIter::new(self)
|
||||
}
|
||||
fn iter_direct_related<T: AnalyticsMtd>(
|
||||
&'a self,
|
||||
an_meta_id: u32,
|
||||
rel_type: RelTypes,
|
||||
) -> AnalyticsMtdIter<T> {
|
||||
AnalyticsMtdIter::new_direct_related(self, an_meta_id, rel_type.into_glib())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AnalyticsMtd> AnalyticsMtdRef<'a, T> {
|
||||
pub fn id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub unsafe fn from_meta(meta: &gst::MetaRef<'a, AnalyticsRelationMeta>, id: u32) -> Self {
|
||||
skip_assert_initialized!();
|
||||
AnalyticsMtdRef {
|
||||
meta: meta.clone(),
|
||||
id,
|
||||
mtd_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_mtd_get_mtd_type")]
|
||||
pub fn mtd_type(&self) -> ffi::GstAnalyticsMtdType {
|
||||
unsafe {
|
||||
let mut mtd = ffi::GstAnalyticsMtd::unsafe_from(self);
|
||||
ffi::gst_analytics_mtd_get_mtd_type(&mut mtd)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> AnalyticsMtdRef<'a, AnalyticsAnyMtd> {
|
||||
pub fn downcast<T: AnalyticsMtd>(
|
||||
self,
|
||||
) -> Result<AnalyticsMtdRef<'a, T>, AnalyticsMtdRef<'a, AnalyticsAnyMtd>> {
|
||||
if self.mtd_type() == T::mtd_type() {
|
||||
Ok(AnalyticsMtdRef {
|
||||
id: self.id,
|
||||
meta: self.meta,
|
||||
mtd_type: PhantomData,
|
||||
})
|
||||
} else {
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn downcast_ref<T: AnalyticsMtd>(&self) -> Option<&AnalyticsMtdRef<'a, T>> {
|
||||
unsafe {
|
||||
if self.mtd_type() == T::mtd_type() {
|
||||
Some(&*(self as *const _ as *const _))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsMtdRefMut<'a, AnalyticsAnyMtd> {
|
||||
pub fn downcast_mut<T: AnalyticsMtd>(&mut self) -> Option<&mut AnalyticsMtdRefMut<'a, T>> {
|
||||
unsafe {
|
||||
if self.as_ref().mtd_type() == T::mtd_type() {
|
||||
Some(&mut *(self as *mut _ as *mut _))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AnalyticsMtd> UnsafeFrom<&AnalyticsMtdRef<'a, T>> for ffi::GstAnalyticsMtd {
|
||||
unsafe fn unsafe_from(t: &AnalyticsMtdRef<'a, T>) -> Self {
|
||||
ffi::GstAnalyticsMtd {
|
||||
id: t.id,
|
||||
meta: t.meta.as_mut_ptr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait AnalyticsMetaRefMutExt<'a>: sealed::Sealed {
|
||||
#[doc(alias = "gst_analytics_relation_meta_get_mtd")]
|
||||
fn mtd_mut<T: AnalyticsMtd>(&'a mut self, an_meta_id: u32)
|
||||
-> Option<AnalyticsMtdRefMut<'a, T>>;
|
||||
|
||||
fn iter_mut<T: AnalyticsMtd>(&'a mut self) -> AnalyticsMtdIterMut<T>;
|
||||
fn iter_direct_related_mut<T: AnalyticsMtd>(
|
||||
&'a mut self,
|
||||
an_meta_id: u32,
|
||||
rel_type: RelTypes,
|
||||
) -> AnalyticsMtdIterMut<T>;
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsMetaRefMutExt<'a>
|
||||
for gst::MetaRefMut<'a, AnalyticsRelationMeta, gst::meta::Standalone>
|
||||
{
|
||||
fn mtd_mut<T: AnalyticsMtd>(
|
||||
&'a mut self,
|
||||
an_meta_id: u32,
|
||||
) -> Option<AnalyticsMtdRefMut<'a, T>> {
|
||||
unsafe {
|
||||
let mut mtd = std::mem::MaybeUninit::uninit();
|
||||
let ret = from_glib(ffi::gst_analytics_relation_meta_get_mtd(
|
||||
self.as_mut_ptr(),
|
||||
an_meta_id,
|
||||
T::mtd_type(),
|
||||
mtd.as_mut_ptr(),
|
||||
));
|
||||
let id = mtd.assume_init().id;
|
||||
|
||||
if ret {
|
||||
Some(AnalyticsMtdRefMut::from_meta(self, id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_mut<T: AnalyticsMtd>(&'a mut self) -> AnalyticsMtdIterMut<T> {
|
||||
AnalyticsMtdIterMut::new(self)
|
||||
}
|
||||
fn iter_direct_related_mut<T: AnalyticsMtd>(
|
||||
&'a mut self,
|
||||
an_meta_id: u32,
|
||||
rel_type: RelTypes,
|
||||
) -> AnalyticsMtdIterMut<T> {
|
||||
AnalyticsMtdIterMut::new_direct_related(self, an_meta_id, rel_type.into_glib())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl MetaAPI for AnalyticsRelationMeta {
|
||||
type GstType = ffi::GstAnalyticsRelationMeta;
|
||||
|
||||
#[doc(alias = "gst_analytics_relation_meta_api_get_type")]
|
||||
#[inline]
|
||||
fn meta_api() -> glib::Type {
|
||||
unsafe { from_glib(ffi::gst_analytics_relation_meta_api_get_type()) }
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe trait AnalyticsMtd {
|
||||
fn mtd_type() -> ffi::GstAnalyticsMtdType;
|
||||
}
|
||||
|
||||
pub trait AnalyticsMtdExt: AnalyticsMtd {
|
||||
#[doc(alias = "gst_analytics_mtd_type_get_name")]
|
||||
fn type_name() -> &'static str {
|
||||
unsafe {
|
||||
let ptr = ffi::gst_analytics_mtd_type_get_name(Self::mtd_type());
|
||||
std::ffi::CStr::from_ptr(ptr).to_str().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AnalyticsMtd> AnalyticsMtdExt for T {}
|
||||
|
||||
impl<'a, T: AnalyticsMtd> AnalyticsMtdRefMut<'a, T> {
|
||||
pub fn id(&self) -> u32 {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub unsafe fn from_meta(
|
||||
meta: &'a mut gst::MetaRefMut<'a, AnalyticsRelationMeta, gst::meta::Standalone>,
|
||||
id: u32,
|
||||
) -> Self {
|
||||
skip_assert_initialized!();
|
||||
AnalyticsMtdRefMut {
|
||||
meta,
|
||||
id,
|
||||
mtd_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AnalyticsMtd> UnsafeFrom<&mut AnalyticsMtdRefMut<'a, T>> for ffi::GstAnalyticsMtd {
|
||||
unsafe fn unsafe_from(t: &mut AnalyticsMtdRefMut<'a, T>) -> Self {
|
||||
ffi::GstAnalyticsMtd {
|
||||
id: t.id,
|
||||
meta: t.meta.as_mut_ptr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AnalyticsMtd> From<AnalyticsMtdRefMut<'a, T>> for AnalyticsMtdRef<'a, T> {
|
||||
fn from(value: AnalyticsMtdRefMut<'a, T>) -> Self {
|
||||
skip_assert_initialized!();
|
||||
AnalyticsMtdRef {
|
||||
meta: value.meta.as_ref().clone(),
|
||||
id: value.id,
|
||||
mtd_type: value.mtd_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AnalyticsMtd> From<&mut AnalyticsMtdRefMut<'a, T>> for AnalyticsMtdRef<'a, T> {
|
||||
fn from(value: &mut AnalyticsMtdRefMut<'a, T>) -> Self {
|
||||
skip_assert_initialized!();
|
||||
AnalyticsMtdRef {
|
||||
meta: value.meta.as_ref().clone(),
|
||||
id: value.id,
|
||||
mtd_type: value.mtd_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AnalyticsMtd> AsRef<AnalyticsMtdRef<'a, T>> for AnalyticsMtdRefMut<'a, T> {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &AnalyticsMtdRef<'a, T> {
|
||||
unsafe { &*(self as *const AnalyticsMtdRefMut<'a, T> as *const AnalyticsMtdRef<'a, T>) }
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_mtd_iter {
|
||||
($name:ident, $metaref:ty, $itemref:ty, $copy_meta:expr) => {
|
||||
pub struct $name<'a, T: AnalyticsMtd> {
|
||||
meta: $metaref,
|
||||
state: glib::ffi::gpointer,
|
||||
mtd_type: ffi::GstAnalyticsMtdType,
|
||||
an_meta_id: u32,
|
||||
rel_type: ffi::GstAnalyticsRelTypes,
|
||||
phantom: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: AnalyticsMtd> $name<'a, T> {
|
||||
fn new(meta: $metaref) -> Self {
|
||||
skip_assert_initialized!();
|
||||
$name {
|
||||
meta,
|
||||
state: std::ptr::null_mut(),
|
||||
mtd_type: T::mtd_type(),
|
||||
an_meta_id: std::u32::MAX,
|
||||
rel_type: RelTypes::ANY.into_glib(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
fn new_direct_related(
|
||||
meta: $metaref,
|
||||
an_meta_id: u32,
|
||||
rel_type: ffi::GstAnalyticsRelTypes,
|
||||
) -> Self {
|
||||
skip_assert_initialized!();
|
||||
$name {
|
||||
meta,
|
||||
state: std::ptr::null_mut(),
|
||||
mtd_type: T::mtd_type(),
|
||||
an_meta_id,
|
||||
rel_type,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AnalyticsMtd + 'a> Iterator for $name<'a, T> {
|
||||
type Item = $itemref;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
unsafe {
|
||||
let mut mtd = ffi::GstAnalyticsMtd::unsafe_from(&**self.meta);
|
||||
let ret = {
|
||||
if self.an_meta_id == std::u32::MAX {
|
||||
ffi::gst_analytics_relation_meta_iterate(
|
||||
self.meta.as_mut_ptr(),
|
||||
&mut self.state,
|
||||
self.mtd_type,
|
||||
&mut mtd,
|
||||
)
|
||||
} else {
|
||||
ffi::gst_analytics_relation_meta_get_direct_related(
|
||||
self.meta.as_mut_ptr(),
|
||||
self.an_meta_id,
|
||||
self.rel_type,
|
||||
self.mtd_type,
|
||||
&mut self.state,
|
||||
&mut mtd,
|
||||
)
|
||||
}
|
||||
};
|
||||
if from_glib(ret) {
|
||||
// This is a known clippy limitation
|
||||
// https://github.com/rust-lang/rust-clippy/issues/1553
|
||||
#[allow(clippy::redundant_closure_call)]
|
||||
Some(Self::Item::from_meta($copy_meta(self.meta), mtd.id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_mtd_iter!(
|
||||
AnalyticsMtdIter,
|
||||
&'a gst::MetaRef<'a, AnalyticsRelationMeta>,
|
||||
AnalyticsMtdRef<'a, T>,
|
||||
|meta| meta
|
||||
);
|
||||
|
||||
define_mtd_iter!(
|
||||
AnalyticsMtdIterMut,
|
||||
&'a mut gst::MetaRefMut<'a, AnalyticsRelationMeta, gst::meta::Standalone>,
|
||||
AnalyticsMtdRefMut<'a, T>,
|
||||
|meta: &mut _| &mut *(meta as *mut gst::MetaRefMut<
|
||||
'a,
|
||||
AnalyticsRelationMeta,
|
||||
gst::meta::Standalone,
|
||||
>)
|
||||
);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AnalyticsAnyMtd {}
|
||||
|
||||
unsafe impl AnalyticsMtd for AnalyticsAnyMtd {
|
||||
fn mtd_type() -> ffi::GstAnalyticsMtdType {
|
||||
ffi::GST_ANALYTICS_MTD_TYPE_ANY as ffi::GstAnalyticsMtdType
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn build_relation_meta() {
|
||||
gst::init().unwrap();
|
||||
|
||||
let mut buf = gst::Buffer::new();
|
||||
|
||||
let meta = AnalyticsRelationMeta::add(buf.make_mut());
|
||||
|
||||
assert!(meta.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_relation_meta_full() {
|
||||
gst::init().unwrap();
|
||||
|
||||
let mut buf = gst::Buffer::new();
|
||||
|
||||
let params = AnalyticsRelationMetaInitParams::new(10, 10);
|
||||
let meta = AnalyticsRelationMeta::add_full(buf.make_mut(), ¶ms);
|
||||
|
||||
assert!(meta.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn relations() {
|
||||
gst::init().unwrap();
|
||||
|
||||
let mut buf = gst::Buffer::new();
|
||||
let _ = AnalyticsRelationMeta::add(buf.make_mut());
|
||||
|
||||
let mut meta = buf.make_mut().meta_mut::<AnalyticsRelationMeta>().unwrap();
|
||||
let od = meta
|
||||
.add_od_mtd(glib::Quark::from_str("blb"), 0, 1, 10, 20, 0.8)
|
||||
.unwrap();
|
||||
let od1_id = od.id();
|
||||
|
||||
let od = meta
|
||||
.add_od_mtd(glib::Quark::from_str("blb"), 0, 1, 10, 20, 0.8)
|
||||
.unwrap();
|
||||
let od2_id = od.id();
|
||||
|
||||
let od: AnalyticsMtdRef<'_, AnalyticsODMtd> = meta
|
||||
.add_od_mtd(glib::Quark::from_str("blb"), 0, 1, 10, 20, 0.8)
|
||||
.unwrap();
|
||||
let od3_id = od.id();
|
||||
|
||||
meta.set_relation(RelTypes::IS_PART_OF, od1_id, od2_id)
|
||||
.unwrap();
|
||||
meta.set_relation(RelTypes::IS_PART_OF, od2_id, od3_id)
|
||||
.unwrap();
|
||||
|
||||
meta.set_relation(RelTypes::IS_PART_OF, 8888, 9999)
|
||||
.expect_err("Invalid id");
|
||||
|
||||
let meta = buf.meta::<AnalyticsRelationMeta>().unwrap();
|
||||
assert!(meta.relation(od1_id, od2_id) == crate::RelTypes::IS_PART_OF);
|
||||
assert!(meta.relation(od2_id, od3_id) == crate::RelTypes::IS_PART_OF);
|
||||
|
||||
assert!(meta.exist(od1_id, od2_id, 1, crate::RelTypes::IS_PART_OF));
|
||||
assert!(meta.exist(od1_id, od3_id, 2, crate::RelTypes::IS_PART_OF));
|
||||
assert!(!meta.exist(od2_id, od1_id, 1, crate::RelTypes::IS_PART_OF));
|
||||
assert!(!meta.exist(od1_id, od3_id, 1, crate::RelTypes::IS_PART_OF));
|
||||
assert!(!meta.exist(od1_id, od2_id, 1, crate::RelTypes::CONTAIN));
|
||||
|
||||
let path = meta
|
||||
.exist_path(od1_id, od3_id, 3, crate::RelTypes::ANY)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(path.as_slice().len(), 3);
|
||||
assert_eq!(path.as_slice()[0], od1_id);
|
||||
assert_eq!(path.as_slice()[1], od2_id);
|
||||
assert_eq!(path.as_slice()[2], od3_id);
|
||||
|
||||
assert_eq!(meta.len(), meta.iter::<AnalyticsAnyMtd>().count());
|
||||
assert_eq!(meta.len(), meta.iter::<AnalyticsODMtd>().count());
|
||||
for mtd in meta.iter::<AnalyticsODMtd>() {
|
||||
assert_eq!(mtd.obj_type().unwrap(), glib::Quark::from_str("blb"))
|
||||
}
|
||||
|
||||
assert_eq!(meta.len(), meta.iter::<AnalyticsAnyMtd>().count());
|
||||
for mtd in meta.iter::<AnalyticsAnyMtd>() {
|
||||
if let Ok(mtd) = mtd.downcast::<AnalyticsODMtd>() {
|
||||
assert_eq!(mtd.obj_type().unwrap(), glib::Quark::from_str("blb"))
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
meta.iter_direct_related::<AnalyticsODMtd>(od1_id, crate::RelTypes::IS_PART_OF)
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
meta.iter_direct_related::<AnalyticsODMtd>(od2_id, crate::RelTypes::IS_PART_OF)
|
||||
.count(),
|
||||
1
|
||||
);
|
||||
assert_eq!(
|
||||
meta.iter_direct_related::<AnalyticsODMtd>(od3_id, crate::RelTypes::IS_PART_OF)
|
||||
.count(),
|
||||
0
|
||||
);
|
||||
assert_eq!(
|
||||
meta.iter_direct_related::<AnalyticsODMtd>(od1_id, crate::RelTypes::CONTAIN)
|
||||
.count(),
|
||||
0
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
meta.iter_direct_related::<AnalyticsAnyMtd>(od1_id, crate::RelTypes::CONTAIN)
|
||||
.count(),
|
||||
0
|
||||
);
|
||||
for mtd in meta.iter_direct_related::<AnalyticsODMtd>(od1_id, crate::RelTypes::IS_PART_OF) {
|
||||
assert_eq!(mtd.obj_type().unwrap(), glib::Quark::from_str("blb"))
|
||||
}
|
||||
|
||||
let mut meta = buf.make_mut().meta_mut::<AnalyticsRelationMeta>().unwrap();
|
||||
assert_eq!(meta.len(), meta.iter_mut::<AnalyticsAnyMtd>().count());
|
||||
|
||||
let mut meta = buf.make_mut().meta_mut::<AnalyticsRelationMeta>().unwrap();
|
||||
let _ = meta.add_tracking_mtd(10, gst::ClockTime::from_seconds(10));
|
||||
let _ = meta.add_tracking_mtd(10, gst::ClockTime::from_seconds(10));
|
||||
|
||||
for mut item in meta.iter_mut::<AnalyticsTrackingMtd>() {
|
||||
item.set_lost().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
158
gstreamer-analytics/src/tracking.rs
Normal file
158
gstreamer-analytics/src/tracking.rs
Normal file
|
@ -0,0 +1,158 @@
|
|||
// Take a look at the license at the top of the repository in the LICENSE file.
|
||||
|
||||
use glib::translate::*;
|
||||
|
||||
use crate::relation_meta::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AnalyticsTrackingMtd {}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed {}
|
||||
impl<T: super::AnalyticsRelationMetaTrackingExt> Sealed for T {}
|
||||
}
|
||||
|
||||
pub trait AnalyticsRelationMetaTrackingExt: sealed::Sealed {
|
||||
fn add_tracking_mtd(
|
||||
&mut self,
|
||||
tracking_id: u64,
|
||||
tracking_first_seen: gst::ClockTime,
|
||||
) -> Result<AnalyticsMtdRef<AnalyticsTrackingMtd>, glib::BoolError>;
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsRelationMetaTrackingExt
|
||||
for gst::MetaRefMut<'a, AnalyticsRelationMeta, gst::meta::Standalone>
|
||||
{
|
||||
#[doc(alias = "gst_analytics_relation_meta_add_tracking_mtd")]
|
||||
fn add_tracking_mtd(
|
||||
&mut self,
|
||||
tracking_id: u64,
|
||||
tracking_first_seen: gst::ClockTime,
|
||||
) -> Result<AnalyticsMtdRef<AnalyticsTrackingMtd>, glib::BoolError> {
|
||||
unsafe {
|
||||
let mut mtd = std::mem::MaybeUninit::uninit();
|
||||
let ret = from_glib(ffi::gst_analytics_relation_meta_add_tracking_mtd(
|
||||
self.as_mut_ptr(),
|
||||
tracking_id,
|
||||
tracking_first_seen.into_glib(),
|
||||
mtd.as_mut_ptr(),
|
||||
));
|
||||
let id = mtd.assume_init().id;
|
||||
|
||||
if ret {
|
||||
Ok(AnalyticsMtdRef::from_meta(self.as_ref(), id))
|
||||
} else {
|
||||
Err(glib::bool_error!("Couldn't add more data"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl AnalyticsMtd for AnalyticsTrackingMtd {
|
||||
#[doc(alias = "gst_analytics_tracking_mtd_get_mtd_type")]
|
||||
fn mtd_type() -> ffi::GstAnalyticsMtdType {
|
||||
unsafe { ffi::gst_analytics_tracking_mtd_get_mtd_type() }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn from(t: ffi::GstAnalyticsMtd) -> ffi::GstAnalyticsTrackingMtd {
|
||||
std::mem::transmute(t)
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsMtdRef<'a, AnalyticsTrackingMtd> {
|
||||
#[doc(alias = "gst_analytics_tracking_mtd_get_info")]
|
||||
pub fn info(&self) -> (u64, gst::ClockTime, gst::ClockTime, bool) {
|
||||
let mut tracking_id: u64 = 0;
|
||||
let mut tracking_first_seen: u64 = 0;
|
||||
let mut tracking_last_seen: u64 = 0;
|
||||
let mut tracking_lost: i32 = 0;
|
||||
|
||||
unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
ffi::gst_analytics_tracking_mtd_get_info(
|
||||
&mut mtd,
|
||||
&mut tracking_id,
|
||||
&mut tracking_first_seen,
|
||||
&mut tracking_last_seen,
|
||||
&mut tracking_lost,
|
||||
);
|
||||
};
|
||||
|
||||
(
|
||||
tracking_id,
|
||||
gst::ClockTime::from_nseconds(tracking_first_seen),
|
||||
gst::ClockTime::from_nseconds(tracking_last_seen),
|
||||
tracking_lost != 0,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AnalyticsMtdRefMut<'a, AnalyticsTrackingMtd> {
|
||||
#[doc(alias = "gst_analytics_tracking_mtd_update_last_seen")]
|
||||
pub fn update_last_seen(&mut self, last_seen: gst::ClockTime) -> Result<(), glib::BoolError> {
|
||||
let ret: bool = unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
from_glib(ffi::gst_analytics_tracking_mtd_update_last_seen(
|
||||
&mut mtd,
|
||||
last_seen.into_glib(),
|
||||
))
|
||||
};
|
||||
assert!(ret);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[doc(alias = "gst_analytics_tracking_mtd_set_lost")]
|
||||
pub fn set_lost(&mut self) -> Result<(), glib::BoolError> {
|
||||
let ret: bool = unsafe {
|
||||
let mut mtd = from(ffi::GstAnalyticsMtd::unsafe_from(self));
|
||||
from_glib(ffi::gst_analytics_tracking_mtd_set_lost(&mut mtd))
|
||||
};
|
||||
assert!(ret);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn tracking() {
|
||||
gst::init().unwrap();
|
||||
|
||||
assert_eq!(AnalyticsTrackingMtd::type_name(), "object-tracking");
|
||||
|
||||
let mut buf = gst::Buffer::new();
|
||||
let mut meta = AnalyticsRelationMeta::add(buf.make_mut());
|
||||
|
||||
assert!(meta.is_empty());
|
||||
|
||||
let track = meta
|
||||
.add_tracking_mtd(10, gst::ClockTime::from_seconds(10))
|
||||
.unwrap();
|
||||
|
||||
let (tracking_id, tracking_first_seen, tracking_last_seen, tracking_lost) = track.info();
|
||||
|
||||
assert_eq!(tracking_id, 10);
|
||||
assert_eq!(tracking_first_seen, gst::ClockTime::from_seconds(10));
|
||||
assert_eq!(tracking_last_seen, gst::ClockTime::from_seconds(10));
|
||||
assert!(!tracking_lost);
|
||||
|
||||
let track_id = track.id();
|
||||
|
||||
let mut tracking_mut = meta.mtd_mut::<AnalyticsTrackingMtd>(track_id).unwrap();
|
||||
|
||||
tracking_mut
|
||||
.update_last_seen(gst::ClockTime::from_seconds(20))
|
||||
.unwrap();
|
||||
tracking_mut.set_lost().unwrap();
|
||||
|
||||
let tracking: AnalyticsMtdRef<_> = tracking_mut.into();
|
||||
let (tracking_id, tracking_first_seen, tracking_last_seen, tracking_lost) = tracking.info();
|
||||
|
||||
assert_eq!(tracking_id, 10);
|
||||
assert_eq!(tracking_first_seen, gst::ClockTime::from_seconds(10));
|
||||
assert_eq!(tracking_last_seen, gst::ClockTime::from_seconds(20));
|
||||
assert!(tracking_lost);
|
||||
}
|
||||
}
|
1
gstreamer-analytics/sys/CHANGELOG.md
Symbolic link
1
gstreamer-analytics/sys/CHANGELOG.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../gstreamer/CHANGELOG.md
|
1
gstreamer-analytics/sys/COPYRIGHT
Symbolic link
1
gstreamer-analytics/sys/COPYRIGHT
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../COPYRIGHT
|
61
gstreamer-analytics/sys/Cargo.toml
Normal file
61
gstreamer-analytics/sys/Cargo.toml
Normal file
|
@ -0,0 +1,61 @@
|
|||
[build-dependencies]
|
||||
system-deps = "6"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
|
||||
[dependencies.glib-sys]
|
||||
workspace = true
|
||||
|
||||
[dependencies.gstreamer-sys]
|
||||
workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
shell-words = "1.0.0"
|
||||
tempfile = "3"
|
||||
|
||||
[lib]
|
||||
name = "gstreamer_analytics_sys"
|
||||
|
||||
[package]
|
||||
authors = ["Olivier Crête <olivier.crete@collabora.com"]
|
||||
build = "build.rs"
|
||||
description = "FFI bindings to libgstanalytics-1.0"
|
||||
documentation = "https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_analytics_sys/"
|
||||
keywords = ["ffi", "gstreamer", "gnome", "multimedia"]
|
||||
license = "MIT"
|
||||
name = "gstreamer-analytics-sys"
|
||||
readme = "README.md"
|
||||
|
||||
[package.version]
|
||||
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]
|
||||
all-features = true
|
||||
rustc-args = ["--cfg", "docsrs"]
|
||||
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
|
||||
|
||||
[package.metadata.system-deps.gstreamer_analytics_1_0]
|
||||
name = "gstreamer-analytics-1.0"
|
||||
version = "1.24"
|
||||
|
||||
[package.metadata.system-deps.gstreamer_analytics_1_0.v1_26]
|
||||
version = "1.25"
|
||||
|
||||
[features]
|
||||
v1_26 = []
|
48
gstreamer-analytics/sys/Gir.toml
Normal file
48
gstreamer-analytics/sys/Gir.toml
Normal file
|
@ -0,0 +1,48 @@
|
|||
[options]
|
||||
girs_directories = ["../../gir-files", "../../gst-gir-files"]
|
||||
library = "GstAnalytics"
|
||||
version = "1.0"
|
||||
min_cfg_version = "1.24"
|
||||
work_mode = "sys"
|
||||
single_version_file = true
|
||||
|
||||
external_libraries = [
|
||||
"GLib",
|
||||
]
|
||||
|
||||
manual = [
|
||||
"GObject.Object",
|
||||
"Gst.Element",
|
||||
"Gst.MiniObject",
|
||||
"Gst.Object",
|
||||
]
|
||||
|
||||
[external_libraries]
|
||||
gstreamer="Gst"
|
||||
|
||||
[[object]]
|
||||
name = "GstAnalytics.RelationMeta"
|
||||
status = "generate"
|
||||
[[object.function]]
|
||||
name = "relation_meta_api_get_type"
|
||||
version = "1.24"
|
||||
|
||||
[[object]]
|
||||
name = "GstAnalytics.ClsMtd"
|
||||
status = "generate"
|
||||
boxed_inline = true
|
||||
|
||||
[[object]]
|
||||
name = "GstAnalytics.TrackingMtd"
|
||||
status = "generate"
|
||||
boxed_inline = true
|
||||
|
||||
[[object]]
|
||||
name = "GstAnalytics.ODMtd"
|
||||
status = "generate"
|
||||
boxed_inline = true
|
||||
|
||||
[[object]]
|
||||
name = "GstAnalytics.Mtd"
|
||||
status = "generate"
|
||||
boxed_inline = true
|
1
gstreamer-analytics/sys/LICENSE
Symbolic link
1
gstreamer-analytics/sys/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-MIT
|
31
gstreamer-analytics/sys/README.md
Normal file
31
gstreamer-analytics/sys/README.md
Normal file
|
@ -0,0 +1,31 @@
|
|||
# gstreamer-sys [![crates.io](https://img.shields.io/crates/v/gstreamer-app-sys.svg)](https://crates.io/crates/gstreamer-app-sys) [![pipeline status](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/badges/main/pipeline.svg)](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/commits/main)
|
||||
|
||||
[GStreamer](https://gstreamer.freedesktop.org/) (App library) FFI bindings for Rust.
|
||||
|
||||
These bindings are providing unsafe FFI API that can be used to interface with
|
||||
GStreamer. Generally they are meant to be used as the building block for
|
||||
higher-level abstractions like:
|
||||
|
||||
* Bindings for GStreamer applications and plugins: https://gitlab.freedesktop.org/gstreamer/gstreamer-rs
|
||||
* Various GStreamer plugins written in Rust: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs
|
||||
|
||||
The bindings are autogenerated with [gir](https://github.com/gtk-rs/gir/)
|
||||
based on the [GObject-Introspection](https://wiki.gnome.org/Projects/GObjectIntrospection/)
|
||||
API metadata provided by the GStreamer project.
|
||||
|
||||
## LICENSE
|
||||
|
||||
gstreamer-sys and all crates contained here are licensed under the MIT
|
||||
license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT).
|
||||
|
||||
GStreamer itself is licensed under the Lesser General Public License version
|
||||
2.1 or (at your option) any later version:
|
||||
https://www.gnu.org/licenses/lgpl-2.1.html
|
||||
|
||||
## Contribution
|
||||
|
||||
Any kinds of contributions are welcome as a pull request.
|
||||
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
for inclusion in gstreamer-rs by you shall be licensed under the MIT license as above,
|
||||
without any additional terms or conditions.
|
18
gstreamer-analytics/sys/build.rs
Normal file
18
gstreamer-analytics/sys/build.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
// 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
|
||||
|
||||
#[cfg(not(docsrs))]
|
||||
use std::process;
|
||||
|
||||
#[cfg(docsrs)]
|
||||
fn main() {} // prevent linking libraries to avoid documentation failure
|
||||
|
||||
#[cfg(not(docsrs))]
|
||||
fn main() {
|
||||
if let Err(s) = system_deps::Config::new().probe() {
|
||||
println!("cargo:warning={s}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue