Compare commits

..

82 commits
main ... 0.20

Author SHA1 Message Date
Sebastian Dröge
37edd497cc Update CHANGELOG.md for 0.20.7 2023-07-05 12:21:59 +03:00
Sebastian Dröge
9e8fee59c4 Update Cargo.lock 2023-07-05 12:18:03 +03:00
Sebastian Dröge
7de3cef890 Update versions to 0.20.7 2023-07-05 12:17:47 +03:00
Sebastian Dröge
2c65a5a86a Update dependencies
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:36:44 +03:00
Sebastian Dröge
0f2c850785 gstreamer: Move various MetaAPI methods to an extension trait
These don't make sense to implement any different than the default.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:36:44 +03:00
Sebastian Dröge
d77f983b02 basetransform: Don't leak any output buffer if prepare_output_buffer fails
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:36:44 +03:00
Sebastian Dröge
38420c7aab basetransform: Fix memory leak when dropping buffers from the transform function
Also add a basic test for a basetransform subclass.

Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/issues/472

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:36:44 +03:00
Li Yuanheng
cbe627fe8b appsink: property should use hyphen not underscore
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:32:45 +03:00
Thibault Saunier
b9a9464301 ges: Mark asset APIs as Send+sync
Those objects are MT. safe

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:32:38 +03:00
Thibault Saunier
559720693d ges: Allow subclassing GESFormatter
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:32:00 +03:00
Sebastian Dröge
9b2a0f55c7 examples: Reduce dependencies of the thumbnail example
Instead of depending on libraries for every possible image format,
depend only on the JPEG and PNG libraries.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:31:23 +03:00
Sebastian Dröge
d05ebfaf45 Update to cocoa 0.25
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:31:17 +03:00
Sebastian Dröge
5cf82ac910 Update to itertools 0.11
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:31:08 +03:00
Guillaume Desmottes
5b0f29003f pbutils: implement debug() method on DiscovererInfo related structs
The default Debug implementation is not very useful but unfortunately
cannot be overridden.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:30:33 +03:00
Sebastian Dröge
3c921f9320 examples: Update to memmap2 0.7
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1290>
2023-07-05 11:30:16 +03:00
Sebastian Dröge
f17ef98d4a Update Cargo.lock 2023-06-06 17:15:41 +03:00
Sebastian Dröge
d703cbcea6 Update CHANGELOG.md for 0.20.6 2023-06-06 17:13:05 +03:00
Sebastian Dröge
3eacb10d43 Update versions to 0.20.6 2023-06-06 17:10:21 +03:00
Sebastian Dröge
1792f72a14 Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 14:33:44 +03:00
Sebastian Dröge
af77bd0b6a pbutils: Move DiscovererStreamInfo iterators to an extension trait
This way it can also be called directly on subclasses.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 14:33:44 +03:00
Sebastian Dröge
24a00b9929 pbutils: Manually implement DiscovererStreamInfo::stream_id()
It can return `NULL` in some cases. The next release will use an
`Option` but to keep backwards compatibility here, `NULL` is mapped to
the empty string for now.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 14:33:44 +03:00
Daniel Pendse
545781d241 rtsp-server: Add RTSPContext uri getter
Add uri getter from RTSPContext

Fix #469

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 14:03:42 +03:00
Sebastian Dröge
011d3535bc gstreamer: Only retrieve the debug category once per log call
Each retrieval would go through the one-time-initialization check, i.e.
yet another branch, so let's avoid that.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 13:56:25 +03:00
Sebastian Dröge
0d45fa3f07 gstreamer: Remove unnecessary clone() in debug logging macros
The macro called from them is already doing the `clone()` itself.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 13:56:19 +03:00
Sebastian Dröge
a7517cd27b gstreamer: Use temporary GStr for the debug category constructors
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 13:56:10 +03:00
Sebastian Dröge
670d8ceec6 gstreamer: Mark DebugCategory as repr(transparent)
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 13:56:02 +03:00
Sebastian Dröge
b0d6cab254 examples: Update to memmap2
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1274>
2023-06-06 13:55:50 +03:00
Sebastian Dröge
371713a506 Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1261>
2023-05-10 11:49:35 +03:00
Sebastian Dröge
a01a0f2e37 gstreamer: Get function name for logging outside the internal closure
Otherwise the function name will include the name of the closure.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1261>
2023-05-10 11:49:16 +03:00
Sebastian Dröge
50ec192285 Update Cargo.lock 2023-04-22 11:52:30 +03:00
Sebastian Dröge
022a340742 Update versions to 0.20.5 2023-04-22 11:49:53 +03:00
Sebastian Dröge
42ec126d56 Update CHANGELOG.md for 0.20.5 2023-04-22 11:49:23 +03:00
Guillaume Desmottes
ff488987fa gstreamer: fix unused import in test
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1252>
2023-04-21 12:19:48 +03:00
Sebastian Dröge
97d55cdacb Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1252>
2023-04-21 12:08:48 +03:00
Sebastian Dröge
b611f9702c Fix a couple of new Rust 1.69 clippy warnings
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1252>
2023-04-21 12:07:26 +03:00
Thibault Saunier
8d478c3fc9 miniobject: Implement the HasParamSpec trait in the macro
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1252>
2023-04-21 12:07:21 +03:00
Bilal Elmoussaoui
4a60c71c84 gst-player: Implement Default for Player
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1252>
2023-04-21 12:07:12 +03:00
Sebastian Dröge
3481f3b6c0 Update Cargo.lock 2023-04-07 13:06:40 +03:00
Sebastian Dröge
6fb1714114 Update versions to 0.20.4 2023-04-07 13:03:23 +03:00
Sebastian Dröge
78ded9ad88 Update CHANGELOG.md for 0.20.4 2023-04-07 13:02:41 +03:00
Sebastian Dröge
b72533d926 Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 12:02:13 +03:00
Sebastian Dröge
e9644fb733 examples: Update to windows 0.48
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 12:02:13 +03:00
Sebastian Dröge
7d9b7fdaf7 examples: Update to windows 0.47
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 12:02:13 +03:00
Sebastian Dröge
43f29e361e examples: Update to windows 0.46
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 12:02:13 +03:00
Sebastian Dröge
a929123d4d deny: Update for older versions of the windows bindings
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 12:02:01 +03:00
Sebastian Dröge
1d9d4cc346 deny: Update to allow multiple versions of syn for now
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 12:02:01 +03:00
Sebastian Dröge
64bc1f7625 webrtc: Work around WebRTCICE::add_candidate() API breakage in 1.24
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 11:55:10 +03:00
Sebastian Dröge
87a70b16ba audio: Don't store a copy of the AudioInfo in AudioFrame
Instead just reference the one inside the FFI struct directly by making
sure that the memory representation of the FFI and Rust type are the
same.

This reduces the size of `AudioFrame` by about half.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 11:52:08 +03:00
Sebastian Dröge
5c88d95553 video: Don't store a copy of the VideoInfo in VideoFrame
Instead just reference the one inside the FFI struct directly by making
sure that the memory representation of the FFI and Rust type are the
same.

This reduces the size of `VideoFrame` by about half.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1245>
2023-04-07 11:52:08 +03:00
Sebastian Dröge
a9dd58eca0 Update Cargo.lock 2023-03-14 13:16:14 +02:00
Sebastian Dröge
55ad90cc4d Update versions to 0.20.3 2023-03-14 13:15:57 +02:00
Sebastian Dröge
deb49017ec Update CHANGELOG.md for 0.20.3 2023-03-14 13:15:43 +02:00
Guillaume Desmottes
9102546d63 utils: streamproducer: document forward_eos default value
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 10:06:10 +01:00
Sebastian Dröge
f431630426 deny: Allow older windows-sys 0.42 for now
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 10:06:06 +01:00
Sebastian Dröge
6ef6f49e40 video: Fix two new clippy warnings
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 10:06:06 +01:00
Marc Wiblishauser
18cbbfb1f8 ParamSpecArray: fix type_ from fraction to array
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 10:05:58 +01:00
Sebastian Dröge
cb6b7a2c2c rtsp-server: Work around GstRTSPClientClass ABI breakage in 1.18
Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/issues/455

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 10:05:58 +01:00
Sebastian Dröge
f0e766e7ee ci: Build 0.20 docs and drop 0.18 docs
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 09:04:43 +01:00
Jordan Petridis
92e2a23a99 ci: Update base image to debian 12 bookworm
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 09:04:43 +01:00
Sebastian Dröge
7be1db86fd ci: Update to gtk 4.10.0
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 09:04:43 +01:00
Sebastian Dröge
8ef5a045c1 ci: Update to meson 1.0.1
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 09:04:43 +01:00
Sebastian Dröge
a77a656d04 ci: Update to Rust 1.68
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 09:04:42 +01:00
Sebastian Dröge
5171267260 ci: Update to rustup 1.25.2
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 09:04:13 +01:00
Sebastian Dröge
d9ebcc5889 ci: windows: Update to dav1d 1.1.0
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1238>
2023-03-13 08:43:41 +01:00
Sebastian Dröge
a4c248cba3 Update Cargo.lock 2023-02-21 17:03:39 +02:00
Sebastian Dröge
4b9ac76020 Update versions to 0.20.2 2023-02-21 16:57:55 +02:00
Sebastian Dröge
facdd10eba Update CHANGELOG.md for 0.20.2 2023-02-21 16:57:30 +02:00
Sebastian Dröge
b78926a7f3 Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1225>
2023-02-21 16:30:10 +02:00
François Laignel
2f41cb99d3 {Audio,Video}CapsBuilder: add for_encoding constructor
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1225>
2023-02-21 16:11:15 +02:00
SeaDve
eeefa80227 gstreamer-play: impl default for Play
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1225>
2023-02-21 16:10:36 +02:00
SeaDve
9446401a46 gstreamer: implement HasParamSpec for ClockTime
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1225>
2023-02-21 16:10:29 +02:00
Sebastian Dröge
8afac7d31b Update CHANGELOG.md for 0.20.1 2023-02-13 15:02:29 +02:00
Sebastian Dröge
dabfc8d181 Update versions to 0.20.1 2023-02-13 15:02:29 +02:00
Sebastian Dröge
d1cc1b6715 video: Don't leak the gst::Buffer when converting a VideoFrame into an ffi::GstVideoFrame
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1220>
2023-02-13 14:35:38 +02:00
Sebastian Dröge
bc3e1404d6 video: Don't forget to unmap the VideoFrame when converting into a gst::Buffer
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1220>
2023-02-13 14:35:37 +02:00
Sebastian Dröge
2354cd6c4f audio: Don't forget to unmap the AudioBuffer and drop the Box when converting into a gst::Buffer
Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1220>
2023-02-13 14:35:35 +02:00
Sebastian Dröge
6cb469934d Revert "Add support for inline variable names in error/warning/info message creation macros"
This reverts commit 6bd559f5b5. It breaks
backwards compatibility as it makes it impossible to use a `&String` or
`&str` as debug message and only allows for string literals or format
strings plus arguments.
2023-02-13 11:40:55 +02:00
Sebastian Dröge
6bd559f5b5 Add support for inline variable names in error/warning/info message creation macros
Simply by removing the special-casing of string literals, which doesn't
really bring any improvements here.

Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/issues/442

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1218>
2023-02-13 11:00:09 +02:00
Sebastian Dröge
66453fdc02 ci: Don't run cargo update on the stable branch 2023-02-11 20:15:46 +02:00
Sebastian Dröge
e901266415 Add Cargo.lock 2023-02-10 00:15:55 +02:00
Sebastian Dröge
8a02757434 Add 0.20 version to all local dependencies 2023-02-09 23:53:05 +02:00
Sebastian Dröge
2db1198311 Update to 0.17 branch of gtk-rs-core and gtk3-rs 2023-02-09 23:48:32 +02:00
884 changed files with 24134 additions and 36779 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
target/
**/*.rs.bk
Cargo.lock

View file

@ -17,7 +17,7 @@
# Updating the nightly image should be done by simply running a scheduled ci
# pipeline on the upstream repo with the $UPDATE_NIGHTLY variable defined.
.templates_sha: &templates_sha b2e24205598dc1d80b5f2c88cf7618051e30e9fd
.templates_sha: &templates_sha fddab8aa63e89a8e65214f59860d9c0f030360c9
include:
- project: 'freedesktop/ci-templates'
@ -49,14 +49,15 @@ 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: "--cfg docsrs --extern-html-root-url=muldiv=https://docs.rs/muldiv/1.0.0/muldiv/ -Z unstable-options --generate-link-to-definition"
RUST_DOCS_FLAGS: "--extern-html-root-url=muldiv=https://docs.rs/muldiv/1.0.0/muldiv/ -Z unstable-options"
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.22=0.22
0.20=0.20
0.19=0.19
stages:
- "trigger"
@ -73,7 +74,6 @@ trigger:
stage: 'trigger'
variables:
GIT_STRATEGY: none
tags: [ 'placeholder-job' ]
script:
- echo "Trigger job done, now running the pipeline."
rules:
@ -127,27 +127,18 @@ trigger:
stage: container-base
variables:
FDO_DISTRIBUTION_PACKAGES: >-
build-essential curl python3-setuptools libglib2.0-dev libxml2-dev
libdrm-dev libegl1-mesa-dev libgl1-mesa-dev libgbm-dev libgles2-mesa-dev
libgl1-mesa-dri libegl-dev libgl1-mesa-glx libwayland-egl1-mesa xz-utils
libssl-dev git wget ca-certificates ninja-build python3-pip flex bison
libglib2.0-dev libx11-dev libx11-xcb-dev libsoup2.4-dev libvorbis-dev
libogg-dev libtheora-dev libmatroska-dev libvpx-dev libopus-dev
libgraphene-1.0-dev libjpeg-dev libwayland-dev wayland-protocols
python3-gi libavcodec-dev libavformat-dev libavutil-dev libavfilter-dev
libswscale-dev yasm libx264-dev libfontconfig-dev libfreetype-dev
libxkbcommon-dev libxi-dev libxcb-render0-dev libxcb-shm0-dev
libxcb1-dev libxext-dev libxrender-dev libxrandr-dev libxcursor-dev
libxdamage-dev libxfixes-dev libxinerama-dev libgudev-1.0-dev
libpango1.0-dev libcairo2-dev libjson-glib-dev libgdk-pixbuf-2.0-dev
libtiff-dev libpng-dev libjpeg-dev libepoxy-dev libsass-dev sassc
libcsound64-dev llvm clang nasm libsodium-dev libwebp-dev
libflac-dev
build-essential curl python3-setuptools liborc-0.4-dev libglib2.0-dev
libxml2-dev libgtk-3-dev libegl1-mesa libgles2-mesa libgl1-mesa-dri
libgl1-mesa-glx libwayland-egl1-mesa xz-utils libssl-dev git wget
ca-certificates ninja-build python3-pip flex bison libglib2.0-dev
libx11-dev libx11-xcb-dev libsoup2.4-dev libvorbis-dev libogg-dev
libtheora-dev libmatroska-dev libvpx-dev libopus-dev libgraphene-1.0-dev
libjpeg-dev libwayland-dev python3-gi libavcodec-dev libavformat-dev
libavutil-dev libavfilter-dev libswscale-dev yasm libx264-dev
FDO_DISTRIBUTION_EXEC: >-
bash ci/install-gst.sh &&
bash ci/install-dav1d.sh &&
pip3 install --break-system-packages git+http://gitlab.freedesktop.org/freedesktop/ci-templates &&
pip3 install --break-system-packages tomli
bash ci/install-gtk4.sh &&
pip3 install --break-system-packages git+http://gitlab.freedesktop.org/freedesktop/ci-templates
.build-final-image:
extends:
@ -207,6 +198,66 @@ update-nightly:
- .debian:12-nightly
- .dist-debian-container
# GST_PLUGINS_RS_TOKEN is a variable of type 'Var' defined in gstreamer-rs CI
# settings and containing a gst-plugins-rs pipeline trigger token
.plugins-update:
stage: deploy
script:
- |
# FDO_DISTRIBUTION_IMAGE still has indirections
- echo $FDO_DISTRIBUTION_IMAGE
- DISTRO_IMAGE=$(eval echo ${FDO_DISTRIBUTION_IMAGE})
- echo $DISTRO_IMAGE
# retrieve the infos from the registry
- JSON_IMAGE=$(skopeo inspect docker://$DISTRO_IMAGE)
- IMAGE_PIPELINE_ID=$(echo $JSON_IMAGE | jq -r '.Labels["fdo.pipeline_id"]')
- echo $IMAGE_PIPELINE_ID
- echo $CI_PIPELINE_ID
- |
if [[ x"$IMAGE_PIPELINE_ID" == x"$CI_PIPELINE_ID" ]]; then
echo "Image has been updated, notify gst-plugins-rs"
curl -X POST -F "token=$GST_PLUGINS_RS_TOKEN" -F "ref=main" -F "variables[UPDATE_IMG]=$UPDATE_IMG" https://gitlab.freedesktop.org/api/v4/projects/1400/trigger/pipeline
else
echo "Image has not been updated, ignore"
fi
rules:
- if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "gstreamer/gstreamer-rs"'
# Those jobs need to use another image as ours doesn't have 'skopeo'
# and it's not easily installable in Debian stable for now.
plugins-update-stable:
extends:
- .plugins-update
- .img-stable
image: quay.io/freedesktop.org/ci-templates:container-build-base-2021-07-29.0
needs:
- job: 'build-stable'
artifacts: false
variables:
UPDATE_IMG: "stable"
plugins-update-msrv:
extends:
- .plugins-update
- .img-msrv
image: quay.io/freedesktop.org/ci-templates:container-build-base-2021-07-29.0
needs:
- job: 'build-msrv'
artifacts: false
variables:
UPDATE_IMG: "msrv"
plugins-update-nightly:
extends:
- .plugins-update
- .img-nightly
image: quay.io/freedesktop.org/ci-templates:container-build-base-2021-07-29.0
needs:
- job: 'build-nightly'
artifacts: false
variables:
UPDATE_IMG: "nightly"
.cargo_test_var: &cargo_test
- ./ci/run-cargo-test.sh
@ -313,7 +364,6 @@ test nightly sys:
rustfmt:
extends: .img-stable
stage: "lint"
tags: [ 'placeholder-job' ]
script:
- cargo fmt --version
- cargo fmt -- --color=always --check
@ -324,23 +374,12 @@ rustfmt:
check commits:
extends: .img-stable
stage: "lint"
tags: [ 'placeholder-job' ]
script:
- ci-fairy check-commits --textwidth 0 --no-signed-off-by
needs:
- job: 'build-stable'
artifacts: false
typos:
extends: .img-stable
stage: "lint"
tags: [ 'placeholder-job' ]
script:
- typos
needs:
- job: 'build-stable'
artifacts: false
clippy:
extends: .img-stable
stage: 'extras'
@ -363,15 +402,13 @@ deny:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- cargo update --color=always
- cargo deny --color=always --workspace --all-features check all
- cargo deny --color=always check
gir-checks:
variables:
GIT_SUBMODULE_STRATEGY: recursive
extends: .img-stable
stage: 'extras'
tags: [ 'placeholder-job' ]
needs:
- job: 'build-stable'
artifacts: false
@ -388,7 +425,6 @@ outdated:
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
script:
- cargo update --color=always
- cargo outdated --color=always --root-deps-only --exit-code 1 -v
coverage:
@ -466,9 +502,8 @@ docs:
- PATH=~/.cargo/bin/:$PATH ./generator.py --gir-files-directories gir-files gst-gir-files --embed-docs --no-fmt
- |
RUSTDOCFLAGS="$RUST_DOCS_FLAGS"
RUSTFLAGS="--cfg docsrs"
eval $(./gir-rustdoc.py pre-docs)
cargo +nightly doc --workspace --exclude examples --exclude tutorials --all-features --color=always --no-deps
cargo +nightly doc --workspace --exclude examples --exclude tutorials --color=always --features=dox --no-deps
- mv target/doc docs
artifacts:
paths:
@ -504,7 +539,6 @@ pages:
.windows rust docker build:
stage: 'container-final'
timeout: '2h'
needs: []
variables:
# Unlike the buildah/linux jobs, this file
@ -514,6 +548,7 @@ 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'

View file

@ -1,33 +0,0 @@
### Describe your issue
<!-- a clear and concise summary of the bug. -->
<!-- For any GStreamer usage question, please contact the community using the #gstreamer channel on IRC https://www.oftc.net/ or the mailing list on https://gstreamer.freedesktop.org/lists/ -->
#### Expected Behavior
<!-- What did you expect to happen -->
#### Observed Behavior
<!-- What actually happened -->
#### Setup
- **Operating System:**
- **Device:** Computer / Tablet / Mobile / Virtual Machine <!-- Delete as appropriate !-->
- **gstreamer-rs Version:**
- **GStreamer Version:**
- **Command line:**
### Steps to reproduce the bug
<!-- please fill in exact steps which reproduce the bug on your system, for example: -->
1. open terminal
2. type `command`
### How reproducible is the bug?
<!-- The reproducibility of the bug is Always/Intermittent/Only once after doing a very specific set of steps-->
### Screenshots if relevant
### Solutions you have tried
### Related non-duplicate issues
### Additional Information
<!-- Any other information such as logs. Make use of <details> for long output -->

2289
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,7 @@
[workspace]
resolver = "2"
default-members = [
"gstreamer/sys",
"gstreamer-analytics/sys",
"gstreamer-app/sys",
"gstreamer-audio/sys",
"gstreamer-base/sys",
@ -23,7 +21,6 @@ default-members = [
"gstreamer-video/sys",
"gstreamer-webrtc/sys",
"gstreamer",
"gstreamer-analytics",
"gstreamer-app",
"gstreamer-audio",
"gstreamer-base",
@ -39,7 +36,6 @@ default-members = [
"gstreamer-rtsp",
"gstreamer-rtsp-server",
"gstreamer-sdp",
"gstreamer-tag",
"gstreamer-validate",
"gstreamer-video",
"gstreamer-webrtc",
@ -49,7 +45,6 @@ default-members = [
members = [
"gstreamer/sys",
"gstreamer-analytics/sys",
"gstreamer-app/sys",
"gstreamer-audio/sys",
"gstreamer-base/sys",
@ -74,7 +69,6 @@ members = [
"gstreamer-webrtc/sys",
"gstreamer-allocators/sys",
"gstreamer",
"gstreamer-analytics",
"gstreamer-app",
"gstreamer-audio",
"gstreamer-base",
@ -94,7 +88,6 @@ members = [
"gstreamer-rtsp",
"gstreamer-rtsp-server",
"gstreamer-sdp",
"gstreamer-tag",
"gstreamer-validate",
"gstreamer-video",
"gstreamer-webrtc",
@ -105,48 +98,3 @@ 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" }

View file

@ -1,7 +1,4 @@
variables:
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'
GST_RS_IMG_TAG: '2023-03-09.1-bookworm'
GST_RS_STABLE: '1.68.0'
GST_RS_MSRV: '1.64.0'

View file

@ -1,11 +0,0 @@
set -e
RELEASE=1.4.1
git clone https://code.videolan.org/videolan/dav1d.git --branch $RELEASE
cd dav1d
meson build -D prefix=/usr/local
ninja -C build
ninja -C build install
cd ..
rm -rf dav1d

View file

@ -2,16 +2,14 @@
set -e
DEFAULT_BRANCH="$GST_UPSTREAM_BRANCH"
pip3 install meson==1.1.1 --break-system-packages
pip3 install meson==1.0.1 --break-system-packages
# gstreamer-rs already has a 'gstreamer' directory so don't clone there
pushd .
cd ..
git clone https://gitlab.freedesktop.org/gstreamer/gstreamer.git \
--depth 1 \
--branch "$DEFAULT_BRANCH"
--branch 1.22
cd gstreamer
@ -24,7 +22,6 @@ PLUGINS="-D gst-plugins-base:ogg=enabled \
-D gst-plugins-bad:opus=enabled \
-D gst-plugins-ugly:x264=enabled"
echo "subproject('gtk')" >> meson.build
meson setup build \
-D prefix=/usr/local \
-D gpl=enabled \

18
ci/install-gtk4.sh Normal file
View file

@ -0,0 +1,18 @@
#! /bin/sh
set -eux
BRANCH=4.10.0
git clone https://gitlab.gnome.org/GNOME/gtk.git --branch $BRANCH --depth=1
cd gtk
meson setup build \
-D prefix=/usr/local \
-Dbuild-tests=false \
-Dwayland-protocols:tests=false
meson compile -C build
meson install -C build
ldconfig
cd ..
rm -rf gtk/

View file

@ -5,7 +5,7 @@ source ./ci/env.sh
set -e
export CARGO_HOME='/usr/local/cargo'
RUSTUP_VERSION=1.27.1
RUSTUP_VERSION=1.25.2
RUST_VERSION=$1
RUST_IMAGE_FULL=$2
RUST_ARCH="x86_64-unknown-linux-gnu"
@ -26,26 +26,17 @@ if [ "$RUST_IMAGE_FULL" = "1" ]; then
rustup component add clippy-preview
rustup component add rustfmt
cargo install --locked --force cargo-deny
cargo install --locked --force cargo-outdated
cargo install --locked --force typos-cli --version "1.19.0"
cargo install --force cargo-deny
cargo install --force cargo-outdated
# Coverage tools
rustup component add llvm-tools-preview
cargo install --locked --force grcov
fi
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
cargo install --force grcov
fi
if [ "$RUST_VERSION" = "nightly" ]; then
rustup component add rustfmt --toolchain nightly
# Documentation tools
cargo install --locked --force rustdoc-stripper
cargo install --force rustdoc-stripper
fi

View file

@ -5,10 +5,27 @@ set -ex
rustc --version
cargo --version
# First build and test all the crates with their relevant features
# Keep features in sync with the list below below
get_features() {
crate=$1
case "$crate" in
gstreamer-audio|gstreamer-editing-services|gstreamer-gl|gstreamer-pbutils|gstreamer-rtp|gstreamer-rtsp|gstreamer-video|gstreamer)
echo "--features=serde,v1_22"
;;
gstreamer-validate)
echo ""
;;
*)
echo "--features=v1_22"
;;
esac
}
for crate in gstreamer* gstreamer-gl/{egl,wayland,x11}; do
if [ -e "$crate/Cargo.toml" ]; then
if [ -n "$ALL_FEATURES" ]; then
FEATURES="--all-features"
FEATURES=$(get_features "$crate")
else
FEATURES=""
fi
@ -23,7 +40,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-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18"
EXAMPLES_FEATURES="--features=gtksink,gtkvideooverlay,gtkvideooverlay-x11,gtkvideooverlay-quartz,rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-wayland,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18"
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

View file

@ -11,10 +11,13 @@ 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_26"
echo "--features=serde,v1_22"
;;
gstreamer-validate)
echo ""
;;
*)
echo "--features=v1_26"
echo "--features=v1_22"
;;
esac
}
@ -31,7 +34,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-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18"
EXAMPLES_FEATURES="--features=gtksink,gtkvideooverlay,gtkvideooverlay-x11,gtkvideooverlay-quartz,rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gl,gst-gl-x11,gst-gl-wayland,gst-gl-egl,allocators,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18"
# And also run over all the examples/tutorials
cargo clippy --locked --color=always --manifest-path examples/Cargo.toml --all-targets "$EXAMPLES_FEATURES" -- $CLIPPY_LINTS

View file

@ -5,22 +5,35 @@ set -ex
rustc --version
cargo --version
get_features() {
module=${1%%/sys}
case "$module" in
gstreamer-validate)
echo ""
;;
*)
echo "--features=v1_22"
;;
esac
}
# First build and test all the crates with their relevant features
# Keep features in sync with below
for crate in gstreamer*/sys gstreamer-gl/*/sys; do
if [ -e "$crate/Cargo.toml" ]; then
echo "Building $crate with --all-features"
cargo build --locked --color=always --manifest-path "$crate/Cargo.toml" --all-features
echo "Building $crate with $(get_features "$crate")"
cargo build --locked --color=always --manifest-path "$crate/Cargo.toml" $(get_features "$crate")
fi
done
# Run tests for crates we can currently run.
# Other tests are broken currently.
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 \
@ -28,16 +41,13 @@ 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)"
cargo test --locked --color=always --manifest-path $crate/Cargo.toml --all-features
echo "Testing $crate with $(get_features $crate)"
cargo test --locked --color=always --manifest-path $crate/Cargo.toml "$(get_features $crate)"
done

View file

@ -16,7 +16,8 @@
# 'gstreamer-gl/egl',
# 'gstreamer-gl/wayland',
# 'gstreamer-gl/x11',
'gstreamer-mpegts',
# only has sys
# 'gstreamer-mpegts',
'gstreamer-mpegts/sys',
'gstreamer-net',
'gstreamer-pbutils',
@ -25,7 +26,8 @@
'gstreamer-rtsp',
'gstreamer-rtsp-server',
'gstreamer-sdp',
'gstreamer-tag',
# only has sys
# 'gstreamer-tag',
'gstreamer-tag/sys',
'gstreamer-video',
'gstreamer-webrtc',
@ -36,10 +38,10 @@
# "" is the default build, no flags appended
[string[]] $features_matrix = @(
# "--no-default-features",
# "--features=v1_18",
# "--features=v1_20",
# "--features=v1_18,",
# "--features=v1_20,",
"",
"--all-features"
"--features=v1_22,"
)
foreach($features in $features_matrix) {
@ -52,6 +54,10 @@ foreach($features in $features_matrix) {
# Don't append feature flags if the string is null/empty
# Or when we want to build without default features
if ($env:LocalFeatures -and ($env:LocalFeatures -ne '--no-default-features')) {
if ($crate -eq 'gstreamer') {
$env:LocalFeatures += "serde,"
}
if ($crate -eq 'examples') {
# FIXME: We can do --all-features for examples once we have gtk3 installed in the image
$env:LocalFeatures = "--features=rtsp-server,rtsp-server-record,pango-cairo,overlay-composition,gst-play,gst-player,ges,image,cairo-rs,gst-video/v1_18,windows"

View file

@ -1,22 +1,25 @@
# escape=`
FROM "registry.freedesktop.org/gstreamer/gstreamer/amd64/windows:2023-07-17.0-main"
FROM "registry.freedesktop.org/gstreamer/gstreamer/amd64/windows:2022-12-10.0-main"
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
# Make sure any failure in PowerShell is fatal
ENV ErrorActionPreference='Stop'
SHELL ["powershell","-NoLogo", "-NonInteractive", "-Command"]
ARG DEFAULT_BRANCH="1.24"
ARG DEFAULT_BRANCH="1.22"
ARG RUST_VERSION="invalid"
RUN choco install -y pkgconfiglite nasm llvm openssl
RUN choco install -y pkgconfiglite nasm llvm
# https://stackoverflow.com/a/50716450
RUN setx PATH '%PATH%;C:\Program Files\NASM;C:\gst-install\bin'
ENV PKG_CONFIG_PATH="C:\gst-install\lib\pkgconfig"
COPY install_gst.ps1 install_dav1d.ps1 C:\
COPY install_pango.ps1 install_gst.ps1 install_gtk.ps1 install_dav1d.ps1 C:\
RUN C:\install_pango.ps1
RUN C:\install_gst.ps1
RUN C:\install_gtk.ps1
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 --locked cargo-c --version 0.9.22+cargo-0.72

View file

@ -1,7 +1,7 @@
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
# Download gstreamer and all its subprojects
git clone -b 1.4.1 --depth 1 https://code.videolan.org/videolan/dav1d.git C:\dav1d
git clone -b 1.1.0 --depth 1 https://code.videolan.org/videolan/dav1d.git C:\dav1d
if (!$?) {
Write-Host "Failed to clone dav1d"
Exit 1

View file

@ -12,60 +12,52 @@ Set-Location C:\gstreamer
# Copy the cache we already have in the image to avoid massive redownloads
Move-Item C:/subprojects/* C:\gstreamer\subprojects
if (!$?) {
Write-Host "Failed to copy subprojects cache"
Exit 1
}
# Update the subprojects cache
Write-Output "Running meson subproject reset"
meson subprojects update --reset
if (!$?) {
Write-Host "Failed to update gstreamer subprojects"
Write-Host "Failed to reset subprojects state"
Exit 1
}
$MESON_ARGS = @(`
"--prefix=C:\gst-install", `
"-Dglib:installed_tests=false", `
"-Dlibnice:tests=disabled", `
"-Dlibnice:examples=disabled", `
"-Dffmpeg:tests=disabled", `
"-Dopenh264:tests=disabled", `
"-Dpygobject:tests=false", `
"-Dgpl=enabled", `
"-Dugly=enabled", `
"-Dbad=enabled", `
"-Dges=enabled", `
"-Drtsp_server=enabled", `
"-Ddevtools=enabled", `
"-Dsharp=disabled", `
"-Dpython=disabled", `
"-Dlibav=disabled", `
"-Dvaapi=disabled", `
"-Dgst-plugins-base:pango=enabled", `
"-Dgst-plugins-good:cairo=enabled", `
"-Dgst-plugins-good:lame=disabled"
)
$env:MESON_ARGS = "--prefix=C:\gst-install\ " +
"-Dglib:installed_tests=false " +
"-Dlibnice:tests=disabled " +
"-Dlibnice:examples=disabled " +
"-Dffmpeg:tests=disabled " +
"-Dopenh264:tests=disabled " +
"-Dpygobject:tests=false " +
"-Dgpl=enabled " +
"-Dugly=enabled " +
"-Dbad=enabled " +
"-Dges=enabled " +
"-Drtsp_server=enabled " +
"-Ddevtools=enabled " +
"-Dsharp=disabled " +
"-Dpython=disabled " +
"-Dlibav=disabled " +
"-Dvaapi=disabled " +
"-Dgst-plugins-base:pango=enabled " +
"-Dgst-plugins-good:cairo=enabled " +
"-Dgst-plugins-good:lame=disabled "
$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'
echo "subproject('gtk')" >> meson.build
Write-Output "Building gst"
cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=amd64 && meson setup _build $env:MESON_ARGS && meson compile -C _build && meson install -C _build"
Write-Output "Building gstreamer"
meson setup --vsenv $MESON_ARGS _build
if (!$?) {
type "_build\meson-logs\meson-log.txt"
Write-Host "Failed to run meson setup, see log above"
Write-Host "Failed to build and install gst"
Exit 1
}
Write-Output "Compiling gstreamer"
meson compile -C _build
cd C:\
cmd /c rmdir /s /q C:\gstreamer
if (!$?) {
Write-Host "Failed to run meson compile"
Write-Host "Failed to remove gst checkout"
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

View file

@ -0,0 +1,27 @@
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
$env:MESON_ARGS = "--prefix=C:\gst-install\"
# Download gtk and all its subprojects
git clone -b 4.10.0 --depth 1 https://gitlab.gnome.org/gnome/gtk.git C:\gtk
if (!$?) {
Write-Host "Failed to clone gtk"
Exit 1
}
Set-Location C:\gtk
Write-Output "Building gtk"
cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=amd64 && meson _build $env:MESON_ARGS && meson compile -C _build && ninja -C _build install"
if (!$?) {
Write-Host "Failed to build and install gtk"
Exit 1
}
cd C:\
cmd /c rmdir /s /q C:\gtk
if (!$?) {
Write-Host "Failed to remove gtk checkout"
Exit 1
}

View file

@ -0,0 +1,27 @@
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
$env:MESON_ARGS = "--prefix=C:\gst-install\ -Dharfbuzz:freetype=enabled -Dfreetype:harfbuzz=disabled"
# Download pango all its subprojects
git clone -b main --depth 1 https://gitlab.gnome.org/gnome/pango.git C:\pango
if (!$?) {
Write-Host "Failed to clone pango"
Exit 1
}
Set-Location C:\pango
Write-Output "Building pango"
cmd.exe /C "C:\BuildTools\Common7\Tools\VsDevCmd.bat -host_arch=amd64 -arch=amd64 && meson _build $env:MESON_ARGS && meson compile -C _build && ninja -C _build install"
if (!$?) {
Write-Host "Failed to build and install pango"
Exit 1
}
cd C:\
cmd /c rmdir /s /q C:\pango
if (!$?) {
Write-Host "Failed to remove gtk checkout"
Exit 1
}

View file

@ -1,8 +1,3 @@
exclude = [
"examples",
"tutorials",
]
[advisories]
db-path = "~/.cargo/advisory-db"
db-urls = ["https://github.com/rustsec/advisory-db"]
@ -13,7 +8,16 @@ ignore = []
[licenses]
unlicensed = "deny"
default = "deny"
allow = [
"Apache-2.0",
]
deny = [
"GPL-1.0",
"GPL-2.0",
"GPL-3.0",
"AGPL-1.0",
"AGPL-3.0",
]
copyleft = "deny"
allow-osi-fsf-free = "either"
confidence-threshold = 0.8
@ -23,11 +27,34 @@ multiple-versions = "deny"
wildcards = "allow"
highlight = "all"
# proc-macro-crate depends on an older version of toml_edit
# https://github.com/bkchr/proc-macro-crate/pull/50
# Various crates depend on an older version of windows-sys
[[bans.skip]]
name = "toml_edit"
version = "0.21"
name = "windows-sys"
version = "0.45"
[[bans.skip]]
name = "windows_x86_64_msvc"
version = "0.42"
[[bans.skip]]
name = "windows_x86_64_gnullvm"
version = "0.42"
[[bans.skip]]
name = "windows_x86_64_gnu"
version = "0.42"
[[bans.skip]]
name = "windows_i686_msvc"
version = "0.42"
[[bans.skip]]
name = "windows_i686_gnu"
version = "0.42"
[[bans.skip]]
name = "windows_aarch64_msvc"
version = "0.42"
[[bans.skip]]
name = "windows_aarch64_gnullvm"
version = "0.42"
[[bans.skip]]
name = "windows-targets"
version = "0.42"
[sources]
unknown-registry = "deny"
@ -35,3 +62,8 @@ 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"

View file

@ -1,72 +1,74 @@
[package]
name = "examples"
version.workspace = true
version = "0.20.7"
license = "MIT"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
edition.workspace = true
rust-version.workspace = true
edition = "2021"
rust-version = "1.64"
[dependencies]
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 }
glib = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.17", version = "0.17" }
gst = { package = "gstreamer", path = "../gstreamer", version = "0.20" }
gst-gl = { package = "gstreamer-gl", path = "../gstreamer-gl", version = "0.20", optional = true }
gst-gl-egl = { package = "gstreamer-gl-egl", path = "../gstreamer-gl/egl", version = "0.20", optional = true }
gst-gl-wayland = { package = "gstreamer-gl-wayland", path = "../gstreamer-gl/wayland", version = "0.20", optional = true }
gst-gl-x11 = { package = "gstreamer-gl-x11", path = "../gstreamer-gl/x11", optional = true }
gst-app = { package = "gstreamer-app", path = "../gstreamer-app", version = "0.20" }
gst-audio = { package = "gstreamer-audio", path = "../gstreamer-audio", version = "0.20" }
gst-base = { package = "gstreamer-base", path = "../gstreamer-base", version = "0.20" }
gst-video = { package = "gstreamer-video", path = "../gstreamer-video", version = "0.20" }
gst-pbutils = { package = "gstreamer-pbutils", path = "../gstreamer-pbutils", version = "0.20" }
gst-play = { package = "gstreamer-play", path = "../gstreamer-play", version = "0.20", optional = true }
gst-player = { package = "gstreamer-player", path = "../gstreamer-player", version = "0.20", optional = true }
ges = { package = "gstreamer-editing-services", path = "../gstreamer-editing-services", version = "0.20", optional = true }
gst-sdp = { package = "gstreamer-sdp", path = "../gstreamer-sdp", version = "0.20", optional = true }
gst-rtsp = { package = "gstreamer-rtsp", path = "../gstreamer-rtsp", version = "0.20", optional = true }
gst-rtsp-server = { package = "gstreamer-rtsp-server", path = "../gstreamer-rtsp-server", version = "0.20", optional = true }
gst-allocators = { package = "gstreamer-allocators", path = "../gstreamer-allocators", version = "0.20", optional = true }
gtk = { git = "https://github.com/gtk-rs/gtk3-rs", branch = "0.17", version = "0.17.0", optional = true }
gdk = { git = "https://github.com/gtk-rs/gtk3-rs", branch = "0.17", version = "0.17.0", optional = true }
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.17", version = "0.17.0", 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"
glutin = { version = "0.31", optional = true, default-features = false }
glutin-winit = { version = "0.4", optional = true, default-features = false }
byte-slice-cast = "1"
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.17", version = "0.17.0", features=["use_glib"], optional = true }
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.17", version = "0.17.0", optional = true }
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.17", version = "0.17.0", optional = true }
glutin = { version = "0.29", optional = true }
once_cell = "1.0"
image = { version = "0.24", optional = true, default-features = false, features = ["png", "jpeg"] }
memmap2 = { version = "0.7", optional = true }
memfd = { version = "0.6", 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"
uds = { version = "0.2", optional = true }
[target.'cfg(windows)'.dependencies]
windows = { version = "0.56", features=["Win32_Graphics_Direct3D11",
windows = { version = "0.48", 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",
"Win32_Graphics_Imaging", "Win32_System_Com", "Foundation_Numerics"], optional = true }
"Foundation_Numerics"], optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.25"
objc = "0.2.7"
[build-dependencies]
gl_generator = { version = "0.14", optional = true }
[features]
default = []
gtksink = ["gtk", "gio"]
gtkvideooverlay = ["gtk", "gdk", "gio"]
gtkvideooverlay-x11 = ["gtkvideooverlay"]
gtkvideooverlay-quartz = ["gtkvideooverlay"]
rtsp-server = ["gst-rtsp-server", "gst-rtsp", "gst-sdp"]
rtsp-server-record = ["gst-rtsp-server", "gst-rtsp", "gio"]
pango-cairo = ["pango", "pangocairo", "cairo-rs"]
overlay-composition = ["pango", "pangocairo", "cairo-rs"]
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
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"]
allocators = ["gst-allocators", "memmap2", "memfd", "uds"]
[[bin]]
@ -93,6 +95,14 @@ name = "encodebin"
[[bin]]
name = "events"
[[bin]]
name = "gtksink"
required-features = ["gtksink"]
[[bin]]
name = "gtkvideooverlay"
required-features = ["gtkvideooverlay"]
[[bin]]
name = "iterator"
@ -136,10 +146,6 @@ 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"
@ -167,10 +173,6 @@ required-features = ["pango-cairo"]
name = "overlay-composition"
required-features = ["overlay-composition"]
[[bin]]
name = "overlay-composition-d2d"
required-features = ["windows"]
[[bin]]
name = "ges"
required-features = ["ges"]
@ -204,6 +206,3 @@ required-features = ["cairo-rs", "gst-video/v1_18"]
[[bin]]
name = "d3d11videosink"
required-features = ["windows"]
[[bin]]
name = "audio_multichannel_interleave"

View file

@ -46,7 +46,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
)
.build();
pipeline.add_many([&src, appsink.upcast_ref()])?;
pipeline.add_many(&[&src, appsink.upcast_ref()])?;
src.link(&appsink)?;
// Getting data out of the appsink is done by setting callbacks on it.
@ -91,7 +91,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
element_error!(
appsink,
gst::ResourceError::Failed,
("Failed to interpret buffer as S16 PCM")
("Failed to interprete buffer as S16 PCM")
);
gst::FlowError::Error

View file

@ -13,7 +13,6 @@
use anyhow::Error;
use derive_more::{Display, Error};
use gst::prelude::*;
use gst_video::prelude::*;
#[path = "../examples-common.rs"]
mod examples_common;
@ -50,8 +49,8 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
let sink = gst::ElementFactory::make("autovideosink").build()?;
pipeline.add_many([appsrc.upcast_ref(), &videoconvert, &sink])?;
gst::Element::link_many([appsrc.upcast_ref(), &videoconvert, &sink])?;
pipeline.add_many(&[appsrc.upcast_ref(), &videoconvert, &sink])?;
gst::Element::link_many(&[appsrc.upcast_ref(), &videoconvert, &sink])?;
// Our frame counter, that is stored in the mutable environment
// of the closure of the need-data callback

View file

@ -1,153 +0,0 @@
// 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);
}

View file

@ -12,6 +12,7 @@ mod examples_common;
mod cairo_compositor {
use gst_base::subclass::prelude::*;
use gst_video::{prelude::*, subclass::prelude::*};
use once_cell::sync::Lazy;
// In the imp submodule we include the actual implementation of the compositor.
mod imp {
@ -52,20 +53,19 @@ mod cairo_compositor {
// Implementation of glib::Object virtual methods.
impl ObjectImpl for CairoCompositor {
// Specification of the compositor properties.
// Specfication of the compositor properties.
// In this case a single property for configuring the background color of the
// composition.
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: std::sync::OnceLock<Vec<glib::ParamSpec>> =
std::sync::OnceLock::new();
PROPERTIES.get_or_init(|| {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
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,24 +100,20 @@ 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: std::sync::OnceLock<gst::subclass::ElementMetadata> =
std::sync::OnceLock::new();
Some(ELEMENT_METADATA.get_or_init(|| {
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
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: std::sync::OnceLock<Vec<gst::PadTemplate>> =
std::sync::OnceLock::new();
PAD_TEMPLATES.get_or_init(|| {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
// 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.
@ -152,7 +148,9 @@ mod cairo_compositor {
)
.unwrap(),
]
})
});
PAD_TEMPLATES.as_ref()
}
// Notify via the child proxy interface whenever a new pad is added or removed.
@ -455,14 +453,11 @@ mod cairo_compositor {
// Implementation of glib::Object virtual methods.
impl ObjectImpl for CairoCompositorPad {
// Specification of the compositor pad properties.
// Specfication of the compositor pad properties.
// 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: std::sync::OnceLock<Vec<glib::ParamSpec>> =
std::sync::OnceLock::new();
PROPERTIES.get_or_init(|| {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecDouble::builder("alpha")
.nick("Alpha")
@ -500,7 +495,9 @@ 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.
@ -579,7 +576,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
comp.set_property("background-color", 0xff_33_33_33u32);
pipeline.add_many([&src1, &src2, comp.upcast_ref(), &conv, &sink])?;
pipeline.add_many(&[&src1, &src2, comp.upcast_ref(), &conv, &sink])?;
// Link everything together.
src1.link_filtered(

View file

@ -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,33 +75,31 @@ 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| {
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);
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");
}
}
}
});
} else {
println!("Got custom event, with send_eos=false. Ignoring");
}
_ => (),
}
gst::PadProbeReturn::Ok
});
@ -115,8 +113,9 @@ 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 Some(pipeline) = pipeline_weak.upgrade() else {
return glib::ControlFlow::Break;
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return glib::Continue(false),
};
println!("Sending custom event to the pipeline with send_eos={send_eos}");
let ev = ExampleCustomEvent::new(*send_eos);
@ -125,43 +124,42 @@ fn example_main() {
}
// Remove this handler, the pipeline will shutdown once our pad probe catches the custom
// event and sends EOS
glib::ControlFlow::Break
glib::Continue(false)
});
}
let main_loop_clone = main_loop.clone();
// This sets the bus's signal handler (don't be mislead by the "add", there can only be one).
// Every message from the bus is passed through this function. Its returnvalue determines
// whether the handler wants to be called again. If glib::ControlFlow::Break is returned, the
// whether the handler wants to be called again. If glib::Continue(false) is returned, the
// handler is removed and will never be called again. The mainloop still runs though.
let _bus_watch = bus
.add_watch(move |_, msg| {
use gst::MessageView;
bus.add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => {
println!("received eos");
// An EndOfStream event was sent to the pipeline, so we tell our main loop
// to stop execution here.
main_loop.quit()
}
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => {
println!("received eos");
// An EndOfStream event was sent to the pipeline, so we tell our main loop
// to stop execution here.
main_loop.quit()
}
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
// Tell the mainloop to continue executing this callback.
glib::ControlFlow::Continue
})
.expect("Failed to add bus watch");
// Tell the mainloop to continue executing this callback.
glib::Continue(true)
})
.expect("Failed to add bus watch");
// Operate GStreamer's bus, facilitating GLib's mainloop here.
// This function call will block until you tell the mainloop to quit
@ -171,6 +169,11 @@ fn example_main() {
pipeline
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state");
// Remove the watch function from the bus.
// Again: There can always only be one watch function.
// Thus we don't have to tell him which function to remove.
bus.remove_watch().unwrap();
}
fn main() {

View file

@ -72,6 +72,7 @@ mod custom_meta {
use std::{mem, ptr};
use glib::translate::*;
use once_cell::sync::Lazy;
pub(super) struct CustomMetaParams {
pub label: String,
@ -86,10 +87,8 @@ 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: 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(
static TYPE: Lazy<glib::Type> = Lazy::new(|| unsafe {
let t = 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.
@ -99,7 +98,9 @@ mod custom_meta {
assert_ne!(t, glib::Type::INVALID);
t
})
});
*TYPE
}
// Initialization function for our meta. This needs to ensure all fields are correctly
@ -156,24 +157,21 @@ mod custom_meta {
unsafe impl Send for MetaInfo {}
unsafe impl Sync for MetaInfo {}
static META_INFO: std::sync::OnceLock<MetaInfo> = std::sync::OnceLock::new();
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"),
)
});
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()
META_INFO.0.as_ptr()
}
}
}

View file

@ -1,7 +1,7 @@
// This example demonstrates the use of the d3d11videosink's "present"
// signal and the use of Direct2D/DirectWrite APIs in Rust.
//
// Application can perform various hardware-accelerated 2D graphics operation
// Application can perform various hardware-acceleated 2D graphics operation
// (e.g., like cairo can support) and text rendering via the Windows APIs.
// In this example, 2D graphics operation and text rendering will happen
// directly to the on the DXGI swapchain's backbuffer via Windows API in
@ -129,7 +129,7 @@ fn main() -> Result<()> {
// APIs are marked as unsafe, except for cast.
//
// In theory, all the Direct3D/Direct2D APIs could fail for
// some reasons (it's hardware!), but in practice, it's very unexpected
// some reasons (it's hardware!), but in pratice, it's very unexpected
// situation and any of failure below would mean we are doing
// something in wrong way or driver bug or so.
unsafe {
@ -194,7 +194,7 @@ fn main() -> Result<()> {
let mut metrics = DWRITE_TEXT_METRICS::default();
layout.GetMetrics(&mut metrics).unwrap();
layout
.GetFontSize(0, &mut font_size, Some(&mut range))
.GetFontSize2(0, &mut font_size, Some(&mut range))
.unwrap();
if metrics.widthIncludingTrailingWhitespace >= desc.Width as f32 {
@ -305,18 +305,19 @@ 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| {
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();
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();
}
}
gst::PadProbeReturn::Ok
});
@ -328,31 +329,30 @@ fn main() -> Result<()> {
let main_loop_clone = main_loop.clone();
let bus = playbin.bus().unwrap();
let _bus_watch = bus
.add_watch(move |_, msg| {
use gst::MessageView;
bus.add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => {
println!("received eos");
main_loop.quit()
}
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => {
println!("received eos");
main_loop.quit()
}
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
glib::ControlFlow::Continue
})
.unwrap();
glib::Continue(true)
})
.unwrap();
playbin.set_state(gst::State::Playing).unwrap();

View file

@ -19,15 +19,15 @@ fn example_main() {
/* Disable stdout debug, then configure the debug ringbuffer and enable
* all debug */
gst::log::remove_default_log_function();
gst::debug_remove_default_log_function();
/* Keep 1KB of logs per thread, removing old threads after 10 seconds */
gst::log::add_ring_buffer_logger(1024, 10);
gst::debug_add_ring_buffer_logger(1024, 10);
/* Enable all debug categories */
gst::log::set_default_threshold(gst::DebugLevel::Log);
gst::debug_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::log::ring_buffer_logger_get_logs().iter() {
for s in gst::debug_ring_buffer_logger_get_logs().iter() {
println!("{s}\n------------------");
}
}

View file

@ -70,8 +70,8 @@ fn example_main() -> Result<(), Error> {
.build()?;
let decodebin = gst::ElementFactory::make("decodebin").build()?;
pipeline.add_many([&src, &decodebin])?;
gst::Element::link_many([&src, &decodebin])?;
pipeline.add_many(&[&src, &decodebin])?;
gst::Element::link_many(&[&src, &decodebin])?;
// Need to move a new reference into the closure.
// !!ATTENTION!!:
@ -90,8 +90,9 @@ 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 Some(pipeline) = pipeline_weak.upgrade() else {
return;
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return,
};
// Try to detect whether the raw stream decodebin provided us with

View file

@ -37,11 +37,7 @@ fn print_tags(info: &DiscovererInfo) {
fn print_stream_info(stream: &DiscovererStreamInfo) {
println!("Stream: ");
if let Some(stream_id) = stream.stream_id() {
println!(" Stream id: {}", stream_id);
}
println!(" Stream id: {}", stream.stream_id());
let caps_str = match stream.caps() {
Some(caps) => caps.to_string(),
None => String::from("--"),

View file

@ -97,13 +97,13 @@ fn example_main() -> Result<(), Error> {
configure_encodebin(&encodebin);
pipeline
.add_many([&src, &encodebin, &sink])
.add_many(&[&src, &encodebin, &sink])
.expect("failed to add elements to pipeline");
// It is clear from the start, that encodebin has only one src pad, so we can
// directly link it to our filesink without problems.
// The caps of encodebin's src-pad are set after we configured the encoding-profile.
// (But filesink doesn't really care about the caps at its input anyway)
gst::Element::link_many([&encodebin, &sink])?;
gst::Element::link_many(&[&encodebin, &sink])?;
// Need to move a new reference into the closure.
// !!ATTENTION!!:
@ -120,8 +120,9 @@ 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 Some(pipeline) = pipeline_weak.upgrade() else {
return;
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return,
};
let (is_audio, is_video) = {
@ -188,7 +189,7 @@ fn example_main() -> Result<(), Error> {
// Request a sink pad from our encodebin, that can handle a raw videostream.
// The encodebin will then automatically create an internal pipeline, that encodes
// the video stream in the format we specified in the EncodingProfile.
// the audio stream in the format we specified in the EncodingProfile.
let enc_sink_pad = encodebin
.request_pad_simple("video_%u")
.expect("Could not get video pad from encodebin");

View file

@ -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
@ -50,13 +50,14 @@ fn example_main() {
// Add a timeout to the main loop. This closure will be executed
// in an interval of 5 seconds. The return value of the handler function
// determines whether the handler still wants to be called:
// - glib::ControlFlow::Break - stop calling this handler, remove timeout
// - glib::ControlFlow::Continue- continue calling this handler
// - glib::Continue(false) - stop calling this handler, remove timeout
// - glib::Continue(true) - continue calling this handler
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 Some(pipeline) = pipeline_weak.upgrade() else {
return glib::ControlFlow::Break;
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return glib::Continue(false),
};
println!("sending eos");
@ -76,7 +77,7 @@ fn example_main() {
// Remove this handler, the pipeline will shutdown anyway, now that we
// sent the EOS event.
glib::ControlFlow::Break
glib::Continue(false)
});
//bus.add_signal_watch();
@ -84,36 +85,35 @@ fn example_main() {
let main_loop_clone = main_loop.clone();
// This sets the bus's signal handler (don't be mislead by the "add", there can only be one).
// Every message from the bus is passed through this function. Its returnvalue determines
// whether the handler wants to be called again. If glib::ControlFlow::Break is returned, the
// whether the handler wants to be called again. If glib::Continue(false) is returned, the
// handler is removed and will never be called again. The mainloop still runs though.
let _bus_watch = bus
.add_watch(move |_, msg| {
use gst::MessageView;
bus.add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => {
println!("received eos");
// An EndOfStream event was sent to the pipeline, so we tell our main loop
// to stop execution here.
main_loop.quit()
}
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => {
println!("received eos");
// An EndOfStream event was sent to the pipeline, so we tell our main loop
// to stop execution here.
main_loop.quit()
}
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
// Tell the mainloop to continue executing this callback.
glib::ControlFlow::Continue
})
.expect("Failed to add bus watch");
// Tell the mainloop to continue executing this callback.
glib::Continue(true)
})
.expect("Failed to add bus watch");
// Operate GStreamer's bus, facilliating GLib's mainloop here.
// This function call will block until you tell the mainloop to quit
@ -123,6 +123,11 @@ fn example_main() {
pipeline
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state");
// Remove the watch function from the bus.
// Again: There can always only be one watch function.
// Thus we don't have to tell him which function to remove.
bus.remove_watch().unwrap();
}
fn main() {

View file

@ -45,8 +45,8 @@ fn create_receiver_pipeline(
let queue = gst::ElementFactory::make("queue").build()?;
let sink = gst::ElementFactory::make("autovideosink").build()?;
pipeline.add_many([src.upcast_ref(), &filter, &convert, &queue, &sink])?;
gst::Element::link_many([src.upcast_ref(), &filter, &convert, &queue, &sink])?;
pipeline.add_many(&[src.upcast_ref(), &filter, &convert, &queue, &sink])?;
gst::Element::link_many(&[src.upcast_ref(), &filter, &convert, &queue, &sink])?;
let fd_allocator = gst_allocators::FdAllocator::new();
let video_info = video_info.clone();
@ -115,8 +115,8 @@ fn create_sender_pipeline(
.ok_or_else(|| anyhow::anyhow!("is not a appsink"))?
.set_caps(Some(&caps));
pipeline.add_many([&src, &sink])?;
gst::Element::link_many([&src, &sink])?;
pipeline.add_many(&[&src, &sink])?;
gst::Element::link_many(&[&src, &sink])?;
let appsink = sink
.downcast::<gst_app::AppSink>()
@ -363,7 +363,7 @@ mod video_filter {
use anyhow::Error;
use gst::{subclass::prelude::*, PadDirection, PadPresence, PadTemplate};
use gst_app::gst_base::subclass::BaseTransformMode;
use gst_video::{prelude::*, subclass::prelude::*, VideoFrameRef};
use gst_video::{subclass::prelude::*, VideoFrameRef};
use memmap2::MmapMut;
use once_cell::sync::Lazy;
@ -430,10 +430,7 @@ mod video_filter {
impl ElementImpl for FdMemoryFadeInVideoFilter {
fn pad_templates() -> &'static [PadTemplate] {
static PAD_TEMPLATES: std::sync::OnceLock<Vec<PadTemplate>> =
std::sync::OnceLock::new();
PAD_TEMPLATES.get_or_init(|| {
static PAD_TEMPLATES: Lazy<Vec<PadTemplate>> = Lazy::new(|| {
let caps = gst_video::VideoCapsBuilder::new()
.format(gst_video::VideoFormat::Bgra)
.build();
@ -443,7 +440,9 @@ mod video_filter {
PadTemplate::new("src", PadDirection::Src, PadPresence::Always, &caps)
.unwrap(),
]
})
});
PAD_TEMPLATES.as_ref()
}
}

View file

@ -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

View file

@ -1,7 +1,5 @@
#![allow(clippy::non_send_fields_in_send_ty)]
use anyhow::Result;
#[path = "../glupload.rs"]
mod glupload;
use glupload::*;
@ -163,12 +161,14 @@ mod mirror {
}
}
fn example_main() -> Result<()> {
fn example_main() {
gst::init().unwrap();
let glfilter = mirror::GLMirrorFilter::new(Some("Mirror filter"));
App::new(Some(glfilter.as_ref())).and_then(main_loop)
let glfilter = mirror::GLMirrorFilter::new(Some("foo"));
App::new(Some(glfilter.as_ref()))
.and_then(main_loop)
.unwrap_or_else(|e| eprintln!("Error! {e}"))
}
fn main() -> Result<()> {
examples_common::run(example_main)
fn main() {
examples_common::run(example_main);
}

View file

@ -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

View file

@ -1,7 +1,5 @@
#![allow(clippy::non_send_fields_in_send_ty)]
use anyhow::Result;
#[path = "../glupload.rs"]
mod glupload;
use glupload::*;
@ -9,10 +7,12 @@ use glupload::*;
#[path = "../examples-common.rs"]
pub mod examples_common;
fn example_main() -> Result<()> {
App::new(None).and_then(main_loop)
fn example_main() {
App::new(None)
.and_then(main_loop)
.unwrap_or_else(|e| eprintln!("Error! {e}"))
}
fn main() -> Result<()> {
examples_common::run(example_main)
fn main() {
examples_common::run(example_main);
}

179
examples/src/bin/gtksink.rs Normal file
View file

@ -0,0 +1,179 @@
// This example demonstrates how to use gstreamer in conjunction with the gtk widget toolkit.
// This example shows the video produced by a videotestsrc within a small gtk gui.
// For this, the gtkglsink is used, which creates a gtk widget one can embed the gtk gui.
// For this, there multiple types of widgets. gtkglsink uses OpenGL to render frames, and
// gtksink uses the CPU to render the frames (which is way slower).
// So the example application first tries to use OpenGL, and when that fails, fall back.
// The pipeline looks like the following:
// gtk-gui: {gtkglsink}-widget
// (|)
// {videotestsrc} - {glsinkbin}
use std::cell::RefCell;
use gio::prelude::*;
use gst::prelude::*;
use gtk::prelude::*;
fn create_ui(app: &gtk::Application) {
let pipeline = gst::Pipeline::default();
let src = gst::ElementFactory::make("videotestsrc").build().unwrap();
// Create the gtk sink and retrieve the widget from it. The sink element will be used
// in the pipeline, and the widget will be embedded in our gui.
// Gstreamer then displays frames in the gtk widget.
// First, we try to use the OpenGL version - and if that fails, we fall back to non-OpenGL.
let (sink, widget) = if let Ok(gtkglsink) = gst::ElementFactory::make("gtkglsink").build() {
// Using the OpenGL widget succeeded, so we are in for a nice playback experience with
// low cpu usage. :)
// The gtkglsink essentially allocates an OpenGL texture on the GPU, that it will display.
// Now we create the glsinkbin element, which is responsible for conversions and for uploading
// video frames to our texture (if they are not already in the GPU). Now we tell the OpenGL-sink
// about our gtkglsink element, form where it will retrieve the OpenGL texture to fill.
let glsinkbin = gst::ElementFactory::make("glsinkbin")
.property("sink", &gtkglsink)
.build()
.unwrap();
// The gtkglsink creates the gtk widget for us. This is accessible through a property.
// So we get it and use it later to add it to our gui.
let widget = gtkglsink.property::<gtk::Widget>("widget");
(glsinkbin, widget)
} else {
// Unfortunately, using the OpenGL widget didn't work out, so we will have to render
// our frames manually, using the CPU. An example why this may fail is, when
// the PC doesn't have proper graphics drivers installed.
let sink = gst::ElementFactory::make("gtksink").build().unwrap();
// The gtksink creates the gtk widget for us. This is accessible through a property.
// So we get it and use it later to add it to our gui.
let widget = sink.property::<gtk::Widget>("widget");
(sink, widget)
};
pipeline.add_many(&[&src, &sink]).unwrap();
src.link(&sink).unwrap();
// Create a simple gtk gui window to place our widget into.
let window = gtk::Window::new(gtk::WindowType::Toplevel);
window.set_default_size(320, 240);
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
// Add our widget to the gui
vbox.pack_start(&widget, true, true, 0);
let label = gtk::Label::new(Some("Position: 00:00:00"));
vbox.pack_start(&label, true, true, 5);
window.add(&vbox);
window.show_all();
app.add_window(&window);
// Need to move a new reference into the closure.
// !!ATTENTION!!:
// It might seem appealing to use pipeline.clone() here, because that greatly
// simplifies the code within the callback. What this actually does, however, is creating
// a memory leak. The clone of a pipeline is a new strong reference on the pipeline.
// Storing this strong reference of the pipeline within the callback (we are moving it in!),
// which is in turn stored in another strong reference on the pipeline is creating a
// reference cycle.
// DO NOT USE pipeline.clone() TO USE THE PIPELINE WITHIN A CALLBACK
let pipeline_weak = pipeline.downgrade();
// Add a timeout to the main loop that will periodically (every 500ms) be
// executed. This will query the current position within the stream from
// the underlying pipeline, and display it in our gui.
// Since this closure is called by the mainloop thread, we are allowed
// to modify the gui widgets here.
let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(500), move || {
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
// we moved into this callback.
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return glib::Continue(true),
};
// Query the current playing position from the underlying pipeline.
let position = pipeline.query_position::<gst::ClockTime>();
// Display the playing position in the gui.
label.set_text(&format!("Position: {:.0}", position.display()));
// Tell the callback to continue calling this closure.
glib::Continue(true)
});
let bus = pipeline.bus().unwrap();
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set the pipeline to the `Playing` state");
let app_weak = app.downgrade();
bus.add_watch_local(move |_, msg| {
use gst::MessageView;
let app = match app_weak.upgrade() {
Some(app) => app,
None => return glib::Continue(false),
};
match msg.view() {
MessageView::Eos(..) => app.quit(),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
app.quit();
}
_ => (),
};
glib::Continue(true)
})
.expect("Failed to add bus watch");
// Pipeline reference is owned by the closure below, so will be
// destroyed once the app is destroyed
let timeout_id = RefCell::new(Some(timeout_id));
let pipeline = RefCell::new(Some(pipeline));
app.connect_shutdown(move |_| {
// Optional, by manually destroying the window here we ensure that
// the gst element is destroyed when shutting down instead of having to wait
// for the process to terminate, allowing us to use the leaks tracer.
unsafe {
window.destroy();
}
// GTK will keep the Application alive for the whole process lifetime.
// Wrapping the pipeline in a RefCell<Option<_>> and removing it from it here
// ensures the pipeline is actually destroyed when shutting down, allowing us
// to use the leaks tracer for example.
if let Some(pipeline) = pipeline.borrow_mut().take() {
pipeline
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state");
pipeline.bus().unwrap().remove_watch().unwrap();
}
if let Some(timeout_id) = timeout_id.borrow_mut().take() {
timeout_id.remove();
}
});
}
fn main() -> glib::ExitCode {
// Initialize gstreamer and the gtk widget toolkit libraries.
gst::init().unwrap();
gtk::init().unwrap();
let res = {
let app = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE);
app.connect_activate(create_ui);
app.run()
};
// Optional, can be used to detect leaks using the leaks tracer
unsafe {
gst::deinit();
}
res
}

View file

@ -0,0 +1,286 @@
// This example demonstrates another type of combination of gtk and gstreamer,
// in comparision to the gtksink example.
// This example uses regions that are managed by the window system, and uses
// the window system's api to insert a videostream into these regions.
// So essentially, the window system of the system overlays our gui with
// the video frames - within the region that we tell it to use.
// Disadvantage of this method is, that it's highly platform specific, since
// the big platforms all have their own window system. Thus, this example
// has special code to handle differences between platforms.
// Windows could theoretically be supported by this example, but is not yet implemented.
// One of the very few (if not the single one) platform, that can not provide the API
// needed for this are Linux desktops using Wayland.
// TODO: Add Windows support
// In this case, a testvideo is displayed within our gui, using the
// following pipeline:
// {videotestsrc} - {xvimagesink(on linux)}
// {videotestsrc} - {glimagesink(on mac)}
use std::{cell::RefCell, os::raw::c_void, process};
use gio::prelude::*;
use gst_video::prelude::*;
use gtk::prelude::*;
#[cfg(all(target_os = "linux", feature = "gtkvideooverlay-x11"))]
fn create_video_sink() -> gst::Element {
// When we are on linux with the Xorg display server, we use the
// X11 protocol's XV extension, which allows to overlay regions
// with video streams. For this, we use the xvimagesink element.
gst::ElementFactory::make("xvimagesink").build().unwrap()
}
#[cfg(all(target_os = "linux", feature = "gtkvideooverlay-x11"))]
fn set_window_handle(video_overlay: &gst_video::VideoOverlay, gdk_window: &gdk::Window) {
let display_type_name = gdk_window.display().type_().name();
// Check if we're using X11 or ...
if display_type_name == "GdkX11Display" {
extern "C" {
pub fn gdk_x11_window_get_xid(window: *mut glib::gobject_ffi::GObject) -> *mut c_void;
}
// This is unsafe because the "window handle" we pass here is basically like a raw pointer.
// If a wrong value were to be passed here (and you can pass any integer), then the window
// system will most likely cause the application to crash.
#[allow(clippy::cast_ptr_alignment)]
unsafe {
// Here we ask gdk what native window handle we got assigned for
// our video region from the window system, and then we will
// pass this unique identifier to the overlay provided by our
// sink - so the sink can then arrange the overlay.
let xid = gdk_x11_window_get_xid(gdk_window.as_ptr() as *mut _);
video_overlay.set_window_handle(xid as usize);
}
} else {
println!("Add support for display type '{display_type_name}'");
process::exit(-1);
}
}
#[cfg(all(target_os = "macos", feature = "gtkvideooverlay-quartz"))]
fn create_video_sink() -> gst::Element {
// On Mac, this is done by overlaying a window region with an
// OpenGL-texture, using the glimagesink element.
gst::ElementFactory::make("glimagesink").build().unwrap()
}
#[cfg(all(target_os = "macos", feature = "gtkvideooverlay-quartz"))]
fn set_window_handle(video_overlay: &gst_video::VideoOverlay, gdk_window: &gdk::Window) {
let display_type_name = gdk_window.display().type_().name();
if display_type_name == "GdkQuartzDisplay" {
extern "C" {
pub fn gdk_quartz_window_get_nsview(
window: *mut glib::gobject_ffi::GObject,
) -> *mut c_void;
}
// This is unsafe because the "window handle" we pass here is basically like a raw pointer.
// If a wrong value were to be passed here (and you can pass any integer), then the window
// system will most likely cause the application to crash.
#[allow(clippy::cast_ptr_alignment)]
unsafe {
// Here we ask gdk what native window handle we got assigned for
// our video region from the windowing system, and then we will
// pass this unique identifier to the overlay provided by our
// sink - so the sink can then arrange the overlay.
let window = gdk_quartz_window_get_nsview(gdk_window.as_ptr() as *mut _);
video_overlay.set_window_handle(window as usize);
}
} else {
println!("Unsupported display type '{}", display_type_name);
process::exit(-1);
}
}
fn create_ui(app: &gtk::Application) {
let pipeline = gst::Pipeline::default();
let src = gst::ElementFactory::make("videotestsrc").build().unwrap();
// Since using the window system to overlay our gui window is making
// direct contact with the windowing system, this is highly platform-
// specific. This example supports Linux and Mac (using X11 and Quartz).
let sink = create_video_sink();
pipeline.add_many(&[&src, &sink]).unwrap();
src.link(&sink).unwrap();
// First, we create our gtk window - which will contain a region where
// our overlayed video will be displayed in.
let window = gtk::Window::new(gtk::WindowType::Toplevel);
window.set_default_size(320, 240);
let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
// This creates the widget we will display our overlay in.
// Later, we will try to tell our window system about this region, so
// it can overlay it with our video stream.
let video_window = gtk::DrawingArea::new();
video_window.set_size_request(320, 240);
// Use the platform-specific sink to create our overlay.
// Since we only use the video_overlay in the closure below, we need a weak reference.
// !!ATTENTION!!:
// It might seem appealing to use .clone() here, because that greatly
// simplifies the code within the callback. What this actually does, however, is creating
// a memory leak.
let video_overlay = sink
.dynamic_cast::<gst_video::VideoOverlay>()
.unwrap()
.downgrade();
// Connect to this widget's realize signal, which will be emitted
// after its display has been initialized. This is neccessary, because
// the window system doesn't know about our region until it was initialized.
video_window.connect_realize(move |video_window| {
// Here we temporarily retrieve a strong reference on the video-overlay from the
// weak reference that we moved into the closure.
let video_overlay = match video_overlay.upgrade() {
Some(video_overlay) => video_overlay,
None => return,
};
// Gtk uses gdk under the hood, to handle its drawing. Drawing regions are
// called gdk windows. We request this underlying drawing region from the
// widget we will overlay with our video.
let gdk_window = video_window.window().unwrap();
// This is where we tell our window system about the drawing-region we
// want it to overlay. Most often, the window system would only know
// about our most outer region (or: our window).
if !gdk_window.ensure_native() {
println!("Can't create native window for widget");
process::exit(-1);
}
set_window_handle(&video_overlay, &gdk_window);
});
vbox.pack_start(&video_window, true, true, 0);
let label = gtk::Label::new(Some("Position: 00:00:00"));
vbox.pack_start(&label, true, true, 5);
window.add(&vbox);
window.show_all();
app.add_window(&window);
// Need to move a new reference into the closure.
// !!ATTENTION!!:
// It might seem appealing to use pipeline.clone() here, because that greatly
// simplifies the code within the callback. What this actually does, however, is creating
// a memory leak. The clone of a pipeline is a new strong reference on the pipeline.
// Storing this strong reference of the pipeline within the callback (we are moving it in!),
// which is in turn stored in another strong reference on the pipeline is creating a
// reference cycle.
// DO NOT USE pipeline.clone() TO USE THE PIPELINE WITHIN A CALLBACK
let pipeline_weak = pipeline.downgrade();
// Add a timeout to the main loop that will periodically (every 500ms) be
// executed. This will query the current position within the stream from
// the underlying pipeline, and display it in our gui.
// Since this closure is called by the mainloop thread, we are allowed
// to modify the gui widgets here.
let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(500), move || {
// Here we temporarily retrieve a strong reference on the pipeline from the weak one
// we moved into this callback.
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return glib::Continue(false),
};
// Query the current playing position from the underlying pipeline.
let position = pipeline.query_position::<gst::ClockTime>();
// Display the playing position in the gui.
label.set_text(&format!("Position: {:.0}", position.display()));
// Tell the timeout to continue calling this callback.
glib::Continue(true)
});
let bus = pipeline.bus().unwrap();
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set the pipeline to the `Playing` state");
let app_weak = app.downgrade();
bus.add_watch_local(move |_, msg| {
use gst::MessageView;
let app = match app_weak.upgrade() {
Some(app) => app,
None => return glib::Continue(false),
};
match msg.view() {
MessageView::Eos(..) => app.quit(),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
app.quit();
}
_ => (),
};
glib::Continue(true)
})
.expect("Failed to add bus watch");
// Pipeline reference is owned by the closure below, so will be
// destroyed once the app is destroyed
let timeout_id = RefCell::new(Some(timeout_id));
let pipeline = RefCell::new(Some(pipeline));
app.connect_shutdown(move |_| {
// Optional, by manually destroying the window here we ensure that
// the gst element is destroyed when shutting down instead of having to wait
// for the process to terminate, allowing us to use the leaks tracer.
unsafe {
window.destroy();
}
// GTK will keep the Application alive for the whole process lifetime.
// Wrapping the pipeline in a RefCell<Option<_>> and removing it from it here
// ensures the pipeline is actually destroyed when shutting down, allowing us
// to use the leaks tracer for example.
if let Some(pipeline) = pipeline.borrow_mut().take() {
pipeline
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state");
pipeline.bus().unwrap().remove_watch().unwrap();
}
if let Some(timeout_id) = timeout_id.borrow_mut().take() {
timeout_id.remove();
}
});
}
fn main() -> glib::ExitCode {
#[cfg(not(unix))]
{
println!("Add support for target platform");
process::exit(-1);
}
// Initialize gstreamer and the gtk widget toolkit libraries.
gst::init().unwrap();
gtk::init().unwrap();
let res = {
let app = gtk::Application::new(None, gio::ApplicationFlags::FLAGS_NONE);
app.connect_activate(create_ui);
app.run()
};
// Optional, can be used to detect leaks using the leaks tracer
unsafe {
gst::deinit();
}
res
}

View file

@ -1,5 +1,5 @@
// This example demonstrates how to use GStreamer's iteration APIs.
// This is used at multiple occasions - for example to iterate an
// This is used at multiple occassions - for example to iterate an
// element's pads.
use gst::prelude::*;

View file

@ -17,7 +17,7 @@ fn example_main() {
gst::init().unwrap();
// Let GStreamer create a pipeline from the parsed launch syntax on the cli.
// In comparison to the launch_glib_main example, this is using the advanced launch syntax
// In comparision to the launch_glib_main example, this is using the advanced launch syntax
// parsing API of GStreamer. The function returns a Result, handing us the pipeline if
// parsing and creating succeeded, and hands us detailed error information if something
// went wrong. The error is passed as gst::ParseError. In this example, we separately
@ -26,22 +26,19 @@ 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}");
}
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)
}
};
process::exit(-1)
}
};
let bus = pipeline.bus().unwrap();
pipeline

View file

@ -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
@ -35,34 +35,38 @@ fn example_main() {
//bus.add_signal_watch();
//bus.connect_message(None, move |_, msg| {
let _bus_watch = bus
.add_watch(move |_, msg| {
use gst::MessageView;
bus.add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => main_loop.quit(),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => main_loop.quit(),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
glib::ControlFlow::Continue
})
.expect("Failed to add bus watch");
glib::Continue(true)
})
.expect("Failed to add bus watch");
main_loop.run();
pipeline
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state");
// Here we remove the bus watch we added above. This avoids a memory leak, that might
// otherwise happen because we moved a strong reference (clone of main_loop) into the
// callback closure above.
bus.remove_watch().unwrap();
}
fn main() {

View file

@ -1,359 +0,0 @@
// This example demonstrates how to draw an overlay on a video stream using
// Direct2D/DirectWrite/WIC and the overlay composition element.
// {videotestsrc} - {overlaycomposition} - {capsfilter} - {videoconvert} - {autovideosink}
// The capsfilter element allows us to dictate the video resolution we want for the
// videotestsrc and the overlaycomposition element.
use std::sync::{Arc, Mutex};
use byte_slice_cast::*;
use anyhow::Error;
use derive_more::{Display, Error};
use gst::prelude::*;
use windows::{
Foundation::Numerics::*,
Win32::{
Graphics::{
Direct2D::{Common::*, *},
DirectWrite::*,
Dxgi::Common::*,
Imaging::*,
},
System::Com::*,
},
};
#[derive(Debug, Display, Error)]
#[display(fmt = "Received error from {}: {} (debug: {:?})", src, error, debug)]
struct ErrorMessage {
src: glib::GString,
error: glib::Error,
debug: Option<glib::GString>,
}
struct DrawingContext {
// Factory for creating render target
d2d_factory: ID2D1Factory,
// Used to create WIC bitmap surface
wic_factory: IWICImagingFactory,
// text layout holding text information (string, font, size, etc)
text_layout: IDWriteTextLayout,
// Holding rendred image
bitmap: Option<IWICBitmap>,
// Bound to bitmap and used to actual Direct2D rendering
render_target: Option<ID2D1RenderTarget>,
info: Option<gst_video::VideoInfo>,
}
// Required for IWICBitmap
unsafe impl Send for DrawingContext {}
fn create_pipeline() -> Result<gst::Pipeline, Error> {
gst::init()?;
let pipeline = gst::Pipeline::default();
// The videotestsrc supports multiple test patterns. In this example, we will use the
// pattern with a white ball moving around the video's center point.
let src = gst::ElementFactory::make("videotestsrc")
.property_from_str("pattern", "ball")
.build()?;
let overlay = gst::ElementFactory::make("overlaycomposition").build()?;
let caps = gst_video::VideoCapsBuilder::new()
.width(800)
.height(800)
.framerate((30, 1).into())
.build();
let capsfilter = gst::ElementFactory::make("capsfilter")
.property("caps", &caps)
.build()?;
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
let sink = gst::ElementFactory::make("autovideosink").build()?;
pipeline.add_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
gst::Element::link_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
// Most Direct2D/DirectWrite APIs (including factory methods) are marked as
// "unsafe", but they shouldn't fail in practice
let drawer = unsafe {
let d2d_factory =
D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None).unwrap();
let dwrite_factory =
DWriteCreateFactory::<IDWriteFactory>(DWRITE_FACTORY_TYPE_SHARED).unwrap();
let text_format = dwrite_factory
.CreateTextFormat(
windows::core::w!("Arial"),
None,
DWRITE_FONT_WEIGHT_BOLD,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
32f32,
windows::core::w!("en-us"),
)
.unwrap();
let text_layout = dwrite_factory
.CreateTextLayout(
windows::core::w!("GStreamer").as_wide(),
&text_format,
// Size will be updated later on "caps-changed" signal
800f32,
800f32,
)
.unwrap();
// Top (default) and center alignment
text_layout
.SetTextAlignment(DWRITE_TEXT_ALIGNMENT_CENTER)
.unwrap();
let wic_factory: IWICImagingFactory =
CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_ALL).unwrap();
Arc::new(Mutex::new(DrawingContext {
d2d_factory,
wic_factory,
text_layout,
bitmap: None,
render_target: None,
info: None,
}))
};
overlay.connect_closure(
"draw",
false,
glib::closure!(@strong drawer => move |_overlay: &gst::Element,
sample: &gst::Sample| {
use std::f64::consts::PI;
let drawer = drawer.lock().unwrap();
let buffer = sample.buffer().unwrap();
let timestamp = buffer.pts().unwrap();
let info = drawer.info.as_ref().unwrap();
let text_layout = &drawer.text_layout;
let bitmap = drawer.bitmap.as_ref().unwrap();
let render_target = drawer.render_target.as_ref().unwrap();
let global_angle = 360. * (timestamp % (10 * gst::ClockTime::SECOND)).nseconds() as f64
/ (10.0 * gst::ClockTime::SECOND.nseconds() as f64);
let center_x = (info.width() / 2) as f32;
let center_y = (info.height() / 2) as f32;
let top_margin = (info.height() / 20) as f32;
unsafe {
// Begin drawing
render_target.BeginDraw();
// Clear background
render_target.Clear(Some(&D2D1_COLOR_F {
r: 0f32,
g: 0f32,
b: 0f32,
a: 0f32,
}));
// This loop will render 10 times the string "GStreamer" in a circle
for i in 0..10 {
let angle = (360. * f64::from(i)) / 10.0;
let red = ((1.0 + f64::cos((angle - 60.0) * PI / 180.0)) / 2.0) as f32;
let text_brush = render_target
.CreateSolidColorBrush(
&D2D1_COLOR_F {
r: red,
g: 0f32,
b: 1f32 - red,
a: 1f32,
},
None,
)
.unwrap();
let angle = (angle + global_angle) as f32;
let matrix = Matrix3x2::rotation(angle, center_x, center_y);
render_target.SetTransform(&matrix);
render_target.DrawTextLayout(
D2D_POINT_2F { x: 0f32, y: top_margin },
text_layout,
&text_brush,
D2D1_DRAW_TEXT_OPTIONS_NONE,
);
}
// EndDraw may not be successful for some reasons.
// Ignores any error in this example
let _ = render_target.EndDraw(None, None);
// Make sure all operations is completed before copying
// bitmap to buffer
let _ = render_target.Flush(None::<*mut u64>, None::<*mut u64>);
}
let mut buffer = gst::Buffer::with_size((info.width() * info.height() * 4) as usize).unwrap();
{
let buffer_mut = buffer.get_mut().unwrap();
let mut map = buffer_mut.map_writable().unwrap();
let dst = map.as_mut_slice_of::<u8>().unwrap();
unsafe {
// Bitmap size is equal to the background image size.
// Copy entire memory
bitmap.CopyPixels(std::ptr::null(), info.width() * 4, dst).unwrap();
}
}
gst_video::VideoMeta::add_full(
buffer.get_mut().unwrap(),
gst_video::VideoFrameFlags::empty(),
gst_video::VideoFormat::Bgra,
info.width(),
info.height(),
&[0],
&[(info.width() * 4) as i32],
)
.unwrap();
// Turn the buffer into a VideoOverlayRectangle, then place
// that into a VideoOverlayComposition and return it.
//
// A VideoOverlayComposition can take a Vec of such rectangles
// spaced around the video frame, but we're just outputting 1
// here
let rect = gst_video::VideoOverlayRectangle::new_raw(
&buffer,
0,
0,
info.width(),
info.height(),
gst_video::VideoOverlayFormatFlags::PREMULTIPLIED_ALPHA,
);
gst_video::VideoOverlayComposition::new(Some(&rect))
.unwrap()
}),
);
// Add a signal handler to the overlay's "caps-changed" signal. This could e.g.
// be called when the sink that we render to does not support resizing the image
// itself - but the user just changed the window-size. The element after the overlay
// will then change its caps and we use the notification about this change to
// resize our canvas's size.
// Another possibility for when this might happen is, when our video is a network
// stream that dynamically changes resolution when enough bandwidth is available.
overlay.connect_closure(
"caps-changed",
false,
glib::closure!(move |_overlay: &gst::Element,
caps: &gst::Caps,
_width: u32,
_height: u32| {
let mut drawer = drawer.lock().unwrap();
let info = gst_video::VideoInfo::from_caps(caps).unwrap();
unsafe {
// Update text layout to be identical to new video resolution
drawer.text_layout.SetMaxWidth(info.width() as f32).unwrap();
drawer
.text_layout
.SetMaxHeight(info.height() as f32)
.unwrap();
// Create new WIC bitmap with PBGRA format (pre-multiplied BGRA)
let bitmap = drawer
.wic_factory
.CreateBitmap(
info.width(),
info.height(),
&GUID_WICPixelFormat32bppPBGRA,
WICBitmapCacheOnDemand,
)
.unwrap();
let render_target = drawer
.d2d_factory
.CreateWicBitmapRenderTarget(
&bitmap,
&D2D1_RENDER_TARGET_PROPERTIES {
r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,
pixelFormat: D2D1_PIXEL_FORMAT {
format: DXGI_FORMAT_B8G8R8A8_UNORM,
alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
},
// zero means default DPI
dpiX: 0f32,
dpiY: 0f32,
usage: D2D1_RENDER_TARGET_USAGE_NONE,
minLevel: D2D1_FEATURE_LEVEL_DEFAULT,
},
)
.unwrap();
drawer.render_target = Some(render_target);
drawer.bitmap = Some(bitmap);
}
drawer.info = Some(info);
}),
);
Ok(pipeline)
}
fn main_loop(pipeline: gst::Pipeline) -> Result<(), Error> {
pipeline.set_state(gst::State::Playing)?;
let bus = pipeline
.bus()
.expect("Pipeline without bus. Shouldn't happen!");
for msg in bus.iter_timed(gst::ClockTime::NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => break,
MessageView::Error(err) => {
pipeline.set_state(gst::State::Null)?;
return Err(ErrorMessage {
src: msg
.src()
.map(|s| s.path_string())
.unwrap_or_else(|| glib::GString::from("UNKNOWN")),
error: err.error(),
debug: err.debug(),
}
.into());
}
_ => (),
}
}
pipeline.set_state(gst::State::Null)?;
Ok(())
}
fn main() {
// WIC requires COM initialization
unsafe {
CoInitializeEx(None, COINIT_MULTITHREADED).unwrap();
}
match create_pipeline().and_then(main_loop) {
Ok(r) => r,
Err(e) => eprintln!("Error! {}", e),
}
unsafe {
CoUninitialize();
}
}

View file

@ -77,8 +77,8 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
let sink = gst::ElementFactory::make("autovideosink").build()?;
pipeline.add_many([&src, &overlay, &capsfilter, &videoconvert, &sink])?;
gst::Element::link_many([&src, &overlay, &capsfilter, &videoconvert, &sink])?;
pipeline.add_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
gst::Element::link_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
// The PangoFontMap represents the set of fonts available for a particular rendering system.
let fontmap = pangocairo::FontMap::new();
@ -150,7 +150,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
// The image we draw (the text) will be static, but we will change the
// transformation on the drawing context, which rotates and shifts everything
// that we draw afterwards. Like this, we have no complicated calculations
// that we draw afterwards. Like this, we have no complicated calulations
// in the actual drawing below.
// Calling multiple transformation methods after each other will apply the
// new transformation on top. If you repeat the cr.rotate(angle) line below
@ -179,7 +179,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
// to end up as a 200x100 rectangle would now be 100x200.
pangocairo::functions::update_layout(&cr, layout);
let (width, _height) = layout.size();
// Using width and height of the text, we can properly position it within
// Using width and height of the text, we can properly possition it within
// our canvas.
cr.move_to(
-(f64::from(width) / f64::from(pango::SCALE)) / 2.0,
@ -241,7 +241,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
// will then change its caps and we use the notification about this change to
// resize our canvas's size.
// Another possibility for when this might happen is, when our video is a network
// stream that dynamically changes resolution when enough bandwidth is available.
// stream that dynamically changes resolution when enough bandwith is available.
overlay.connect_closure(
"caps-changed",
false,

View file

@ -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,38 +38,36 @@ 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
let Some(buffer) = probe_info.buffer() else {
return gst::PadProbeReturn::Ok;
};
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();
// 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();
// 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;
};
// 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}");
// 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
});

View file

@ -76,8 +76,8 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
let videoconvert = gst::ElementFactory::make("videoconvert").build()?;
let sink = gst::ElementFactory::make("autovideosink").build()?;
pipeline.add_many([&src, &overlay, &capsfilter, &videoconvert, &sink])?;
gst::Element::link_many([&src, &overlay, &capsfilter, &videoconvert, &sink])?;
pipeline.add_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
gst::Element::link_many(&[&src, &overlay, &capsfilter, &videoconvert, &sink])?;
// The PangoFontMap represents the set of fonts available for a particular rendering system.
let fontmap = pangocairo::FontMap::new();
@ -134,7 +134,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
// The image we draw (the text) will be static, but we will change the
// transformation on the drawing context, which rotates and shifts everything
// that we draw afterwards. Like this, we have no complicated calculations
// that we draw afterwards. Like this, we have no complicated calulations
// in the actual drawing below.
// Calling multiple transformation methods after each other will apply the
// new transformation on top. If you repeat the cr.rotate(angle) line below
@ -163,7 +163,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
// to end up as a 200x100 rectangle would now be 100x200.
pangocairo::functions::update_layout(&cr, layout);
let (width, _height) = layout.size();
// Using width and height of the text, we can properly position it within
// Using width and height of the text, we can properly possition it within
// our canvas.
cr.move_to(
-(f64::from(width) / f64::from(pango::SCALE)) / 2.0,
@ -187,7 +187,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
// will then change its caps and we use the notification about this change to
// resize our canvas's size.
// Another possibility for when this might happen is, when our video is a network
// stream that dynamically changes resolution when enough bandwidth is available.
// stream that dynamically changes resolution when enough bandwith is available.
overlay.connect("caps-changed", false, move |args| {
let _overlay = args[0].get::<gst::Element>().unwrap();
let caps = args[1].get::<gst::Caps>().unwrap();

View file

@ -37,11 +37,6 @@ 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())
}

View file

@ -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.property_value("flags");
// let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
// let flags = playbin.get_property("flags").unwrap();
// let flags_class = FlagsClass::new(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);
// playbin.set_property_from_value("flags", &flags).unwrap();
// 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.
@ -59,7 +59,7 @@ fn example_main() {
let playbin = values[0]
.get::<glib::Object>()
.expect("playbin \"audio-tags-changed\" signal values[1]");
// This gets the index of the stream that changed. This is necessary, since
// This gets the index of the stream that changed. This is neccessary, since
// there could e.g. be multiple audio streams (english, spanish, ...).
let idx = values[1]
.get::<i32>()

View file

@ -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,8 +50,9 @@ 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 Some(pipeline) = pipeline_weak.upgrade() else {
return glib::ControlFlow::Break;
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return glib::Continue(true),
};
//let pos = pipeline.query_position(gst::Format::Time).unwrap_or(-1);
@ -86,35 +87,34 @@ fn example_main() {
println!("{} / {}", pos.display(), dur.display());
glib::ControlFlow::Continue
glib::Continue(true)
});
// Need to move a new reference into the closure.
let main_loop_clone = main_loop.clone();
//bus.add_signal_watch();
//bus.connect_message(None, move |_, msg| {
let _bus_watch = bus
.add_watch(move |_, msg| {
use gst::MessageView;
bus.add_watch(move |_, msg| {
use gst::MessageView;
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => main_loop.quit(),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
let main_loop = &main_loop_clone;
match msg.view() {
MessageView::Eos(..) => main_loop.quit(),
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
main_loop.quit();
}
_ => (),
};
glib::ControlFlow::Continue
})
.expect("Failed to add bus watch");
glib::Continue(true)
})
.expect("Failed to add bus watch");
main_loop.run();
@ -122,6 +122,7 @@ fn example_main() {
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state");
bus.remove_watch().unwrap();
timeout_id.remove();
}

View file

@ -114,8 +114,8 @@ fn example_main() -> Result<(), Error> {
.property("caps", &video_caps)
.build()?;
pipeline.add_many([&src, &netsim, &rtpbin, &depay, &dec, &conv, &scale, &filter])?;
gst::Element::link_many([&depay, &dec, &conv, &scale, &filter])?;
pipeline.add_many(&[&src, &netsim, &rtpbin, &depay, &dec, &conv, &scale, &filter])?;
gst::Element::link_many(&[&depay, &dec, &conv, &scale, &filter])?;
match args[1].as_str() {
"play" => {
@ -132,8 +132,8 @@ fn example_main() -> Result<(), Error> {
.property("location", "out.mkv")
.build()?;
pipeline.add_many([&enc, &mux, &sink])?;
gst::Element::link_many([&filter, &enc, &mux, &sink])?;
pipeline.add_many(&[&enc, &mux, &sink])?;
gst::Element::link_many(&[&filter, &enc, &mux, &sink])?;
eprintln!("Recording to out.mkv");
}
_ => return Err(Error::from(UsageError(args[0].clone()))),
@ -203,8 +203,9 @@ fn example_main() -> Result<(), Error> {
let depay_weak = depay.downgrade();
rtpbin.connect_pad_added(move |rtpbin, src_pad| {
let Some(depay) = depay_weak.upgrade() else {
return;
let depay = match depay_weak.upgrade() {
Some(depay) => depay,
None => return,
};
match connect_rtpbin_srcpad(src_pad, &depay) {
@ -252,7 +253,11 @@ fn example_main() -> Result<(), Error> {
if let Some(element) = msg.src() {
if element == &pipeline && s.current() == gst::State::Playing {
eprintln!("PLAYING");
pipeline.debug_to_dot_file(gst::DebugGraphDetails::all(), "client-playing");
gst::debug_bin_to_dot_file(
&pipeline,
gst::DebugGraphDetails::all(),
"client-playing",
);
}
}
}

View file

@ -99,7 +99,7 @@ fn example_main() -> Result<(), Error> {
.property("sync", true)
.build()?;
pipeline.add_many([&src, &conv, &q1, &enc, &q2, &pay, &rtpbin, &sink])?;
pipeline.add_many(&[&src, &conv, &q1, &enc, &q2, &pay, &rtpbin, &sink])?;
conv.link(&q1)?;
q1.link(&enc)?;
@ -179,7 +179,11 @@ fn example_main() -> Result<(), Error> {
if let Some(element) = msg.src() {
if element == &pipeline && s.current() == gst::State::Playing {
eprintln!("PLAYING");
pipeline.debug_to_dot_file(gst::DebugGraphDetails::all(), "server-playing");
gst::debug_bin_to_dot_file(
&pipeline,
gst::DebugGraphDetails::all(),
"server-playing",
);
}
}
}

View file

@ -1,223 +0,0 @@
// 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}"),
}
}

View file

@ -4,10 +4,11 @@
// 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;
use std::{env, ptr};
use anyhow::Error;
use derive_more::{Display, Error};
use glib::translate::*;
use gst_rtsp_server::prelude::*;
#[path = "../examples-common.rs"]
@ -44,11 +45,12 @@ 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::builder()
.field(gst_rtsp_server::RTSP_TOKEN_MEDIA_FACTORY_ROLE, "user")
.build();
let token = gst_rtsp_server::RTSPToken::new(&[(
gst_rtsp_server::RTSP_TOKEN_MEDIA_FACTORY_ROLE,
&"user",
)]);
let basic = gst_rtsp_server::RTSPAuth::make_basic("user", "password");
// For proper authentication, we want to use encryption. And there's no
// For propery authentication, we want to use encryption. And there's no
// encryption without a certificate!
let cert = gio::TlsCertificate::from_pem(
"-----BEGIN CERTIFICATE-----\
@ -76,14 +78,24 @@ 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.
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(),
);
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>(),
);
}
auth.set_tls_certificate(Some(&cert));
auth.add_basic(basic.as_str(), &token);

View file

@ -19,6 +19,10 @@ 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();
@ -133,8 +137,8 @@ mod media_factory {
.build()
.unwrap();
bin.add_many([&src, &enc, &pay]).unwrap();
gst::Element::link_many([&src, &enc, &pay]).unwrap();
bin.add_many(&[&src, &enc, &pay]).unwrap();
gst::Element::link_many(&[&src, &enc, &pay]).unwrap();
Some(bin.upcast())
}

View file

@ -60,27 +60,23 @@ mod fir_filter {
// Implementation of gst::Element virtual methods
impl ElementImpl for FirFilter {
// 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
// gst-inspect-1.0 and can also be programatically 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(|| {
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
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: std::sync::OnceLock<Vec<gst::PadTemplate>> =
std::sync::OnceLock::new();
PAD_TEMPLATES.get_or_init(|| {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
// 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.
@ -111,7 +107,9 @@ mod fir_filter {
)
.unwrap(),
]
})
});
PAD_TEMPLATES.as_ref()
}
}
@ -250,7 +248,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Error> {
let conv = gst::ElementFactory::make("audioconvert").build()?;
let sink = gst::ElementFactory::make("autoaudiosink").build()?;
pipeline.add_many([&src, filter.upcast_ref(), &conv, &sink])?;
pipeline.add_many(&[&src, filter.upcast_ref(), &conv, &sink])?;
src.link(&filter)?;
filter.link(&conv)?;
conv.link(&sink)?;

View file

@ -1,188 +0,0 @@
// 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) {}
}

View file

@ -1,86 +0,0 @@
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);
};
}
}

View file

@ -1,170 +0,0 @@
// 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);
}
}

View file

@ -1,15 +0,0 @@
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;
}

View file

@ -1,66 +0,0 @@
// 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);
}

View file

@ -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(),

View file

@ -10,7 +10,6 @@
use anyhow::Error;
use derive_more::{Display, Error};
use gst::{element_error, prelude::*};
use gst_video::prelude::*;
#[path = "../examples-common.rs"]
mod examples_common;
@ -27,7 +26,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>()

View file

@ -34,8 +34,8 @@ fn example_main() {
.unwrap();
let decodebin = gst::ElementFactory::make("decodebin").build().unwrap();
pipeline.add_many([&src, &decodebin]).unwrap();
gst::Element::link_many([&src, &decodebin]).unwrap();
pipeline.add_many(&[&src, &decodebin]).unwrap();
gst::Element::link_many(&[&src, &decodebin]).unwrap();
// Need to move a new reference into the closure.
// !!ATTENTION!!:
@ -52,8 +52,9 @@ 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 Some(pipeline) = pipeline_weak.upgrade() else {
return;
let pipeline = match pipeline_weak.upgrade() {
Some(pipeline) => pipeline,
None => return,
};
// In this example, we are only interested about parsing the ToC, so

View file

@ -66,7 +66,7 @@ fn example_main() -> Result<(), Error> {
// Increase the queue capacity to 100MB to avoid a stalling pipeline
pipeline
.add_many([&src, &typefinder, &queue, &muxer, &sink])
.add_many(&[&src, &typefinder, &queue, &muxer, &sink])
.expect("failed to add elements to pipeline");
src.link(&typefinder)?;

View file

@ -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",
)

View file

@ -1,229 +0,0 @@
// Zoom example using navigation events and a compositor
// Use can change the video player zoom using the next keys:
// * +: Zoom in
// * -: Zoom out
// * Up/Down/Right/Left: Move the frame
// * r: reset the zoom
// Also mouse navigation events can be used for a better UX.
use gst::prelude::*;
use gst_video::video_event::NavigationEvent;
use std::sync::Mutex;
#[path = "../examples-common.rs"]
mod examples_common;
const WIDTH: i32 = 1280;
const HEIGHT: i32 = 720;
#[derive(Default)]
struct MouseState {
clicked: bool,
clicked_x: f64,
clicked_y: f64,
clicked_xpos: i32,
clicked_ypos: i32,
}
fn zoom(mixer_sink_pad: gst::Pad, x: i32, y: i32, zoom_in: bool) {
let xpos = mixer_sink_pad.property::<i32>("xpos");
let ypos = mixer_sink_pad.property::<i32>("ypos");
let width = mixer_sink_pad.property::<i32>("width");
let height = mixer_sink_pad.property::<i32>("height");
let (width_offset, height_offset) = if zoom_in {
(WIDTH / 10, HEIGHT / 10)
} else {
(-WIDTH / 10, -HEIGHT / 10)
};
if width_offset + width <= 0 {
return;
}
mixer_sink_pad.set_property("width", width + width_offset);
mixer_sink_pad.set_property("height", height + height_offset);
let xpos_offset = ((x as f32 / WIDTH as f32) * width_offset as f32) as i32;
let new_xpos = xpos - xpos_offset;
let ypos_offset = ((y as f32 / HEIGHT as f32) * height_offset as f32) as i32;
let new_ypos = ypos - ypos_offset;
if new_xpos != xpos {
mixer_sink_pad.set_property("xpos", new_xpos);
}
if new_ypos != ypos {
mixer_sink_pad.set_property("ypos", new_ypos);
}
}
fn reset_zoom(mixer_sink_pad: gst::Pad) {
let xpos = mixer_sink_pad.property::<i32>("xpos");
let ypos = mixer_sink_pad.property::<i32>("ypos");
let width = mixer_sink_pad.property::<i32>("width");
let height = mixer_sink_pad.property::<i32>("height");
if 0 != xpos {
mixer_sink_pad.set_property("xpos", 0);
}
if 0 != ypos {
mixer_sink_pad.set_property("ypos", 0);
}
if WIDTH != width {
mixer_sink_pad.set_property("width", WIDTH);
}
if HEIGHT != height {
mixer_sink_pad.set_property("height", HEIGHT);
}
}
fn example_main() {
let clicked = Mutex::new(MouseState::default());
gst::init().unwrap();
let pipeline = gst::parse::launch(&format!(
"compositor name=mix background=1 sink_0::xpos=0 sink_0::ypos=0 sink_0::zorder=0 sink_0::width={WIDTH} sink_0::height={HEIGHT} ! xvimagesink \
videotestsrc name=src ! video/x-raw,framerate=30/1,width={WIDTH},height={HEIGHT},pixel-aspect-ratio=1/1 ! queue ! mix.sink_0"
)).unwrap().downcast::<gst::Pipeline>().unwrap();
let mixer = pipeline.by_name("mix").unwrap();
let mixer_src_pad = mixer.static_pad("src").unwrap();
let mixer_sink_pad_weak = mixer.static_pad("sink_0").unwrap().downgrade();
// Probe added in the sink pad to get direct navigation events w/o transformation done by the mixer
mixer_src_pad.add_probe(gst::PadProbeType::EVENT_UPSTREAM, move |_, probe_info| {
let mixer_sink_pad = mixer_sink_pad_weak.upgrade().unwrap();
let Some(ev) = probe_info.event() else {
return gst::PadProbeReturn::Ok;
};
if ev.type_() != gst::EventType::Navigation {
return gst::PadProbeReturn::Ok;
};
let Ok(nav_event) = NavigationEvent::parse(ev) else {
return gst::PadProbeReturn::Ok;
};
match nav_event {
NavigationEvent::KeyPress { key, .. } => match key.as_str() {
"Left" => {
let xpos = mixer_sink_pad.property::<i32>("xpos");
mixer_sink_pad.set_property("xpos", xpos - 10);
}
"Right" => {
let xpos = mixer_sink_pad.property::<i32>("xpos");
mixer_sink_pad.set_property("xpos", xpos + 10);
}
"Up" => {
let ypos = mixer_sink_pad.property::<i32>("ypos");
mixer_sink_pad.set_property("ypos", ypos - 10);
}
"Down" => {
let ypos = mixer_sink_pad.property::<i32>("ypos");
mixer_sink_pad.set_property("ypos", ypos + 10);
}
"plus" => {
zoom(mixer_sink_pad, WIDTH / 2, HEIGHT / 2, true);
}
"minus" => {
zoom(mixer_sink_pad, WIDTH / 2, HEIGHT / 2, false);
}
"r" => {
reset_zoom(mixer_sink_pad);
}
_ => (),
},
NavigationEvent::MouseMove { x, y, .. } => {
let state = clicked.lock().unwrap();
if state.clicked {
let xpos = mixer_sink_pad.property::<i32>("xpos");
let ypos = mixer_sink_pad.property::<i32>("ypos");
let new_xpos = state.clicked_xpos + (x - state.clicked_x) as i32;
let new_ypos = state.clicked_ypos + (y - state.clicked_y) as i32;
if new_xpos != xpos {
mixer_sink_pad.set_property("xpos", new_xpos);
}
if new_ypos != ypos {
mixer_sink_pad.set_property("ypos", new_ypos);
}
}
}
NavigationEvent::MouseButtonPress { button, x, y, .. } => {
if button == 1 || button == 272 {
let mut state = clicked.lock().unwrap();
state.clicked = true;
state.clicked_x = x;
state.clicked_y = y;
state.clicked_xpos = mixer_sink_pad.property("xpos");
state.clicked_ypos = mixer_sink_pad.property("ypos");
} else if button == 2 || button == 3 || button == 274 || button == 273 {
reset_zoom(mixer_sink_pad);
} else if button == 4 {
zoom(mixer_sink_pad, x as i32, y as i32, true);
} else if button == 5 {
zoom(mixer_sink_pad, x as i32, y as i32, false);
}
}
NavigationEvent::MouseButtonRelease { button, .. } => {
if button == 1 || button == 272 {
let mut state = clicked.lock().unwrap();
state.clicked = false;
}
}
#[cfg(feature = "v1_18")]
NavigationEvent::MouseScroll { x, y, delta_y, .. } => {
if delta_y > 0.0 {
zoom(mixer_sink_pad, x as i32, y as i32, true);
} else if delta_y < 0.0 {
zoom(mixer_sink_pad, x as i32, y as i32, false);
}
}
_ => (),
}
gst::PadProbeReturn::Ok
});
pipeline
.set_state(gst::State::Playing)
.expect("Unable to set the pipeline to the `Playing` state");
let bus = pipeline.bus().unwrap();
for msg in bus.iter_timed(gst::ClockTime::NONE) {
use gst::MessageView;
match msg.view() {
MessageView::Eos(..) => {
println!("received eos");
break;
}
MessageView::Error(err) => {
println!(
"Error from {:?}: {} ({:?})",
err.src().map(|s| s.path_string()),
err.error(),
err.debug()
);
break;
}
_ => (),
};
}
pipeline
.set_state(gst::State::Null)
.expect("Unable to set the pipeline to the `Null` state");
}
fn main() {
// tutorials_common::run is only required to set up the application environment on macOS
// (but not necessary in normal Cocoa applications where this is set up automatically)
examples_common::run(example_main);
}

View file

@ -17,47 +17,13 @@ pub fn run<T, F: FnOnce() -> T + Send + 'static>(main: F) -> T
where
T: Send + 'static,
{
use std::{
ffi::c_void,
sync::mpsc::{channel, Sender},
thread,
};
use std::thread;
use cocoa::{
appkit::{NSApplication, NSWindow},
base::id,
delegate,
};
use objc::{
class, msg_send,
runtime::{Object, Sel},
sel, sel_impl,
};
use cocoa::appkit::NSApplication;
unsafe {
let app = cocoa::appkit::NSApp();
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 t = thread::spawn(|| {
let res = main();
let app = cocoa::appkit::NSApp();

View file

@ -1,30 +1,15 @@
//! 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>
// 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.
// {videotestsrc} - { glsinkbin }
use std::{
ffi::{CStr, CString},
mem,
num::NonZeroU32,
ptr,
};
use std::{ffi::CStr, mem, ptr, sync};
use anyhow::{Context, Result};
use anyhow::Error;
use derive_more::{Display, Error};
use glutin::{
config::GetGlConfig as _,
context::AsRawContext as _,
display::{AsRawDisplay as _, GetGlDisplay as _},
prelude::*,
};
use glutin_winit::GlWindow as _;
use gst::element_error;
use gst_gl::prelude::*;
use raw_window_handle::HasRawWindowHandle as _;
#[derive(Debug, Display, Error)]
#[display(fmt = "Received error from {src}: {error} (debug: {debug:?})")]
@ -186,7 +171,7 @@ impl Gl {
}
}
fn resize(&self, size: winit::dpi::PhysicalSize<u32>) {
fn resize(&self, size: glutin::dpi::PhysicalSize<u32>) {
unsafe {
self.gl
.Viewport(0, 0, size.width as i32, size.height as i32);
@ -194,17 +179,14 @@ impl Gl {
}
}
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()
});
fn load(gl_context: &glutin::WindowedContext<glutin::PossiblyCurrent>) -> Gl {
let gl = gl::Gl::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _);
let version = unsafe {
let version = gl.GetString(gl::VERSION);
assert!(!version.is_null());
let version = CStr::from_ptr(version.cast());
version.to_string_lossy()
let data = CStr::from_ptr(gl.GetString(gl::VERSION) as *const _)
.to_bytes()
.to_vec();
String::from_utf8(data).unwrap()
};
println!("OpenGL version {version}");
@ -224,10 +206,9 @@ fn load(gl_display: &impl glutin::display::GlDisplay) -> Gl {
gl.LinkProgram(program);
{
let mut success = 1;
gl.GetProgramiv(program, gl::LINK_STATUS, &mut success);
assert_ne!(success, 0);
assert_eq!(gl.GetError(), 0);
let mut success: gl::types::GLint = 1;
gl.GetProgramiv(fs, gl::LINK_STATUS, &mut success);
assert!(success != 0);
}
let attr_position = gl.GetAttribLocation(program, b"a_position\0".as_ptr() as *const _);
@ -298,8 +279,6 @@ fn load(gl_display: &impl glutin::display::GlDisplay) -> Gl {
gl.BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
gl.BindBuffer(gl::ARRAY_BUFFER, 0);
assert_eq!(gl.GetError(), 0);
(
program,
attr_position,
@ -331,199 +310,154 @@ pub(crate) struct App {
pipeline: gst::Pipeline,
appsink: gst_app::AppSink,
bus: gst::Bus,
event_loop: winit::event_loop::EventLoop<Message>,
window: Option<winit::window::Window>,
not_current_gl_context: Option<glutin::context::NotCurrentContext>,
event_loop: glutin::event_loop::EventLoop<Message>,
windowed_context: glutin::WindowedContext<glutin::PossiblyCurrent>,
shared_context: gst_gl::GLContext,
}
impl App {
pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result<App> {
pub(crate) fn new(gl_element: Option<&gst::Element>) -> Result<App, Error> {
gst::init()?;
let (pipeline, appsink) = App::create_pipeline(gl_element)?;
let bus = pipeline
.bus()
.context("Pipeline without bus. Shouldn't happen!")?;
.expect("Pipeline without bus. Shouldn't happen!");
let event_loop = winit::event_loop::EventLoopBuilder::with_user_event().build()?;
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)?;
// 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")
});
let windowed_context = unsafe { windowed_context.make_current().map_err(|(_, err)| err)? };
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);
#[cfg(any(feature = "gst-gl-x11", feature = "gst-gl-wayland"))]
let inner_window = windowed_context.window();
if prefer_transparency || new_config.num_samples() > current.num_samples() {
new_config
} else {
current
}
})
.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());
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};
// 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();
let api = App::map_gl_api(windowed_context.get_api());
println!("Using raw display connection {:?}", raw_gl_display);
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;
let raw_window_handle = window.as_ref().map(|window| window.raw_window_handle());
#[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>(),
)
};
// 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);
#[cfg(feature = "gst-gl-wayland")]
if let Some(display) = inner_window.wayland_display() {
gl_display = Some(
unsafe {
gst_gl_wayland::GLDisplayWayland::with_display(display as usize)
}
.unwrap()
.upcast::<gst_gl::GLDisplay>(),
)
};
(
egl_context as usize,
gl_display.expect("Could not retrieve GLDisplay through EGL context and/or Wayland display"),
gst_gl::GLPlatform::EGL,
)
}
#[cfg(feature = "gst-gl-x11")]
RawHandle::Glx(glx_context) => {
let gl_display = if let Some(display) = inner_window.xlib_display() {
unsafe { gst_gl_x11::GLDisplayX11::with_display(display as usize) }.unwrap()
} else {
panic!("X11 window without X Display");
};
(
glx_context as usize,
gl_display.upcast::<gst_gl::GLDisplay>(),
gst_gl::GLPlatform::GLX,
)
}
#[allow(unreachable_patterns)]
handler => panic!("Unsupported platform: {handler:?}."),
};
shared_context =
unsafe { gst_gl::GLContext::new_wrapped(&gl_display, gl_context, platform, api) }
.unwrap();
shared_context
.activate(true)
.expect("Couldn't activate wrapped GL context");
shared_context.fill_info()?;
let gl_context = shared_context.clone();
let event_proxy = sync::Mutex::new(event_loop.create_proxy());
#[allow(clippy::single_match)]
bus.set_sync_handler(move |_, msg| {
match msg.view() {
gst::MessageView::NeedContext(ctxt) => {
let context_type = ctxt.context_type();
if context_type == *gst_gl::GL_DISPLAY_CONTEXT_TYPE {
if let Some(el) =
msg.src().map(|s| s.downcast_ref::<gst::Element>().unwrap())
{
let context = gst::Context::new(context_type, true);
context.set_gl_display(&gl_display);
el.set_context(&context);
}
}
if context_type == "gst.gl.app_context" {
if let Some(el) =
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);
}
el.set_context(&context);
}
}
_ => (),
}
_ => (),
}
if let Err(e) = event_proxy.send_event(Message::BusEvent) {
eprintln!("Failed to send BusEvent to event proxy: {e}")
}
if let Err(e) = event_proxy.lock().unwrap().send_event(Message::BusEvent) {
eprintln!("Failed to send BusEvent to event proxy: {e}")
}
gst::BusSyncReply::Pass
});
gst::BusSyncReply::Pass
});
} else {
panic!("This example only has Linux support");
}
Ok(App {
pipeline,
appsink,
bus,
event_loop,
window,
not_current_gl_context: Some(not_current_gl_context),
windowed_context,
shared_context,
})
}
fn setup(&self, event_loop: &winit::event_loop::EventLoop<Message>) -> Result<()> {
fn setup(&self, event_loop: &glutin::event_loop::EventLoop<Message>) -> Result<(), Error> {
let event_proxy = event_loop.create_proxy();
self.appsink.set_callbacks(
gst_app::AppSinkCallbacks::builder()
@ -587,33 +521,22 @@ impl App {
.build(),
);
self.pipeline.set_state(gst::State::Playing)?;
Ok(())
}
/// 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 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(),
}
}
fn create_pipeline(
gl_element: Option<&gst::Element>,
) -> Result<(gst::Pipeline, gst_app::AppSink)> {
) -> Result<(gst::Pipeline, gst_app::AppSink), Error> {
let pipeline = gst::Pipeline::default();
let src = gst::ElementFactory::make("videotestsrc").build()?;
@ -632,7 +555,7 @@ impl App {
if let Some(gl_element) = gl_element {
let glupload = gst::ElementFactory::make("glupload").build()?;
pipeline.add_many([&src, &glupload])?;
pipeline.add_many(&[&src, &glupload])?;
pipeline.add(gl_element)?;
pipeline.add(&appsink)?;
@ -646,14 +569,14 @@ impl App {
.property("sink", &appsink)
.build()?;
pipeline.add_many([&src, &sink])?;
pipeline.add_many(&[&src, &sink])?;
src.link(&sink)?;
Ok((pipeline, appsink))
}
}
fn handle_messages(bus: &gst::Bus) -> Result<()> {
fn handle_messages(bus: &gst::Bus) -> Result<(), Error> {
use gst::MessageView;
for msg in bus.iter() {
@ -678,143 +601,77 @@ impl App {
}
}
pub(crate) fn main_loop(app: App) -> Result<()> {
pub(crate) fn main_loop(app: App) -> Result<(), Error> {
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,
mut window,
mut not_current_gl_context,
pipeline,
shared_context,
windowed_context,
..
} = app;
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);
event_loop.run(move |event, _, cf| {
*cf = glutin::event_loop::ControlFlow::Wait;
let mut needs_redraw = false;
match event {
winit::event::Event::LoopExiting => {
glutin::event::Event::LoopDestroyed => {
pipeline.send_event(gst::event::Eos::new());
pipeline.set_state(gst::State::Null).unwrap();
}
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),
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),
..
},
..
} => 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);
}
} => *cf = glutin::event_loop::ControlFlow::Exit,
glutin::event::WindowEvent::Resized(physical_size) => {
windowed_context.resize(physical_size);
gl.resize(physical_size);
}
winit::event::WindowEvent::RedrawRequested => needs_redraw = true,
_ => (),
},
glutin::event::Event::RedrawRequested(_) => needs_redraw = true,
// Receive a frame
winit::event::Event::UserEvent(Message::Frame(info, buffer)) => {
if let Ok(frame) = gst_gl::GLVideoFrame::from_buffer_readable(buffer, &info) {
glutin::event::Event::UserEvent(Message::Frame(info, buffer)) => {
if let Ok(frame) = gst_video::VideoFrame::from_buffer_readable_gl(buffer, &info) {
curr_frame = Some(frame);
needs_redraw = true;
}
}
// Handle all pending messages when we are awaken by set_sync_handler
winit::event::Event::UserEvent(Message::BusEvent) => {
glutin::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((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);
}
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);
}
gl_surface.swap_buffers(gl_context).unwrap();
}
windowed_context.swap_buffers().unwrap();
}
})?)
})
}

2
gir

@ -1 +1 @@
Subproject commit 5223ce91b97a833b09d6cbd04bbeab1bf18112b7
Subproject commit 425f84d5af7ff4e599b2528bb0e2f53657feb5cf

@ -1 +1 @@
Subproject commit 6cd7b656acd61172ab7f125a7059e4d0ecfc9637
Subproject commit 4eaad6a722bf41028ea9210d2c356b7078b25008

@ -1 +1 @@
Subproject commit c988e03b5e99349efb8ffb6f502879ad4ddbc248
Subproject commit ae0d1447f5204ddf882ff08d2163b299db5da228

View file

@ -1,24 +1,25 @@
[package]
name = "gstreamer-allocators"
version = "0.20.7"
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"]
version.workspace = true
categories.workspace = true
repository.workspace = true
homepage.workspace = true
edition.workspace = true
rust-version.workspace = true
edition = "2021"
rust-version = "1.64"
[dependencies]
libc = "0.2"
ffi = { package = "gstreamer-allocators-sys", path = "sys" }
glib.workspace = true
gst.workspace = true
once_cell = "1"
bitflags = "1.0"
ffi = { package = "gstreamer-allocators-sys", path = "sys", version = "0.20" }
glib = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.17", version = "0.17" }
gst = { package = "gstreamer", path = "../gstreamer", version = "0.20" }
once_cell = "1.0"
[dev-dependencies]
gir-format-check = "0.1"
@ -29,10 +30,7 @@ v1_16 = ["gst/v1_16", "ffi/v1_16"]
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"]
dox = ["ffi/dox", "glib/dox", "gst/dox"]
[package.metadata.docs.rs]
all-features = true
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
features = ["dox"]

View file

@ -52,15 +52,6 @@ status = "generate"
[[object.function]]
name = "phys_memory_get_phys_addr"
manual = true
[[object.function]]
name = "is_drm_dumb_memory"
manual = true
[[object.function]]
name = "drm_dumb_memory_get_handle"
manual = true
[[object.function]]
name = "drm_dumb_memory_export_dmabuf"
manual = true
[[object]]
name = "GstAllocators.DmaBufAllocator"
@ -73,36 +64,9 @@ cfg_condition = "target_os = \"linux\""
name = "alloc_with_flags"
manual = true
[[object]]
name = "GstAllocators.DRMDumbAllocator"
status = "generate"
cfg_condition = "target_os = \"linux\""
[[object.function]]
name = "alloc"
manual = true
[[object.function]]
name = "new_with_fd"
manual = true
[[object.function]]
name = "new_with_device_path"
[object.function.return]
nullable_return_is_error = "Failed to create allocator"
[[object]]
name = "GstAllocators.FdAllocator"
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

View file

@ -11,11 +11,6 @@ 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) };

View file

@ -1,60 +0,0 @@
// 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::{prelude::*, translate::*};
glib::wrapper! {
#[doc(alias = "GstDRMDumbAllocator")]
pub struct DRMDumbAllocator(Object<ffi::GstDRMDumbAllocator, ffi::GstDRMDumbAllocatorClass>) @extends gst::Allocator;
match fn {
type_ => || ffi::gst_drm_dumb_allocator_get_type(),
}
}
impl DRMDumbAllocator {
#[doc(alias = "gst_drm_dumb_allocator_new_with_device_path")]
#[doc(alias = "new_with_device_path")]
pub fn with_device_path(
drm_device_path: impl AsRef<std::path::Path>,
) -> Result<DRMDumbAllocator, glib::BoolError> {
assert_initialized_main_thread!();
unsafe {
Option::<gst::Allocator>::from_glib_full(
ffi::gst_drm_dumb_allocator_new_with_device_path(
drm_device_path.as_ref().to_glib_none().0,
),
)
.map(|o| o.unsafe_cast())
.ok_or_else(|| glib::bool_error!("Failed to create allocator"))
}
}
#[doc(alias = "gst_drm_dumb_allocator_has_prime_export")]
pub fn has_prime_export(&self) -> bool {
unsafe {
from_glib(ffi::gst_drm_dumb_allocator_has_prime_export(
self.to_glib_none().0,
))
}
}
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
#[doc(alias = "drm-device-path")]
pub fn drm_device_path(&self) -> Option<std::path::PathBuf> {
ObjectExt::property(self, "drm-device-path")
}
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
#[doc(alias = "drm-fd")]
pub fn drm_fd(&self) -> i32 {
ObjectExt::property(self, "drm-fd")
}
}
unsafe impl Send for DRMDumbAllocator {}
unsafe impl Sync for DRMDumbAllocator {}

View file

@ -3,10 +3,10 @@
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
// DO NOT EDIT
use glib::{bitflags::bitflags, translate::*};
use bitflags::bitflags;
use glib::translate::*;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[doc(alias = "GstFdMemoryFlags")]
pub struct FdMemoryFlags: u32 {
#[doc(alias = "GST_FD_MEMORY_FLAG_NONE")]

View file

@ -3,22 +3,11 @@
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
// DO NOT EDIT
#[cfg(target_os = "linux")]
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
mod drm_dumb_allocator;
#[cfg(target_os = "linux")]
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub use self::drm_dumb_allocator::DRMDumbAllocator;
#[cfg(target_os = "linux")]
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(any(target_os = "linux", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
mod dma_buf_allocator;
#[cfg(target_os = "linux")]
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(any(target_os = "linux", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
pub use self::dma_buf_allocator::DmaBufAllocator;
mod fd_allocator;
@ -27,30 +16,17 @@ 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(crate) mod functions;
pub 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;
pub(crate) mod traits {
#[doc(hidden)]
pub mod traits {
pub use super::phys_memory_allocator::PhysMemoryAllocatorExt;
}

View file

@ -21,11 +21,6 @@ impl PhysMemoryAllocator {
unsafe impl Send for PhysMemoryAllocator {}
unsafe impl Sync for PhysMemoryAllocator {}
mod sealed {
pub trait Sealed {}
impl<T: super::IsA<super::PhysMemoryAllocator>> Sealed for T {}
}
pub trait PhysMemoryAllocatorExt: IsA<PhysMemoryAllocator> + sealed::Sealed + 'static {}
pub trait PhysMemoryAllocatorExt: 'static {}
impl<O: IsA<PhysMemoryAllocator>> PhysMemoryAllocatorExt for O {}

View file

@ -1,20 +0,0 @@
// 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 {}

View file

@ -1,3 +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)
Generated by gir (https://github.com/gtk-rs/gir @ 425f84d5af7f)
from gir-files (https://github.com/gtk-rs/gir-files @ 4eaad6a722bf)
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ ae0d1447f520)

View file

@ -3,11 +3,11 @@ use std::{
os::unix::prelude::{IntoRawFd, RawFd},
};
use glib::{prelude::*, translate::*};
use glib::{translate::*, Cast};
use gst::{Memory, MemoryRef};
#[cfg(feature = "v1_16")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
#[cfg(any(feature = "v1_16", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))]
use crate::FdMemoryFlags;
use crate::{DmaBufAllocator, FdMemory, FdMemoryRef};
@ -58,8 +58,8 @@ impl DmaBufAllocator {
.ok_or_else(|| glib::bool_error!("Failed to allocate memory"))
}
#[cfg(feature = "v1_16")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
#[cfg(any(feature = "v1_16", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))]
#[doc(alias = "gst_dmabuf_allocator_alloc_with_flags")]
pub unsafe fn alloc_with_flags(
&self,

View file

@ -1,81 +0,0 @@
use std::{fmt, mem, os::unix::prelude::IntoRawFd};
use glib::{prelude::*, translate::*};
use gst::{Memory, MemoryRef};
use crate::{DRMDumbAllocator, DmaBufMemory};
gst::memory_object_wrapper!(
DRMDumbMemory,
DRMDumbMemoryRef,
gst::ffi::GstMemory,
|mem: &gst::MemoryRef| { unsafe { from_glib(ffi::gst_is_drm_dumb_memory(mem.as_mut_ptr())) } },
Memory,
MemoryRef
);
impl fmt::Debug for DRMDumbMemory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
DRMDumbMemoryRef::fmt(self, f)
}
}
impl fmt::Debug for DRMDumbMemoryRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
MemoryRef::fmt(self, f)
}
}
impl DRMDumbMemoryRef {
#[doc(alias = "gst_drm_dumb_memory_get_handle")]
pub fn fd(&self) -> u32 {
skip_assert_initialized!();
unsafe { ffi::gst_drm_dumb_memory_get_handle(self.as_mut_ptr()) }
}
#[doc(alias = "gst_drm_dumb_memory_export_dmabuf")]
pub fn export_dmabuf(&self) -> Result<DmaBufMemory, glib::BoolError> {
skip_assert_initialized!();
unsafe {
Option::<DmaBufMemory>::from_glib_full(ffi::gst_drm_dumb_memory_export_dmabuf(
self.as_mut_ptr(),
))
.ok_or_else(|| glib::bool_error!("Failed to export as dmabuf"))
}
}
}
impl DRMDumbAllocator {
#[doc(alias = "gst_drm_dumb_allocator_new_with_fd")]
#[doc(alias = "new_with_fd")]
pub fn with_fd<A: IntoRawFd>(drm_fd: A) -> Result<DRMDumbAllocator, glib::BoolError> {
assert_initialized_main_thread!();
unsafe {
Option::<gst::Allocator>::from_glib_full(ffi::gst_drm_dumb_allocator_new_with_fd(
drm_fd.into_raw_fd(),
))
.map(|o| o.unsafe_cast())
.ok_or_else(|| glib::bool_error!("Failed to create allocator"))
}
}
#[doc(alias = "gst_drm_dumb_allocator_alloc")]
pub unsafe fn alloc(
&self,
drm_fourcc: u32,
width: u32,
height: u32,
) -> Result<(gst::Memory, u32), glib::BoolError> {
skip_assert_initialized!();
let mut out_pitch = mem::MaybeUninit::uninit();
Option::<_>::from_glib_full(ffi::gst_drm_dumb_allocator_alloc(
self.to_glib_none().0,
drm_fourcc,
width,
height,
out_pitch.as_mut_ptr(),
))
.ok_or_else(|| glib::bool_error!("Failed to allocate memory"))
.map(|mem| (mem, unsafe { out_pitch.assume_init() }))
}
}

View file

@ -1,6 +1,6 @@
use std::{fmt, os::unix::prelude::RawFd};
use glib::{prelude::*, translate::*};
use glib::{translate::*, Cast};
use gst::{Memory, MemoryRef};
use crate::{FdAllocator, FdMemoryFlags};

View file

@ -1,6 +1,6 @@
// Take a look at the license at the top of the repository in the LICENSE file.
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(feature = "dox", feature(doc_cfg))]
#![allow(clippy::missing_safety_doc)]
#![doc = include_str!("../README.md")]
@ -30,24 +30,13 @@ pub use crate::caps_features::CAPS_FEATURES_MEMORY_DMABUF;
mod fd_allocator;
pub use fd_allocator::*;
#[cfg(any(target_os = "linux", docsrs))]
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(any(target_os = "linux", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
mod dma_buf_allocator;
#[cfg(any(target_os = "linux", docsrs))]
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(any(target_os = "linux", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
pub use dma_buf_allocator::*;
#[cfg(any(all(feature = "v1_24", target_os = "linux"), docsrs))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "v1_24", target_os = "linux"))))]
mod drm_dumb_allocator;
#[cfg(any(all(feature = "v1_24", target_os = "linux"), docsrs))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "v1_24", target_os = "linux"))))]
pub use drm_dumb_allocator::*;
#[cfg(any(all(feature = "v1_24", unix), docsrs))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "v1_24", unix))))]
mod shm_allocator;
mod phys_memory;
pub use phys_memory::*;
@ -56,8 +45,6 @@ pub use phys_memory::*;
pub mod prelude {
#[doc(hidden)]
pub use gst::prelude::*;
pub use crate::auto::traits::*;
}
pub mod subclass;

View file

@ -1,14 +0,0 @@
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())
}
}
}

View file

@ -1,5 +1,5 @@
#[cfg(any(target_os = "linux", docsrs))]
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(any(target_os = "linux", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
mod dma_buf_allocator;
mod fd_allocator;
@ -7,8 +7,8 @@ pub mod prelude {
#[doc(hidden)]
pub use gst::subclass::prelude::*;
#[cfg(any(target_os = "linux", docsrs))]
#[cfg_attr(docsrs, doc(cfg(target_os = "linux")))]
#[cfg(any(target_os = "linux", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(target_os = "linux")))]
pub use super::dma_buf_allocator::DmaBufAllocatorImpl;
pub use super::fd_allocator::FdAllocatorImpl;
}

View file

@ -4,26 +4,33 @@ system-deps = "6"
[dependencies]
libc = "0.2"
[dependencies.glib-sys]
workspace = true
[dependencies.glib]
git = "https://github.com/gtk-rs/gtk-rs-core"
branch = "0.17"
version = "0.17"
package = "glib-sys"
[dependencies.gobject-sys]
workspace = true
[dependencies.gobject]
git = "https://github.com/gtk-rs/gtk-rs-core"
branch = "0.17"
version = "0.17"
package = "gobject-sys"
[dependencies.gstreamer-sys]
workspace = true
[dependencies.gst]
package = "gstreamer-sys"
path = "../../gstreamer/sys"
version = "0.20"
[dev-dependencies]
shell-words = "1.0.0"
tempfile = "3"
[features]
dox = ["glib/dox", "gobject/dox", "gst/dox"]
v1_16 = []
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"
@ -33,33 +40,18 @@ 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"
[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
repository = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
rust-version = "1.64"
version = "0.20.7"
[package.metadata.docs.rs]
all-features = true
rustc-args = ["--cfg", "docsrs"]
rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"]
features = ["dox"]
[package.metadata.system-deps.gstreamer_allocators_1_0]
name = "gstreamer-allocators-1.0"
@ -76,9 +68,3 @@ version = "1.20"
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_22]
version = "1.22"
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_24]
version = "1.24"
[package.metadata.system-deps.gstreamer_allocators_1_0.v1_26]
version = "1.25"

View file

@ -7,7 +7,6 @@ work_mode = "sys"
single_version_file = true
extra_versions = [
"1.24",
"1.22",
"1.20",
"1.18",

View file

@ -3,13 +3,13 @@
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
// DO NOT EDIT
#[cfg(not(docsrs))]
#[cfg(not(feature = "dox"))]
use std::process;
#[cfg(docsrs)]
#[cfg(feature = "dox")]
fn main() {} // prevent linking libraries to avoid documentation failure
#[cfg(not(docsrs))]
#[cfg(not(feature = "dox"))]
fn main() {
if let Err(s) = system_deps::Config::new().probe() {
println!("cargo:warning={s}");

View file

@ -1,3 +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)
Generated by gir (https://github.com/gtk-rs/gir @ 425f84d5af7f)
from gir-files (https://github.com/gtk-rs/gir-files @ 4eaad6a722bf)
from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git @ ae0d1447f520)

View file

@ -10,20 +10,13 @@
clippy::unreadable_literal,
clippy::upper_case_acronyms
)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use glib_sys as glib;
use gobject_sys as gobject;
use gstreamer_sys as gst;
#![cfg_attr(feature = "dox", feature(doc_cfg))]
#[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, off_t, size_t, ssize_t, time_t, uintptr_t, FILE,
intptr_t, size_t, ssize_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};
@ -31,7 +24,6 @@ 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
@ -42,20 +34,6 @@ pub const GST_FD_MEMORY_FLAG_MAP_PRIVATE: GstFdMemoryFlags = 2;
pub const GST_FD_MEMORY_FLAG_DONT_CLOSE: GstFdMemoryFlags = 4;
// Records
#[derive(Copy, Clone)]
#[repr(C)]
pub struct GstDRMDumbAllocatorClass {
pub parent_class: gst::GstAllocatorClass,
}
impl ::std::fmt::Debug for GstDRMDumbAllocatorClass {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
f.debug_struct(&format!("GstDRMDumbAllocatorClass @ {self:p}"))
.field("parent_class", &self.parent_class)
.finish()
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct GstDmaBufAllocatorClass {
@ -101,34 +79,7 @@ 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 {
_data: [u8; 0],
_marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>,
}
impl ::std::fmt::Debug for GstDRMDumbAllocator {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
f.debug_struct(&format!("GstDRMDumbAllocator @ {self:p}"))
.finish()
}
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct GstDmaBufAllocator {
@ -158,19 +109,6 @@ 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 {
@ -187,34 +125,6 @@ impl ::std::fmt::Debug for GstPhysMemoryAllocator {
#[link(name = "gstallocators-1.0")]
extern "C" {
//=========================================================================
// GstDRMDumbAllocator
//=========================================================================
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub fn gst_drm_dumb_allocator_get_type() -> GType;
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub fn gst_drm_dumb_allocator_new_with_device_path(
drm_device_path: *const c_char,
) -> *mut gst::GstAllocator;
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub fn gst_drm_dumb_allocator_new_with_fd(drm_fd: c_int) -> *mut gst::GstAllocator;
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub fn gst_drm_dumb_allocator_alloc(
allocator: *mut GstDRMDumbAllocator,
drm_fourcc: u32,
width: u32,
height: u32,
out_pitch: *mut u32,
) -> *mut gst::GstMemory;
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub fn gst_drm_dumb_allocator_has_prime_export(allocator: *mut GstDRMDumbAllocator)
-> gboolean;
//=========================================================================
// GstDmaBufAllocator
//=========================================================================
@ -225,8 +135,8 @@ extern "C" {
fd: c_int,
size: size_t,
) -> *mut gst::GstMemory;
#[cfg(feature = "v1_16")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_16")))]
#[cfg(any(feature = "v1_16", feature = "dox"))]
#[cfg_attr(feature = "dox", doc(cfg(feature = "v1_16")))]
pub fn gst_dmabuf_allocator_alloc_with_flags(
allocator: *mut gst::GstAllocator,
fd: c_int,
@ -246,19 +156,6 @@ 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
//=========================================================================
@ -268,17 +165,8 @@ extern "C" {
// Other functions
//=========================================================================
pub fn gst_dmabuf_memory_get_fd(mem: *mut gst::GstMemory) -> c_int;
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub fn gst_drm_dumb_memory_export_dmabuf(mem: *mut gst::GstMemory) -> *mut gst::GstMemory;
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub fn gst_drm_dumb_memory_get_handle(mem: *mut gst::GstMemory) -> u32;
pub fn gst_fd_memory_get_fd(mem: *mut gst::GstMemory) -> c_int;
pub fn gst_is_dmabuf_memory(mem: *mut gst::GstMemory) -> gboolean;
#[cfg(feature = "v1_24")]
#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
pub fn gst_is_drm_dumb_memory(mem: *mut gst::GstMemory) -> gboolean;
pub fn gst_is_fd_memory(mem: *mut gst::GstMemory) -> gboolean;
pub fn gst_is_phys_memory(mem: *mut gst::GstMemory) -> gboolean;
pub fn gst_phys_memory_get_phys_addr(mem: *mut gst::GstMemory) -> uintptr_t;

View file

@ -3,7 +3,7 @@
// from gst-gir-files (https://gitlab.freedesktop.org/gstreamer/gir-files-rs.git)
// DO NOT EDIT
#![cfg(unix)]
#![cfg(target_os = "linux")]
use gstreamer_allocators_sys::*;
use std::env;
@ -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, Stdio};
use std::process::Command;
use std::str;
use tempfile::Builder;
@ -71,11 +71,9 @@ 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() {
let (status, stdout) = (out.status, String::from_utf8_lossy(&out.stdout));
return Err(format!("command {cmd:?} failed, {status:?}\nstdout: {stdout}").into());
return Err(format!("command {cmd:?} returned {}", out.status).into());
}
let stdout = str::from_utf8(&out.stdout)?;
Ok(shell_words::split(stdout.trim())?)
@ -190,25 +188,16 @@ 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 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());
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());
}
Ok(String::from_utf8(out.stdout)?)
Ok(String::from_utf8(output.stdout)?)
}
const RUST_LAYOUTS: &[(&str, Layout)] = &[
(
"GstDRMDumbAllocatorClass",
Layout {
size: size_of::<GstDRMDumbAllocatorClass>(),
alignment: align_of::<GstDRMDumbAllocatorClass>(),
},
),
(
"GstDmaBufAllocator",
Layout {
@ -251,19 +240,11 @@ 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"),

View file

@ -31,7 +31,6 @@
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);

View file

@ -8,13 +8,11 @@
#include <stdio.h>
int main() {
printf("%s;%zu;%zu\n", "GstDRMDumbAllocatorClass", sizeof(GstDRMDumbAllocatorClass), alignof(GstDRMDumbAllocatorClass));
printf("%s;%zu;%zu\n", "GstDmaBufAllocator", sizeof(GstDmaBufAllocator), alignof(GstDmaBufAllocator));
printf("%s;%zu;%zu\n", "GstDmaBufAllocatorClass", sizeof(GstDmaBufAllocatorClass), alignof(GstDmaBufAllocatorClass));
printf("%s;%zu;%zu\n", "GstFdAllocator", sizeof(GstFdAllocator), alignof(GstFdAllocator));
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;
}

View file

@ -1 +0,0 @@
../gstreamer/CHANGELOG.md

View file

@ -1 +0,0 @@
../COPYRIGHT

View file

@ -1,32 +0,0 @@
[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"]

View file

@ -1,34 +0,0 @@
[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"

View file

@ -1 +0,0 @@
../LICENSE-APACHE

Some files were not shown because too many files have changed in this diff Show more