Compare commits

...

151 commits

Author SHA1 Message Date
Sebastian Dröge
5bba2f7836 meson: Update version to 0.11.3 too 2024-01-04 13:05:23 +02:00
Sebastian Dröge
46b84c1564 Update CHANGELOG.md to 0.11.3 2023-12-18 12:22:18 +02:00
Sebastian Dröge
4c8db5623f Update versions to 0.11.3 2023-12-18 12:13:48 +02:00
Sebastian Dröge
aed165ee17 Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:46:42 +02:00
Sebastian Dröge
ed5b090318 gtk4: Use async-channel instead of the glib MainContext channel
The latter will be removed in favour of using async code in the future,
and async code generally allows for more flexible message handling than the
callback based MainContext channel.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:46:42 +02:00
Arun Raghavan
af4cf79b37 threadshare: Fix a deadlock in used-socket notification
This manifests in a gst-launch-1.0 pipeline using ts-udpsrc, since
notification of used-socket results in the property being read by the
application, and the settings lock causes a deadlock.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:07:29 +02:00
Arun Raghavan
12ceb178ed threadshare: Fix a typo while logging
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:07:24 +02:00
Sebastian Dröge
814735e537 webrtc: Downgrade aws-smithy-http to 0.60
Version 0.61 was yanked from crates.io.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:07:17 +02:00
Sebastian Dröge
25e8638a09 deny: Update
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:07:12 +02:00
Sebastian Dröge
fd64d5259a webrtc: Update to aws-smithy-http 0.61
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:07:04 +02:00
Sebastian Dröge
975a3a53af rtp: Update to bitstream-io 2.0
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:06:50 +02:00
Sebastian Dröge
c655edd892 Update to async-tungstenite 0.24
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:06:17 +02:00
Sebastian Dröge
c0499c7dd1 Update further AWS SDK crates to 1.0
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:05:12 +02:00
Mathieu Duponchelle
824ae39848 webrtcsink: don't panic on failure to request pad from webrtcbin
webrtcbin will refuse pad requests for all sorts of reasons, and should
be logging an error when doing so, simply post an error message and let
the application deal with it, the reason for the refusal should
hopefully be available in the logs to the user.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:05:06 +02:00
Matthew Waters
fc02f5abd4 tttocea608: use crate defined is_* functions instead of reeimplementing them
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:04:56 +02:00
Sebastian Dröge
a788bf07f4 Update to AWS SDK 1.0 / 0.60 / 0.39
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:04:48 +02:00
Sebastian Dröge
5d52cd4e4c deny: Update for duplicated crypto-bigint dependency
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:04:43 +02:00
Sebastian Dröge
43db2c24bb Update to AWS SDK 0.101 / 0.59 / 0.38
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:04:36 +02:00
Sebastian Dröge
c7d91ce28a aws: Stop using deprecated aws_config function in the test
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:04:31 +02:00
Sebastian Dröge
eb7dd5b14b deny: Add duplicated windows crates version
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:04:18 +02:00
Sebastian Dröge
d02211988d Update to latest AWS SDK
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:04:11 +02:00
Sebastian Dröge
2a4e2abf56 gtk4: Update to windows-sys 0.52
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:01:15 +02:00
Sebastian Dröge
efda10bd8f deny: Remove unnecessary tracing-log duplicate and add itertools
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:00:28 +02:00
Sebastian Dröge
4643853ca5 Update to AWS SDK 0.36
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:00:21 +02:00
Seungha Yang
4fd3b287e8 fallbacksrc: Fix timeout scheduling
Other thread can schedule the timeout (e.g., unblock signal
or active pad change) while state lock is released

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 10:00:15 +02:00
Sebastian Dröge
2274c62db7 uriplaylistbin: Fix new clippy warning
warning: the borrowed expression implements the required traits
    --> utils/uriplaylistbin/src/uriplaylistbin/imp.rs:1691:32
     |
1691 |         self.obj().remove_many(&children_ref).unwrap();
     |                                ^^^^^^^^^^^^^ help: change this to: `children_ref`

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 09:52:20 +02:00
Sebastian Dröge
022e9ce651 ndi: Don't mark private type as public
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 09:52:10 +02:00
Sebastian Dröge
9db4c45e47 ndi: Remove wrong Clone impl on RecvInstance
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1410>
2023-12-18 09:52:03 +02:00
Sebastian Dröge
ab093e4218 Update Cargo.lock 2023-11-11 20:52:45 +02:00
Sebastian Dröge
151702d264 Update versions to 0.11.2 2023-11-11 20:52:45 +02:00
Sebastian Dröge
4cc95c4327 Update CHANGELOG.md for 0.11.2 2023-11-11 20:52:45 +02:00
Sebastian Dröge
07934b0f87 Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:05:59 +02:00
Sebastian Dröge
4cfa48b4ef deny: Add override for duplicated toml_edit dependency
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:05:26 +02:00
Maksym Khomenko
7fcf6d6faf webrtcsrc: add turn-servers property
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:05:18 +02:00
Mathieu Duponchelle
6747b11cb9 Port to AWS SDK 0.57/0.35
Co-authored-by: Sebastian Dröge <sebastian@centricular.com>
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:05:12 +02:00
Sebastian Dröge
aa491e0d54 aws: Update to test-with 0.12
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:05:06 +02:00
Sebastian Dröge
ca11b32fab sccparse: Fix leading spaces between the tab and caption data
CCExtractor is creating files like this.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:05:01 +02:00
Sebastian Dröge
2bec8a8eaa Set sync=false in rsfilesink / s3sink
BaseSink defaults to sync=true and that doesn't make much sense for
these elements.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:04:46 +02:00
Sebastian Dröge
1ceaf8b179 Use let-else instead of match for weak reference upgrades
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:04:38 +02:00
Sebastian Dröge
b6b7511591 deny: Update duplicated dependencies
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:52 +02:00
Sebastian Dröge
e501ee0340 Update to AWS SDK 0.34 and tracing-log 0.2
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:46 +02:00
Jan Alexander Steffens (heftig)
b4855f1ba8 livesync: Remove the stop from outgoing segments
Our buffer duplication can extend a segment indefinitely.

Fixes: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/452
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:42 +02:00
Jan Alexander Steffens (heftig)
d65504aeb8 livesync: Keep existing buffer duration in some cases
Resize a repeat buffer only if caps gave us a duration to use, or we
consider its current duration unreasonable.

In particular, for audio streams we should prefer reusing the buffer
size upstream gave us, as we did before 6633cc4046.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:36 +02:00
Jan Alexander Steffens (heftig)
48020dfbf5 livesync: Split fallback_duration into in_ and out_duration
Make it independent of the `latency`; this was inconsistent anyway,
where the default latency of zero got you a fallback duration of 100 ms
and something else got you half the latency.

Maintain a separate duration for the `in` and the `out` side so we
change the duration of repeat buffers after a caps change, not just
before.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:31 +02:00
Guillaume Desmottes
352d947655 livesync: display jitter when waiting on clock
We already log the result of the clock wait call so may as well log the
returned jitter.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:27 +02:00
Guillaume Desmottes
559825a551 livesync: log new pending segments
The debug print of the event does not display details about the segment:
  Unqueueing Some(Event(Event { ptr: 0x7fa3e0002580, type: "segment", seqnum: Seqnum(479), structure: Some(GstEventSegment { segment: (GstSegment) ((GstSegment*) 0x7fa3e8001d00) }) }))

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:22 +02:00
Jan Alexander Steffens (heftig)
34ce94932f livesync: example: Add identities single-segment=1
These let us change the runtime offset of the test buffers via pad
offsets without pushing new segments into livesync, which is necessary
to demo the late-threshold behavior.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:17 +02:00
Jan Alexander Steffens (heftig)
7c2f05c271 livesync: Use fallback_duration for audio repeat buffers as well
Don't depend on upstream giving us sanely-sized buffers if we want to
repeat.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:12 +02:00
Jan Alexander Steffens (heftig)
0b80e3aa47 livesync: Separate out_buffer duplicate status from GAP flag
Otherwise we might get confused by upstream GAP buffers.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:07 +02:00
Jan Alexander Steffens (heftig)
ccf1171776 livesync: Handle flags and late buffer patching after queueing
This makes the chain function almost independent of the output state. We
still do the early discard check with `buffer_is_backwards` so we don't
try to queue buffers we can't use, allowing us to fast-forward upstream
without blocking on the src task.

Don't accept `LateOverThreshold` buffers when we have `pending_caps` or
a `pending_segment`. We need to apply these first before we can sensibly
patch buffers from the new stream.

Deduplicate most of the output buffer patching code into a new
`patch_output_buffer` method.

For: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/450
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:02:00 +02:00
Jan Alexander Steffens (heftig)
8deb2d26a8 livesync: Simplify num_duplicate counting
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:55 +02:00
Jan Alexander Steffens (heftig)
3a7bf71483 livesync: Move num_in counting to the src task
This is in preparation for moving more accept/discard logic to the src
task, so we can only count `num_in` here.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:49 +02:00
Jan Alexander Steffens (heftig)
9ad3c42e21 livesync: Move a notify closer to the interesting state change
Move the `notify_all` to where we pop the buffer. We're moving within a
single state lock so no change in behavior.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:41 +02:00
Jan Alexander Steffens (heftig)
80ba5b980d livesync: Replace an if-let with match
No change in behavior, yet. Separate commit to ease reviewing.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:37 +02:00
Jan Alexander Steffens (heftig)
391b244b9e livesync: Clean up state handling
- Separate resetting state more cleanly, introducing `set_flushing`,
  `sink_reset` and `src_reset`.
- Clear the queue early when we flush, in order to unblock waits on
  query responses.
- Return an error when we fail to start, pause or stop the task.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:32 +02:00
Jan Alexander Steffens (heftig)
04b7a1afd6 livesync: Log a category error when we are missing the segment
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:27 +02:00
Jan Alexander Steffens (heftig)
79f58785d9 livesync: Improve audio duration fixups
- An entirely missing duration is now only logged at debug level instead
  of pretending the duration was zero and warning about it.
- Silently fix up a duration difference up to one sample.
- Error when we fail to calculate the duration; don't try to apply the
  `fallback_duration` to a non-video stream.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:22 +02:00
Jan Alexander Steffens (heftig)
913fb606fc livesync: Simplify start_src_task and src_loop
This should effect no change in behavior.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:16 +02:00
Jan Alexander Steffens (heftig)
90a22950d2 livesync: Rename activatemode methods to *_activatemode
This matches the other plugins.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:01:11 +02:00
Arun Raghavan
54bc4012d2 hlssink3: Close the playlist giostreamsink on stop if possible
This is a property that will be available from GStreamer 1.24, and will
ensure that we are able to flush the playlist during the READY->NULL
transition instead of when the element is freed.

Fixes: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/423
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 16:00:31 +02:00
Seungha Yang
4bb82748b9 hlssink3: Various cleanup
* Simplify state/playlist management
* Fix a bug that segment is not deleted if location contains directory
and playlist-root is unset
* Split playlist update routine into two steps, adding segment
to playlist and playlist write

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:59:17 +02:00
Seungha Yang
9b229eb456 hlssink3: Don't remove old files if max-files is zero
Follow hlssink2 element's behavior

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:59:13 +02:00
Seungha Yang
c41e4c0304 hlssink3: Remove unused deps
gstreamer-base dep is unused. And use gst::glib

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:59:04 +02:00
Seungha Yang
1e5f499b3f hlssink3: Use Path API for getting file name
Current implementation does not support Windows path separator.
Use Path API instead.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:58:42 +02:00
Seungha Yang
d5e6ca3e5a hlssink3: Use sprintf for segment name formatting
The zero-padded naming requirement is unnecessary. Use simple
sprintf instead

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:58:37 +02:00
Arun Raghavan
cf4d7c5431 hlssink3: Minor PDT-related naming fixups
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:58:32 +02:00
rajneeshksoni
12e26cef8b hlssink3: Add property track-pipeline-clock-for-pdt.
This is required to take care of clock skew between
system time and pipeline time.
`track-pipeline-clock-for-pdt: true` mean utd time is
sampled for first segment and for subsequent segments
keep adding the time based on pipeline clock. difference
of segment duration and PDT time will match.
track-pipeline-clock-for-pdt: false` mean utd time is
sampled for each segment. system time may jump forward
or backward based on adjustments. If application needs
to synchronization of external events `false` is
recommended.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:58:27 +02:00
rajneeshksoni
4971084f63 hlssink3: Allow adding EXT-X-PROGRAM-DATE-TIME tag.
- connect to `format-location-full` it provide the first
sample of the fragment. preserve the running-time of the
first sample in fragment.
- on fragment-close message, find the mapping of running-time
to UTC time.
- on each subsequent fragment, calculate the offset of the
running-time with first fragment and add offset to base
utc time

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:58:22 +02:00
Arun Raghavan
12418a3f52 aws: s3: Properly percent-decode GstS3Url
We previously only percent-decoded the first fragment. This doesn't
necessarily harm anything, but for consistency we keep the structure
un-encoded, and encode when converting to a string representation.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:56:11 +02:00
Arun Raghavan
dd05c0d51a aws: s3sink: Fix handling of special characters in key
Properly URL-encode the string if needed, and add some tests for a
couple of cases.

Fixes: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/431
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:56:06 +02:00
Sebastian Dröge
5160b6c30f rtpav1depay: Don't push stale temporal delimiters downstream
Only push them downstream once a complete OBU was assembled.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:55:58 +02:00
Sebastian Dröge
3d3dc19fc2 rtpav1depay: Skip unexpected leading fragments
If a packet is starting with a leading fragment but we do not expect to
receive one, then skip over it to the next OBU.

Not doing so would cause parsing of the middle of an OBU, which would
most likely fail and cause unnecessary warning messages about a
corrupted stream.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:55:51 +02:00
Sebastian Dröge
d234c89e23 deny: Remove unnecessary ignore entry
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:55:46 +02:00
Sebastian Dröge
af729699b8 Update to quick-xml 0.31
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:55:38 +02:00
Nirbheek Chauhan
095258aaf1 ci: Make meson warnings fatal
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:55:13 +02:00
Nirbheek Chauhan
1c3166c0c6 meson: Bump requirement to 1.1
WARNING: Project specifies a minimum meson_version '>= 0.60' but uses features which were added in newer versions:
* 1.1.0: {'feature_option.enable_if()'}

Caused by https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1363

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:55:06 +02:00
Nirbheek Chauhan
4e7b3e35a3 meson: Enable the RTP option when WebRTC is enabled
And make the webrtc option yielding, see:

https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5505

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:54:39 +02:00
Nirbheek Chauhan
cc174bd382 .gitignore: Ignore the meson subproject wrap hash
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:54:33 +02:00
Olivier Crête
b245afbd2d Revert "deny: Temporarily allow a duplicated tungstenite dependency"
LiveKit has now been fixed.

This reverts commit 23e1bfa720.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:54:27 +02:00
Sebastian Dröge
bf48e76b59 webrtc: Update to livekit 0.2
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:54:20 +02:00
Sebastian Dröge
4a53938b75 deny: Update for duplicated redox_syscall dependency
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:54:15 +02:00
Sebastian Dröge
880f4229d0 Clean up usage of pad probes
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:52:18 +02:00
François Laignel
9fd9f0eb83 net/webrtcsrc: define signaller property as CONSTRUCT_ONLY
The "signaller" property used to be defined as MUTABLE_READY which meant that
the property was always set after `constructed()` was called.

Since `connect_signaller()` was called from `constructed()`, only the default
signaller was used.

This commit sets the "signaller" property as CONSTRUCT_ONLY. Using a builder,
this property will now be set before the call to `constructed()`.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:51:32 +02:00
François Laignel
ff4b1cb757 net/webrtcsink: drop State lock before calling set-local-description
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:51:15 +02:00
François Laignel
6dc624ac2f net/webrtcsink: don't miss ice candidates
During `on_remote_description_set()` processing, current session is removed
from the sessions `HashMap`. If an ice candidate is submitted to `handle_ice()`
by that time, the session can't be found and the candidate is ignored.

This commit wraps the Session in the sessions `HashMap` so an entry is kept
while `on_remote_description_set()` is running. Incoming candidates received by
`handle_ice()` will be processed immediately or enqueued and handled when the
session is restored by `on_remote_description_set()`.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:51:10 +02:00
Sebastian Dröge
f5d6d703c5 aws: Update to test-with 0.11
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:51:03 +02:00
Lieven Paulissen
56095c5434 ndisrc: Assume input with more than 8 raw audio channels is unpositioned
gst_audio_channel_positions_from_mask() will otherwise print warnings
all the time.

Fixes #444

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:50:59 +02:00
Maksym Khomenko
2c923128dc webrtcsrc: use @watch instead of @to-owned
@to-owned increases refcount of the element, which prevents the object from proper destruction, as the initial refcount with ElementFactory::make is larger than 1.

Instead, use @watch to create a weak reference and unbind the closure automatically if the object gets destroyed

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:50:53 +02:00
Sebastian Dröge
36273c0eae Update to AWS SDK 0.33
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:50:47 +02:00
Taruntej Kanakamalla
7bfb86c6cc net/webrtc/whip_signaller: Use the correct URL during redirect
Copy of 90e06dc3 for whipclientsink

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:50:42 +02:00
Maksym Khomenko
aabbe49dc3 webrtcsink: README: add documentation for custom signaller
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:50:36 +02:00
Maksym Khomenko
569dfadcab webrtcsink: add custom signaller example
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:50:29 +02:00
Sebastian Dröge
107c610bb0 Update to AWS SDK 0.32
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1388>
2023-11-10 15:50:23 +02:00
Philippe Normand
f853fa3bc7 audiornnoise: Attach audio level meta to output buffers
This is useful downstream for processing of audio voice payloads, for
instance feeding a speech recognition library such as Whisper.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1353>
2023-10-09 12:07:45 +03:00
Sebastian Dröge
b78269285b ci: Don't run cargo update
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1353>
2023-10-09 12:07:05 +03:00
Sebastian Dröge
e5ccfdbd55 Update plugins versions in Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1353>
2023-10-09 12:06:23 +03:00
Sebastian Dröge
a2e0853583 Update CHANGELOG.md for 0.11.1 2023-10-04 23:55:55 +03:00
Sebastian Dröge
2b8728103f Update versions to 0.11.1 2023-10-04 23:35:35 +03:00
Sebastian Dröge
b9e8f4cbb3 ndi: Comment out empty Opus handling and add FIXME comment
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:30:49 +03:00
Sebastian Dröge
dce1e3b044 Update plugin documentation cache
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:30:23 +03:00
Sebastian Dröge
4eeff19075 Fix indentation
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:19:23 +03:00
Sebastian Dröge
2afa8862a0 Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:16:57 +03:00
Sebastian Dröge
b360b5247e Update gst-plugin-version-helper version requirement to 0.8
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:15:53 +03:00
Sebastian Dröge
9bf24a715b deny: Simplify license handling
Deny all copyleft licenses except for the MPL-2.0 and add an exception
for gst-plugin-threadshare to allow LGPL-2.1.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:10:24 +03:00
Sebastian Dröge
471901e4ca deny: Remove unnecessary toml_edit exception
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:10:19 +03:00
Sebastian Dröge
5c59ba51d5 ci: Run cargo-deny on the whole workspace with all features enabled
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:10:13 +03:00
Stéphane Cerveau
1e22caf6c3 fmp4mux: specify the fragment duration unit
The fragment duration is expressed in nanoseconds.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:10:08 +03:00
Sebastian Dröge
29697dae45 Fix various new 1.73 clippy warnings
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:10:02 +03:00
Sebastian Dröge
b89adc88f8 threadshare: Fix docs typos
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:09:57 +03:00
François Laignel
206c3964c8 generic: threadshare: macOS fixes
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:09:43 +03:00
Sebastian Dröge
2ea53dd3a4 deny: Update with some new overrides
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:09:35 +03:00
François Laignel
ffb7ea1885 generic: threadshare: port to polling 3.1.0
Also use `rustix` & `std::ffi` instead of `libc`.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:09:11 +03:00
Andoni Morales Alastruey
fcd16bc070 threadshare: fix warning for unused variable
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:08:37 +03:00
Piotr Brzeziński
e792fdbe48 webrtc: Fix paths in README
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:07:14 +03:00
Sebastian Dröge
cd8ab38c09 version-helper: Update version to 0.8.0 and MSRV to 1.66
Previous release was 0.7.5 and 1.63, but toml_edit unfortunately
requires Rust 1.66 at least.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:06:56 +03:00
Sebastian Dröge
d384999b0c version-helper: Also try parsing release date from Cargo.toml
The `package.metadata.gstreamer.release_date` date string can be used to
specify the release date.

This is used if there's no git repository as a fallback before using the
mtime of the Cargo.toml. Using the mtime will fail when building a crate
packaged by cargo because cargo sets the mtimes to bogus values.

Fixes https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/440

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:06:25 +03:00
Sean DuBois
4a1a7864cf net: webrtc/webrtchttp: Respect HTTP redirects
Properly follow redirect URL. Before new request would be made, but with
original URL again.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:06:03 +03:00
Sebastian Dröge
389ab2e46c deny: Remove obsolete entries
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:04:27 +03:00
Sebastian Dröge
a168456428 Update to AWS SDK 0.31
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1350>
2023-10-04 19:04:21 +03:00
Sebastian Dröge
f0f06c4569 Update Cargo.lock
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-25 22:53:30 +03:00
Sebastian Dröge
427f1ec912 ci: Use the 0.21 branch of the gstreamer-rs images template
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-25 22:53:30 +03:00
Sebastian Dröge
a5bcc9099b gtk4: Only support RGBA/RGB in the GL code path
For all other component orderings a shader is necessary to re-order the
components for what GTK expects.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:58:54 +03:00
Ivan Molodetskikh
432782d09a gtk4: Premultiply alpha in GL textures
GTK expects GL textures to have premultiplied alpha. The ones we get
from GStreamer don't, leading to incorrect rendering of semitransparent
frames.

GTK 4.12 gained an API to set a different GL texture format, but it
won't help for older GTK versions. Plus, at the time of writing, it
causes a very slow download/upload path in GTK.

So, use a GTK GL shader node to premultiply the alpha without leaving
the GPU.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:58:49 +03:00
Sebastian Dröge
9187d0e11e onvifmetadataparse: Skip metadata frames with unrepresentable UTC time
Previously we would panic, which causes the element to post an error
message. Instead, simply skip metadata frames if their UTC time since
the UNIX epoch can't be represented as nanoseconds in u64.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:58:43 +03:00
Seungha Yang
468f73092d webrtcsink: Propagate GstContext messages
Implement CustomBusStream so that NEED_CONTEXT and HAVE_CONTEXT
messages from session/discovery can be forwarded to parent
pipeline and also GstContext can be shared.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:58:13 +03:00
Seungha Yang
2b1d07a757 webrtcsink: Add support for d3d11 memory and qsvh264enc
Adding d3d11 memory and qsvh264enc support

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:58:06 +03:00
Mathieu Duponchelle
33156b52c8 webrtcsink: fix codec selection discoveries
Since ab1ec12698:

webrtcsink: Add support for pre encoded streams

Discovery pipelines for remote offers were no longer fed any buffers.

While some encoders could already produce caps with no input buffers,
others, such as x264enc, simply hung forever. This resulted in no answer
getting produced if for instance video-caps were constrained to H264.

Fix this by tracking discovery pipelines at the State rather than the
InputStream level, removing the useless distinction of Initial vs.
CodecSelection discoveries, and always feeding all the current
discovery pipelines with incoming buffers.

For reference, the issue here was that codec selection discoveries were
assigned to local clones of InputStreams, not tracked anywhere, and thus
not iterated for discoveries when queuing incoming buffers from the
chain function, as it only looked at the original instance of
InputStream's in state.streams.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:57:57 +03:00
Robert Ayrapetyan
391ba3f5f8 webrtcsink: fix TWCC extension adding
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:57:40 +03:00
Sebastian Dröge
d607d0ac93 fmp4mux: Update to dash-mpd 0.14
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:57:34 +03:00
Jakub Adam
dcfa4e6352 tutorial/2: update the text to match the latest source code
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:57:27 +03:00
Jakub Adam
5e55623c6e tutorial/1: fix TOC
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:57:15 +03:00
Sebastian Dröge
dc1e45eaa1 Update to AWS SDK 0.30
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:57:00 +03:00
robert
3eb9e0c88d meson: Fix handling of optional deps
We were requiring the presence of all optional dependencies, such as
gstreamer-check-1.0 and gstreamer-gl-1.0, on the system, regardless of
whether the user actually requires these functionalities.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:52 +03:00
L. E. Segovia
f8b07ddcc1 meson: Tell cargo to prefer static libraries
This fixes most, but not all, of the build errors in Windows when using
static libraries.

The ones remaining are:

- redirection of gstreamer-1.0 towards gstreamer-full-1.0
- Cairo not exporting the C++ stdlib requirement when built statically

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:47 +03:00
Jakub Adam
0645b138b9 tutorial/1: update the text to match the latest source code
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:42 +03:00
L. E. Segovia
6c5c4205a4 meson: Disable plugins and related outputs if features are disabled
Previously, there was no check performed on features of plugins if these
specify GStreamer plugins. This commit adds that, and ensures that the
plugins and pkg-config targets are skipped if no outputs are to be
generated (this is already done for examples).

Closes #369

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:35 +03:00
L. E. Segovia
2c34193590 meson: Disable plugins and related outputs if features are disabled
Previously, there was no check performed on features of plugins if these
specify GStreamer plugins. This commit adds that, and ensures that the
plugins and pkg-config targets are skipped if no outputs are to be
generated (this is already done for examples).

Closes #369

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:30 +03:00
L. E. Segovia
746ce65e8f meson: Allow usage of externally overridden pkg-config
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:25 +03:00
Sebastian Dröge
131a933b00 threadshare: Update to flume 0.11
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:19 +03:00
Guillaume Desmottes
dd117366c5 fallbackswitch: protect src pad stream lock using Cond
Should prevent stream and State deadlocks, see https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/202

Fix #202
Hopefully fix #192 as well.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:14 +03:00
Guillaume Desmottes
a11388453a fallbackswitch: prevent deadlocks in chain function
Calling schedule_timeout() may result in handle_timeout() being called right away,
which will need pad state locks which was still hold in chain().

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:09 +03:00
Guillaume Desmottes
acc9fcd8c8 fallbackswitch: ensure strict ordering when taking mutexes
Should prevent deadlocks.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1331>
2023-09-20 19:56:03 +03:00
Sebastian Dröge
de0363efad meson: Check for correct minimum cargo-c version
Fixes https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/404
2023-08-14 11:08:19 +03:00
Sebastian Dröge
16d58caf2e Add Cargo.lock
Fixes https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/402
2023-08-11 08:14:37 +03:00
Sebastian Dröge
47f0520c20 meson: Update version to 0.11.0 2023-08-10 17:19:19 +03:00
Sebastian Dröge
383d8e2668 Add version to gst-plugin-gtk4 dependencies 2023-08-10 17:12:16 +03:00
Sebastian Dröge
f35603a020 webrtc: Add versions to gst_plugin_webrtc_protocol dependency 2023-08-10 16:57:11 +03:00
Sebastian Dröge
982b5d6ef9 Add version to the gst-plugin-version-helper dependency 2023-08-10 16:56:04 +03:00
Sebastian Dröge
b421054152 Add CHANGELOG.md for 0.11.0 2023-08-10 16:45:51 +03:00
Sebastian Dröge
67c3732b94 Update dependencies to release branches of gtk-rs / gtk4-rs / gstreamer-rs 2023-08-09 18:14:18 +03:00
Sebastian Dröge
9b1853c1de Update versions to 0.11.0 2023-08-09 18:04:38 +03:00
125 changed files with 10289 additions and 2218 deletions

3
.gitignore vendored
View file

@ -4,4 +4,5 @@ target
*.bk
*.swp
.vscode
builddir
builddir
.meson-subproject-wrap-hash.txt

View file

@ -6,7 +6,7 @@ include:
file: '/templates/debian.yml'
- project: 'gstreamer/gstreamer-rs'
ref: main
ref: '0.21'
file: '/ci/images_template.yml'
- project: 'gstreamer/gstreamer'
@ -73,13 +73,6 @@ trigger:
before_script:
- source ./ci/env.sh
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
# If cargo exists assume we probably will want to update
# the lockfile
- |
if command -v cargo; then
cargo generate-lockfile
cargo update
fi
.debian:12-stable:
extends: .debian:12
@ -140,7 +133,7 @@ meson shared:
variables:
CI_ARTIFACTS_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/raw/"
script:
- meson build --default-library=shared --prefix=$(pwd)/install
- meson build --default-library=shared --prefix=$(pwd)/install --fatal-meson-warnings
- ninja -C build install
- ./ci/check-installed.py install
- ninja -C build docs/gst_plugins_cache.json
@ -344,7 +337,7 @@ deny:
rules:
- if: '$CI_PIPELINE_SOURCE == "schedule"'
script:
- cargo deny check
- cargo deny --color=always --workspace --all-features check all
outdated:
extends: '.debian:12-stable'

View file

@ -5,6 +5,94 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html),
specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-version-field).
## [0.11.3] - 2023-12-18
### Fixed
- ndi: Mark a private type as such and remove a wrong `Clone` impl of internal types.
- uriplaylistbin: Fix a minor clippy warning.
- fallbacksrc: Fix error during badly timed timeout scheduling.
- webrtcsink: Fail gracefully if webrtcbin pads can't be requested instead of
panicking.
- threadshare: Fix deadlock in `ts-udpsrc` `notify::used-socket` signal
emission.
### Changed
- Update to AWS SDK 1.0.
- Update to windows-sys 0.52.
- Update to async-tungstenite 0.24.
- Update to bitstream-io 2.0.
- tttocea608: De-duplicate some functions.
- gtk4: Use async-channel instead of deprecated GLib main context channel.
## [0.11.2] - 2023-11-11
### Fixed
- filesink / s3sink: Set `sync=false` to allow processing faster than
real-time.
- hlssink3: Various minor bugfixes and cleanups.
- livesync: Various minor bugfixes and cleanups that should make the element
work more reliable.
- s3sink: Fix handling of non-ASCII characters in URIs and keys.
- sccparse: Parse SCC files that are incorrectly created by CCExtractor.
- ndisrc: Assume > 8 channels are unpositioned.
- rtpav1depay: Skip unexpected leading fragments instead of repeatedly warning
about the stream possibly being corrupted.
- rtpav1depay: Don't push stale temporal delimiters downstream but wait until
a complete OBU is collected.
- whipwebrtcsink: Use correct URL during redirects.
- webrtcsink: Make sure to not miss any ICE candidates.
- webrtcsink: Fix deadlock when calling `set-local-description`.
- webrtcsrc: Fix reference cycles that prevented the element from being freed.
- webrtcsrc: Define signaller property as `CONSTRUCT_ONLY` to make it actually
possible to set different signallers.
- webrtc: Update livekit signaller to livekit 0.2.
- meson: Various fixes to the meson-based build system.
### Added
- audiornnoise: Attach audio level meta to output buffers.
- hlssink3: Allow adding `EXT-X-PROGRAM-DATE-TIME` tag to the manifest.
- webrtcsrc: Add `turn-servers` property.
### Changed
- aws/webrtc: Update to AWS SDK 0.57/0.35.
## [0.11.1] - 2023-10-04
### Fixed
- fallbackswitch: Fix various deadlocks.
- webrtcsink: Gracefully fail if adding the TWCC RTP header extension fails.
- webrtcsink: Fix codec selection discovery.
- webrtcsink: Add support for D3D11 memory and qsvh264enc.
- onvifmetadataparse: Skip metadata frames with unrepresentable UTC times.
- gtk4paintablesink: Pre-multiply alpha when creating GL textures with alpha.
- gtk4paintablesink: Only support RGBA/RGB in the GL code path.
- webrtchttp: Respect HTTP redirects.
- fmp4mux: Specify unit of fragment-duration property.
### Changed
- threadshare: Port to polling 3.1.
## [0.11.0] - 2023-08-10
### Changed
- Updated MSRV to 1.70.
- Compatible with gtk-rs 0.18 and gstreamer-rs 0.21.
- awstranscriber: Move to HTTP2-based API via the aws-sdk-transcribestreaming
crate instead of our own implementation around the WebSocket API.
### Added
- webrtcsink: Add AWS KVS signaller and corresponding aws-kvs-webrtcsink
element.
- awstranscriber / transcriberbin: Add support for translations and outputting
transcriptions from a single audio stream in multiple languages at once.
- gstwebrtc-api: JavaScript API for interacting with the default signalling
protocol used by webrtcsink / webrtcsrc.
- cea608to708: New element for converting CEA608 to CEA708 closed captions.
- webrtcsink: Expose the signaller as property and allow implementing a
custom signaller by connecting signal handlers to the default signaller.
- webrtcsink: Add support for pre-encoded streams.
- togglerecord: Add support for non-live input streams.
- webrtcsink: New whipwebrtcsink that implements WHIP around webrtcsink.
The existing whipsink still exists but will sooner or later be deprecated.
- webrtcsink: Add LiveKit signaller and corresponding livekitwebrtcsink
element.
## [0.10.11] - 2023-07-20
### Fixed
- fallbackswitch: Fix pad health calculation and notifies.
@ -213,8 +301,12 @@ specifically the [variant used by Rust](http://doc.crates.io/manifest.html#the-v
- webrtcsink: Make the `turn-server` property a `turn-servers` list
- webrtcsink: Move from async-std to tokio
[Unreleased]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.11...HEAD
[0.10.11]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.9...0.10.11
[Unreleased]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.3...HEAD
[0.11.3]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.2...0.11.3
[0.11.2]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.1...0.11.2
[0.11.1]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.11.0...0.11.1
[0.11.0]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.11...0.11.0
[0.10.11]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.10...0.10.11
[0.10.10]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.9...0.10.10
[0.10.9]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.8...0.10.9
[0.10.8]: https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/compare/0.10.7...0.10.8

6821
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-audiofx"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,9 +9,9 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
anyhow = "1"
byte-slice-cast = "1.0"
num-traits = "0.2"
@ -28,11 +28,11 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[dev-dependencies]
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -129,10 +129,13 @@ impl AudioRNNoise {
buffer.set_duration(duration);
buffer.set_pts(pts);
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
let (level, has_voice) = {
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
self.process(state, &settings, in_data, out_data)
};
self.process(state, &settings, in_data, out_data);
gst_audio::AudioLevelMeta::add(buffer, level, has_voice);
}
self.obj().src_pad().push(buffer)
@ -160,10 +163,13 @@ impl AudioRNNoise {
buffer.set_duration(duration);
buffer.set_pts(pts);
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
let (level, has_voice) = {
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
self.process(state, &settings, in_data, out_data)
};
self.process(state, &settings, in_data, out_data);
gst_audio::AudioLevelMeta::add(buffer, level, has_voice);
}
Ok(GenerateOutputSuccess::Buffer(buffer))
@ -175,9 +181,10 @@ impl AudioRNNoise {
settings: &Settings,
input_plane: &[f32],
output_plane: &mut [f32],
) {
) -> (u8, bool) {
let channels = state.in_info.channels() as usize;
let size = FRAME_SIZE * channels;
let mut has_voice = false;
for (out_frame, in_frame) in output_plane.chunks_mut(size).zip(input_plane.chunks(size)) {
for (index, item) in in_frame.iter().enumerate() {
@ -207,11 +214,15 @@ impl AudioRNNoise {
);
}
gst::debug!(CAT, imp: self, "Voice activity: {}", vad);
gst::trace!(CAT, imp: self, "Voice activity: {}", vad);
if vad < settings.vad_threshold {
out_frame.fill(0.0);
} else {
// Upon voice activity nnoiseless never really reports a 1.0
// VAD, so we use a hardcoded value close to 1.0 here.
if vad >= 0.98 {
has_voice = true;
}
for (index, item) in out_frame.iter_mut().enumerate() {
let channel_index = index % channels;
let channel_denoiser = &state.denoisers[channel_index];
@ -220,6 +231,19 @@ impl AudioRNNoise {
}
}
}
let rms = output_plane.iter().copied().map(|x| x * x).sum::<f32>();
let level = (20.0 * f32::log10(rms + f32::EPSILON)) as u8;
gst::trace!(
CAT,
imp: self,
"rms: {}, level: {}, has_voice : {} ", rms,
level,
has_voice
);
(level, has_voice)
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-claxon"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Ruben Gonzalez <rgonzalez@fluendo.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MIT OR Apache-2.0"
@ -9,15 +9,15 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
claxon = { version = "0.4" }
byte-slice-cast = "1.0"
atomic_refcell = "0.1"
once_cell = "1"
[dev-dependencies]
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gstclaxon"
@ -25,7 +25,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-csound"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Natanael Mojica <neithanmo@gmail.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,14 +9,14 @@ rust-version = "1.70"
description = "GStreamer Audio Filter plugin based on Csound"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
csound = "0.1.8"
byte-slice-cast = "1.0"
[dev-dependencies]
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gstcsound"
@ -28,7 +28,7 @@ name = "csound-effect"
path = "examples/effect_example.rs"
[build-dependencies]
gst-plugin-version-helper = { path = "../../version-helper" }
gst-plugin-version-helper = { path = "../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-lewton"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MIT OR Apache-2.0"
@ -9,14 +9,14 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
lewton = { version = "0.10", default-features = false }
byte-slice-cast = "1.0"
atomic_refcell = "0.1"
[dev-dependencies]
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gstlewton"
@ -24,7 +24,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-spotify"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Guillaume Desmottes <guillaume@desmottes.be>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,8 +9,8 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
librespot = { version = "0.4", default-features = false }
tokio = "1.0"
futures = "0.3"
@ -23,7 +23,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

118
deny.toml
View file

@ -10,8 +10,6 @@ ignore = [
"RUSTSEC-2021-0060",
"RUSTSEC-2021-0061",
"RUSTSEC-2021-0145",
# https://github.com/chronotope/chrono/issues/499
"RUSTSEC-2020-0071",
# sodiumoxide is deprecated
"RUSTSEC-2021-0137",
]
@ -19,16 +17,10 @@ ignore = [
[licenses]
unlicensed = "deny"
allow = [
"Apache-2.0",
"MPL-2.0",
]
deny = [
"GPL-1.0",
"GPL-2.0",
"GPL-3.0",
"AGPL-1.0",
"AGPL-3.0",
]
copyleft = "allow"
default = "deny"
copyleft = "deny"
allow-osi-fsf-free = "either"
confidence-threshold = 0.8
@ -40,22 +32,22 @@ license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 }
]
# Allow AGPL3 from dssim-core, which is optionally used in gst-plugin-videofx
[[licenses.exceptions]]
allow = ["AGPL-3.0"]
name = "dssim-core"
version = "3.2"
# Allow LGPL 2.1 for the threadshare plugin as it includes some LGPL code
[[licenses.exceptions]]
allow = ["LGPL-2.1"]
name = "gst-plugin-threadshare"
[bans]
multiple-versions = "deny"
highlight = "all"
wildcards = "allow"
# ignore duplicated deps because of chrono, cookie, cookie_store, hyper,
# hyperx, reqwest depending on old time
# https://github.com/chronotope/chrono/issues/400
# https://github.com/pfernie/cookie_store/issues/11
# https://github.com/hyperium/hyper/pull/2139
# https://github.com/dekellum/hyperx/issues/21
# https://github.com/seanmonstar/reqwest/issues/934
[[bans.skip]]
name = "time"
version = "0.1"
# ignore duplicated crc dependency because ffv1 depends on an old version
# https://github.com/rust-av/ffv1/issues/21
[[bans.skip]]
@ -79,11 +71,6 @@ version = "0.9"
name = "hmac"
version = "0.11"
# ignore duplicated wasi dependency because various crates depends on an old version
[[bans.skip]]
name = "wasi"
version = "0.10"
# ignore duplicated spin dependency because various crates depend on an old version
[[bans.skip]]
name = "spin"
@ -133,11 +120,6 @@ version = "1.0"
name = "toml"
version = "0.5"
# Various crates depend on an older version of redox_syscall
[[bans.skip]]
name = "redox_syscall"
version = "0.2"
# tracing-subscriber depends on an older version of regex-syntax
[[bans.skip]]
name = "regex-syntax"
@ -174,11 +156,77 @@ version = "0.3"
name = "regex-automata"
version = "0.1"
# old livekit-api depends on an old version of tokio-tungstenite/tungstenite
# https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1293
# aws-config and jsonwebtoken depend on an old version of ring
[[bans.skip]]
name = "ring"
version = "0.16"
[[bans.skip]]
name = "untrusted"
version = "0.7"
# proc-macro-crate depends on an older version of toml_edit
# https://github.com/bkchr/proc-macro-crate/pull/41
[[bans.skip]]
name = "toml_edit"
version = "0.20"
# dssim-core depends on an older version of itertools
[[bans.skip]]
name = "itertools"
version = "0.11"
# Various crates depend on old versions of the windows crates
[[bans.skip]]
name = "windows_x86_64_msvc"
version = "0.48"
[[bans.skip]]
name = "windows_x86_64_gnullvm"
version = "0.48"
[[bans.skip]]
name = "windows_x86_64_gnu"
version = "0.48"
[[bans.skip]]
name = "windows_i686_msvc"
version = "0.48"
[[bans.skip]]
name = "windows_i686_gnu"
version = "0.48"
[[bans.skip]]
name = "windows_aarch64_msvc"
version = "0.48"
[[bans.skip]]
name = "windows_aarch64_gnullvm"
version = "0.48"
[[bans.skip]]
name = "windows-targets"
version = "0.48"
[[bans.skip]]
name = "windows-sys"
version = "0.48"
# Various crates depend on an older version of crypto-bigint
[[bans.skip]]
name = "crypto-bigint"
version = "0.4"
# livekit-api depends on an older version of tokio-tungstenite
[[bans.skip]]
name = "tokio-tungstenite"
version = "0.20"
[[bans.skip]]
name = "tungstenite"
version = "0.19"
version = "0.20"
# Various crates depend on an older version of http
[[bans.skip]]
name = "http"
version = "0.2"
# rav1e depends on an older version of bitstream-io
# https://github.com/xiph/rav1e/pull/3301
[[bans.skip]]
name = "bitstream-io"
version = "1.0"
[sources]
unknown-registry = "deny"

View file

@ -1888,7 +1888,7 @@
"writable": true
},
"fragment-duration": {
"blurb": "Duration for each FMP4 fragment",
"blurb": "Duration for each FMP4 fragment in nanoseconds",
"conditionally-available": false,
"construct": false,
"construct-only": false,
@ -2125,7 +2125,7 @@
"long-name": "GTK 4 Paintable Sink",
"pad-templates": {
"sink": {
"caps": "video/x-raw:\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n\nvideo/x-raw(memory:GLMemory, meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n texture-target: 2D\n\nvideo/x-raw(memory:GLMemory):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n texture-target: 2D\n\nvideo/x-raw(memory:SystemMemory, meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n\nvideo/x-raw(meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"caps": "video/x-raw:\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n\nvideo/x-raw(memory:GLMemory, meta:GstVideoOverlayComposition):\n format: { RGBA, RGB }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n texture-target: 2D\n\nvideo/x-raw(memory:GLMemory):\n format: { RGBA, RGB }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n texture-target: 2D\n\nvideo/x-raw(memory:SystemMemory, meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n\nvideo/x-raw(meta:GstVideoOverlayComposition):\n format: { BGRA, ARGB, RGBA, ABGR, RGB, BGR }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"direction": "sink",
"presence": "always"
}
@ -2186,6 +2186,18 @@
}
},
"properties": {
"enable-program-date-time": {
"blurb": "put EXT-X-PROGRAM-DATE-TIME tag in the playlist",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "false",
"mutable": "null",
"readable": true,
"type": "gboolean",
"writable": true
},
"i-frames-only": {
"blurb": "Each video segments is single iframe, So put EXT-X-I-FRAMES-ONLY tag in the playlist",
"conditionally-available": false,
@ -2224,6 +2236,18 @@
"type": "guint",
"writable": true
},
"pdt-follows-pipeline-clock": {
"blurb": "As there might be drift between the wallclock and pipeline clock, this controls whether the Program-Date-Time markers should follow the pipeline clock rate (true), or be skewed to match the wallclock rate (false).",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "true",
"mutable": "null",
"readable": true,
"type": "gboolean",
"writable": true
},
"playlist-length": {
"blurb": "Length of HLS playlist. To allow players to conform to section 6.3.3 of the HLS specification, this should be at least 3. If set to 0, the playlist will be infinite.",
"conditionally-available": false,
@ -4558,12 +4582,12 @@
"long-name": "Cea 608 overlay",
"pad-templates": {
"sink": {
"caps": "video/x-raw:\n format: { ABGR64_LE, BGRA64_LE, AYUV64, ARGB64_LE, ARGB64, RGBA64_LE, ABGR64_BE, BGRA64_BE, ARGB64_BE, RGBA64_BE, GBRA_12LE, GBRA_12BE, Y412_LE, Y412_BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, RGB10A2_LE, BGR10A2_LE, Y410, GBRA, ABGR, VUYA, BGRA, AYUV, ARGB, RGBA, A420, AV12, Y444_16LE, Y444_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, v210, UYVP, I420_10LE, I420_10BE, P010_10LE, NV12_10LE32, NV12_10LE40, P010_10BE, NV12_10LE40_4L4, NV12_10BE_8L128, Y444, RGBP, GBR, BGRP, NV24, xBGR, BGRx, xRGB, RGBx, BGR, IYU2, v308, RGB, Y42B, NV61, NV16, VYUY, UYVY, YVYU, YUY2, I420, YV12, NV21, NV12, NV12_8L128, NV12_64Z32, NV12_4L4, NV12_32L32, NV12_16L32S, Y41B, IYU1, YVU9, YUV9, RGB16, BGR16, RGB15, BGR15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"caps": "video/x-raw:\n format: { A444_16LE, A444_16BE, AYUV64, RGBA64_LE, ARGB64, ARGB64_LE, BGRA64_LE, ABGR64_LE, RGBA64_BE, ARGB64_BE, BGRA64_BE, ABGR64_BE, A422_16LE, A422_16BE, A420_16LE, A420_16BE, A444_12LE, GBRA_12LE, A444_12BE, GBRA_12BE, Y412_LE, Y412_BE, A422_12LE, A422_12BE, A420_12LE, A420_12BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, BGR10A2_LE, RGB10A2_LE, Y410, A444, GBRA, AYUV, VUYA, RGBA, ARGB, BGRA, ABGR, A422, A420, AV12, Y444_16LE, GBR_16LE, Y444_16BE, GBR_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, UYVP, v210, I420_10LE, I420_10BE, P010_10LE, NV12_10LE40, NV12_10LE32, P010_10BE, MT2110R, MT2110T, NV12_10BE_8L128, NV12_10LE40_4L4, Y444, BGRP, GBR, RGBP, NV24, v308, IYU2, RGBx, xRGB, BGRx, xBGR, RGB, BGR, Y42B, NV16, NV61, YUY2, YVYU, UYVY, VYUY, I420, YV12, NV12, NV21, NV12_16L32S, NV12_32L32, NV12_4L4, NV12_64Z32, NV12_8L128, Y41B, IYU1, YUV9, YVU9, BGR16, RGB16, BGR15, RGB15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"direction": "sink",
"presence": "always"
},
"src": {
"caps": "video/x-raw:\n format: { ABGR64_LE, BGRA64_LE, AYUV64, ARGB64_LE, ARGB64, RGBA64_LE, ABGR64_BE, BGRA64_BE, ARGB64_BE, RGBA64_BE, GBRA_12LE, GBRA_12BE, Y412_LE, Y412_BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, RGB10A2_LE, BGR10A2_LE, Y410, GBRA, ABGR, VUYA, BGRA, AYUV, ARGB, RGBA, A420, AV12, Y444_16LE, Y444_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, v210, UYVP, I420_10LE, I420_10BE, P010_10LE, NV12_10LE32, NV12_10LE40, P010_10BE, NV12_10LE40_4L4, NV12_10BE_8L128, Y444, RGBP, GBR, BGRP, NV24, xBGR, BGRx, xRGB, RGBx, BGR, IYU2, v308, RGB, Y42B, NV61, NV16, VYUY, UYVY, YVYU, YUY2, I420, YV12, NV21, NV12, NV12_8L128, NV12_64Z32, NV12_4L4, NV12_32L32, NV12_16L32S, Y41B, IYU1, YVU9, YUV9, RGB16, BGR16, RGB15, BGR15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"caps": "video/x-raw:\n format: { A444_16LE, A444_16BE, AYUV64, RGBA64_LE, ARGB64, ARGB64_LE, BGRA64_LE, ABGR64_LE, RGBA64_BE, ARGB64_BE, BGRA64_BE, ABGR64_BE, A422_16LE, A422_16BE, A420_16LE, A420_16BE, A444_12LE, GBRA_12LE, A444_12BE, GBRA_12BE, Y412_LE, Y412_BE, A422_12LE, A422_12BE, A420_12LE, A420_12BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, BGR10A2_LE, RGB10A2_LE, Y410, A444, GBRA, AYUV, VUYA, RGBA, ARGB, BGRA, ABGR, A422, A420, AV12, Y444_16LE, GBR_16LE, Y444_16BE, GBR_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, UYVP, v210, I420_10LE, I420_10BE, P010_10LE, NV12_10LE40, NV12_10LE32, P010_10BE, MT2110R, MT2110T, NV12_10BE_8L128, NV12_10LE40_4L4, Y444, BGRP, GBR, RGBP, NV24, v308, IYU2, RGBx, xRGB, BGRx, xBGR, RGB, BGR, Y42B, NV16, NV61, YUY2, YVYU, UYVY, VYUY, I420, YV12, NV12, NV21, NV12_16L32S, NV12_32L32, NV12_4L4, NV12_64Z32, NV12_8L128, Y41B, IYU1, YUV9, YVU9, BGR16, RGB16, BGR15, RGB15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"direction": "src",
"presence": "always"
}
@ -5420,12 +5444,12 @@
"long-name": "ONVIF Metadata overlay",
"pad-templates": {
"sink": {
"caps": "video/x-raw:\n format: { ABGR64_LE, BGRA64_LE, AYUV64, ARGB64_LE, ARGB64, RGBA64_LE, ABGR64_BE, BGRA64_BE, ARGB64_BE, RGBA64_BE, GBRA_12LE, GBRA_12BE, Y412_LE, Y412_BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, RGB10A2_LE, BGR10A2_LE, Y410, GBRA, ABGR, VUYA, BGRA, AYUV, ARGB, RGBA, A420, AV12, Y444_16LE, Y444_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, v210, UYVP, I420_10LE, I420_10BE, P010_10LE, NV12_10LE32, NV12_10LE40, P010_10BE, NV12_10LE40_4L4, NV12_10BE_8L128, Y444, RGBP, GBR, BGRP, NV24, xBGR, BGRx, xRGB, RGBx, BGR, IYU2, v308, RGB, Y42B, NV61, NV16, VYUY, UYVY, YVYU, YUY2, I420, YV12, NV21, NV12, NV12_8L128, NV12_64Z32, NV12_4L4, NV12_32L32, NV12_16L32S, Y41B, IYU1, YVU9, YUV9, RGB16, BGR16, RGB15, BGR15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"caps": "video/x-raw:\n format: { A444_16LE, A444_16BE, AYUV64, RGBA64_LE, ARGB64, ARGB64_LE, BGRA64_LE, ABGR64_LE, RGBA64_BE, ARGB64_BE, BGRA64_BE, ABGR64_BE, A422_16LE, A422_16BE, A420_16LE, A420_16BE, A444_12LE, GBRA_12LE, A444_12BE, GBRA_12BE, Y412_LE, Y412_BE, A422_12LE, A422_12BE, A420_12LE, A420_12BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, BGR10A2_LE, RGB10A2_LE, Y410, A444, GBRA, AYUV, VUYA, RGBA, ARGB, BGRA, ABGR, A422, A420, AV12, Y444_16LE, GBR_16LE, Y444_16BE, GBR_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, UYVP, v210, I420_10LE, I420_10BE, P010_10LE, NV12_10LE40, NV12_10LE32, P010_10BE, MT2110R, MT2110T, NV12_10BE_8L128, NV12_10LE40_4L4, Y444, BGRP, GBR, RGBP, NV24, v308, IYU2, RGBx, xRGB, BGRx, xBGR, RGB, BGR, Y42B, NV16, NV61, YUY2, YVYU, UYVY, VYUY, I420, YV12, NV12, NV21, NV12_16L32S, NV12_32L32, NV12_4L4, NV12_64Z32, NV12_8L128, Y41B, IYU1, YUV9, YVU9, BGR16, RGB16, BGR15, RGB15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"direction": "sink",
"presence": "always"
},
"src": {
"caps": "video/x-raw:\n format: { ABGR64_LE, BGRA64_LE, AYUV64, ARGB64_LE, ARGB64, RGBA64_LE, ABGR64_BE, BGRA64_BE, ARGB64_BE, RGBA64_BE, GBRA_12LE, GBRA_12BE, Y412_LE, Y412_BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, RGB10A2_LE, BGR10A2_LE, Y410, GBRA, ABGR, VUYA, BGRA, AYUV, ARGB, RGBA, A420, AV12, Y444_16LE, Y444_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, v210, UYVP, I420_10LE, I420_10BE, P010_10LE, NV12_10LE32, NV12_10LE40, P010_10BE, NV12_10LE40_4L4, NV12_10BE_8L128, Y444, RGBP, GBR, BGRP, NV24, xBGR, BGRx, xRGB, RGBx, BGR, IYU2, v308, RGB, Y42B, NV61, NV16, VYUY, UYVY, YVYU, YUY2, I420, YV12, NV21, NV12, NV12_8L128, NV12_64Z32, NV12_4L4, NV12_32L32, NV12_16L32S, Y41B, IYU1, YVU9, YUV9, RGB16, BGR16, RGB15, BGR15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"caps": "video/x-raw:\n format: { A444_16LE, A444_16BE, AYUV64, RGBA64_LE, ARGB64, ARGB64_LE, BGRA64_LE, ABGR64_LE, RGBA64_BE, ARGB64_BE, BGRA64_BE, ABGR64_BE, A422_16LE, A422_16BE, A420_16LE, A420_16BE, A444_12LE, GBRA_12LE, A444_12BE, GBRA_12BE, Y412_LE, Y412_BE, A422_12LE, A422_12BE, A420_12LE, A420_12BE, A444_10LE, GBRA_10LE, A444_10BE, GBRA_10BE, A422_10LE, A422_10BE, A420_10LE, A420_10BE, BGR10A2_LE, RGB10A2_LE, Y410, A444, GBRA, AYUV, VUYA, RGBA, ARGB, BGRA, ABGR, A422, A420, AV12, Y444_16LE, GBR_16LE, Y444_16BE, GBR_16BE, v216, P016_LE, P016_BE, Y444_12LE, GBR_12LE, Y444_12BE, GBR_12BE, I422_12LE, I422_12BE, Y212_LE, Y212_BE, I420_12LE, I420_12BE, P012_LE, P012_BE, Y444_10LE, GBR_10LE, Y444_10BE, GBR_10BE, r210, I422_10LE, I422_10BE, NV16_10LE32, Y210, UYVP, v210, I420_10LE, I420_10BE, P010_10LE, NV12_10LE40, NV12_10LE32, P010_10BE, MT2110R, MT2110T, NV12_10BE_8L128, NV12_10LE40_4L4, Y444, BGRP, GBR, RGBP, NV24, v308, IYU2, RGBx, xRGB, BGRx, xBGR, RGB, BGR, Y42B, NV16, NV61, YUY2, YVYU, UYVY, VYUY, I420, YV12, NV12, NV21, NV12_16L32S, NV12_32L32, NV12_4L4, NV12_64Z32, NV12_8L128, Y41B, IYU1, YUV9, YVU9, BGR16, RGB16, BGR15, RGB15, RGB8P, GRAY16_LE, GRAY16_BE, GRAY10_LE32, GRAY8 }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n framerate: [ 0/1, 2147483647/1 ]\n",
"direction": "src",
"presence": "always"
}
@ -6111,7 +6135,7 @@
"presence": "request"
},
"video_%%u": {
"caps": "video/x-raw:\n\nvideo/x-raw(memory:CUDAMemory):\n\nvideo/x-raw(memory:GLMemory):\n\nvideo/x-raw(memory:NVMM):\nvideo/x-vp8:\nvideo/x-h264:\nvideo/x-vp9:\nvideo/x-h265:\n",
"caps": "video/x-raw:\n\nvideo/x-raw(memory:CUDAMemory):\n\nvideo/x-raw(memory:GLMemory):\n\nvideo/x-raw(memory:NVMM):\n\nvideo/x-raw(memory:D3D11Memory):\nvideo/x-vp8:\nvideo/x-h264:\nvideo/x-vp9:\nvideo/x-h265:\n",
"direction": "sink",
"presence": "request"
}
@ -6142,7 +6166,7 @@
"presence": "request"
},
"video_%%u": {
"caps": "video/x-raw:\n\nvideo/x-raw(memory:CUDAMemory):\n\nvideo/x-raw(memory:GLMemory):\n\nvideo/x-raw(memory:NVMM):\nvideo/x-vp8:\nvideo/x-h264:\nvideo/x-vp9:\nvideo/x-h265:\n",
"caps": "video/x-raw:\n\nvideo/x-raw(memory:CUDAMemory):\n\nvideo/x-raw(memory:GLMemory):\n\nvideo/x-raw(memory:NVMM):\n\nvideo/x-raw(memory:D3D11Memory):\nvideo/x-vp8:\nvideo/x-h264:\nvideo/x-vp9:\nvideo/x-h265:\n",
"direction": "sink",
"presence": "request"
}
@ -6173,7 +6197,7 @@
"presence": "request"
},
"video_%%u": {
"caps": "video/x-raw:\n\nvideo/x-raw(memory:CUDAMemory):\n\nvideo/x-raw(memory:GLMemory):\n\nvideo/x-raw(memory:NVMM):\nvideo/x-vp8:\nvideo/x-h264:\nvideo/x-vp9:\nvideo/x-h265:\n",
"caps": "video/x-raw:\n\nvideo/x-raw(memory:CUDAMemory):\n\nvideo/x-raw(memory:GLMemory):\n\nvideo/x-raw(memory:NVMM):\n\nvideo/x-raw(memory:D3D11Memory):\nvideo/x-vp8:\nvideo/x-h264:\nvideo/x-vp9:\nvideo/x-h265:\n",
"direction": "sink",
"presence": "request"
}
@ -6238,25 +6262,36 @@
"blurb": "The Signallable object to use to handle WebRTC Signalling",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"construct-only": true,
"controllable": false,
"mutable": "ready",
"mutable": "null",
"readable": true,
"type": "GstRSWebRTCSignallableIface",
"writable": true
},
"stun-server": {
"blurb": "NULL",
"blurb": "The STUN server of the form stun://host:port",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"default": "stun://stun.l.google.com:19302",
"mutable": "null",
"mutable": "ready",
"readable": true,
"type": "gchararray",
"writable": true
},
"turn-servers": {
"blurb": "The TURN servers of the form <\"turn(s)://username:password@host:port\", \"turn(s)://username1:password1@host1:port1\">",
"conditionally-available": false,
"construct": false,
"construct-only": false,
"controllable": false,
"mutable": "ready",
"readable": true,
"type": "GstValueArray",
"writable": true
},
"video-codecs": {
"blurb": "Names of video codecs to be be used during the SDP negotiation. Valid values: [VP8, H264, VP9, H265]",
"conditionally-available": false,
@ -6315,7 +6350,7 @@
"presence": "request"
},
"video_%%u": {
"caps": "video/x-raw:\n\nvideo/x-raw(memory:CUDAMemory):\n\nvideo/x-raw(memory:GLMemory):\n\nvideo/x-raw(memory:NVMM):\nvideo/x-vp8:\nvideo/x-h264:\nvideo/x-vp9:\nvideo/x-h265:\n",
"caps": "video/x-raw:\n\nvideo/x-raw(memory:CUDAMemory):\n\nvideo/x-raw(memory:GLMemory):\n\nvideo/x-raw(memory:NVMM):\n\nvideo/x-raw(memory:D3D11Memory):\nvideo/x-vp8:\nvideo/x-h264:\nvideo/x-vp9:\nvideo/x-h265:\n",
"direction": "sink",
"presence": "request"
}

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-file"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MIT OR Apache-2.0"
@ -10,8 +10,8 @@ rust-version = "1.70"
[dependencies]
url = "2"
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gstrsfile"
@ -19,7 +19,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -13,6 +13,7 @@
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst_base::prelude::*;
use gst_base::subclass::prelude::*;
use std::fs::File;
@ -111,6 +112,12 @@ impl ObjectSubclass for FileSink {
}
impl ObjectImpl for FileSink {
fn constructed(&self) {
self.parent_constructed();
self.obj().set_sync(false);
}
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![glib::ParamSpecString::builder("location")

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-sodium"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Jordan Petridis <jordan@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
description = "GStreamer plugin for libsodium-based file encryption and decryption"
@ -9,8 +9,8 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package="gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package="gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
sodiumoxide = "0.2.1"
once_cell = "1.3.0"
hex = "0.4"
@ -27,10 +27,14 @@ rand = "0.8"
[dev-dependencies.gst-check]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
branch = "0.21"
version = "0.21"
package="gstreamer-check"
[dev-dependencies.gst-app]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
branch = "0.21"
version = "0.21"
package="gstreamer-app"
[lib]
@ -54,7 +58,7 @@ path = "examples/decrypt_example.rs"
required-features = ["serde", "serde_json", "clap"]
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-threadshare"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
license = "LGPL-2.1-or-later"
description = "GStreamer Threadshare Plugin"
@ -10,19 +10,20 @@ rust-version = "1.70"
[dependencies]
async-task = "4.3.0"
concurrent-queue = "2"
flume = "0.10.13"
futures = "0.3.21"
libc = "0.2"
gio = { git = "https://github.com/gtk-rs/gtk-rs-core" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-net = { package = "gstreamer-net", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
cfg-if = "1"
concurrent-queue = "2.2.0"
flume = "0.11"
futures = "0.3.28"
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-net = { package = "gstreamer-net", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
once_cell = "1"
pin-project-lite = "0.2.0"
polling = "2.2.0"
polling = "3.1.0"
rand = "0.8"
rustix = { version = "0.38.2", default-features = false, features = ["std", "fs", "net"] }
slab = "0.4.7"
socket2 = {features = ["all"], version = "0.5"}
waker-fn = "1.1"
@ -34,8 +35,8 @@ clap = { version = "4", features = ["derive"], optional = true }
winapi = { version = "0.3.9", features = ["winsock2", "processthreadsapi"] }
[dev-dependencies]
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gstthreadshare"
@ -59,7 +60,7 @@ name = "ts-standalone"
path = "examples/standalone/main.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
cc = "1.0.38"
pkg-config = "0.3.15"

View file

@ -21,7 +21,7 @@ use glib::ffi::{gboolean, gpointer, GList, GType};
use gst::glib;
use gst::ffi::GstClockTime;
use libc::{c_int, c_uint, c_ulonglong, c_ushort, c_void};
use std::ffi::{c_int, c_uint, c_ulonglong, c_ushort, c_void};
#[repr(C)]
#[derive(Copy, Clone)]

View file

@ -381,7 +381,7 @@ impl SinkHandler {
let caps = element
.emit_by_name::<Option<gst::Caps>>("request-pt-map", &[&(pt as u32)])
.ok_or_else(|| {
gst::error!(CAT, obj: pad, "Signal 'request-pt-map' retuned None");
gst::error!(CAT, obj: pad, "Signal 'request-pt-map' returned None");
gst::FlowError::Error
})?;
let mut state = jb.state.lock().unwrap();

View file

@ -18,20 +18,20 @@ use std::task::{Context, Poll};
#[cfg(unix)]
use std::{
os::unix::io::{AsRawFd, RawFd},
os::unix::io::{AsFd, AsRawFd, BorrowedFd, OwnedFd, RawFd},
os::unix::net::{SocketAddr as UnixSocketAddr, UnixDatagram, UnixListener, UnixStream},
path::Path,
};
#[cfg(windows)]
use std::os::windows::io::{AsRawSocket, RawSocket};
use std::os::windows::io::{AsRawSocket, AsSocket, BorrowedSocket, OwnedSocket, RawSocket};
use socket2::{Domain, Protocol, SockAddr, Socket, Type};
use crate::runtime::RUNTIME_CAT;
use super::scheduler::{self, Scheduler};
use super::{Reactor, Readable, ReadableOwned, Source, Writable, WritableOwned};
use super::{Reactor, Readable, ReadableOwned, Registration, Source, Writable, WritableOwned};
/// Async adapter for I/O types.
///
@ -94,20 +94,20 @@ pub struct Async<T: Send + 'static> {
pub(super) source: Arc<Source>,
/// The inner I/O handle.
io: Option<T>,
pub(super) io: Option<T>,
// The [`Handle`] on the [`Scheduler`] on which this Async wrapper is registered.
sched: scheduler::HandleWeak,
pub(super) sched: scheduler::HandleWeak,
}
impl<T: Send + 'static> Unpin for Async<T> {}
#[cfg(unix)]
impl<T: AsRawFd + Send + 'static> Async<T> {
impl<T: AsFd + Send + 'static> Async<T> {
/// Creates an async I/O handle.
///
/// This method will put the handle in non-blocking mode and register it in
/// [epoll]/[kqueue]/[event ports]/[wepoll].
/// [epoll]/[kqueue]/[event ports]/[IOCP].
///
/// On Unix systems, the handle must implement `AsRawFd`, while on Windows it must implement
/// `AsRawSocket`.
@ -115,22 +115,33 @@ impl<T: AsRawFd + Send + 'static> Async<T> {
/// [epoll]: https://en.wikipedia.org/wiki/Epoll
/// [kqueue]: https://en.wikipedia.org/wiki/Kqueue
/// [event ports]: https://illumos.org/man/port_create
/// [wepoll]: https://github.com/piscisaureus/wepoll
/// [IOCP]: https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports
pub fn new(io: T) -> io::Result<Async<T>> {
let fd = io.as_raw_fd();
// Put the file descriptor in non-blocking mode.
unsafe {
let mut res = libc::fcntl(fd, libc::F_GETFL);
if res != -1 {
res = libc::fcntl(fd, libc::F_SETFL, res | libc::O_NONBLOCK);
}
if res == -1 {
return Err(io::Error::last_os_error());
let fd = io.as_fd();
cfg_if::cfg_if! {
// ioctl(FIONBIO) sets the flag atomically, but we use this only on Linux
// for now, as with the standard library, because it seems to behave
// differently depending on the platform.
// https://github.com/rust-lang/rust/commit/efeb42be2837842d1beb47b51bb693c7474aba3d
// https://github.com/libuv/libuv/blob/e9d91fccfc3e5ff772d5da90e1c4a24061198ca0/src/unix/poll.c#L78-L80
// https://github.com/tokio-rs/mio/commit/0db49f6d5caf54b12176821363d154384357e70a
if #[cfg(target_os = "linux")] {
rustix::io::ioctl_fionbio(fd, true)?;
} else {
let previous = rustix::fs::fcntl_getfl(fd)?;
let new = previous | rustix::fs::OFlags::NONBLOCK;
if new != previous {
rustix::fs::fcntl_setfl(fd, new)?;
}
}
}
let source = Reactor::with_mut(|reactor| reactor.insert_io(fd))?;
// SAFETY: It is impossible to drop the I/O source while it is registered through
// this type.
let registration = unsafe { Registration::new(fd) };
let source = Reactor::with_mut(|reactor| reactor.insert_io(registration))?;
Ok(Async {
source,
io: Some(io),
@ -144,16 +155,41 @@ impl<T: AsRawFd + Send + 'static> Async<T> {
#[cfg(unix)]
impl<T: AsRawFd + Send + 'static> AsRawFd for Async<T> {
fn as_raw_fd(&self) -> RawFd {
self.source.raw
self.get_ref().as_raw_fd()
}
}
#[cfg(unix)]
impl<T: AsFd + Send + 'static> AsFd for Async<T> {
fn as_fd(&self) -> BorrowedFd<'_> {
self.get_ref().as_fd()
}
}
#[cfg(unix)]
impl<T: AsFd + From<OwnedFd> + Send + 'static> TryFrom<OwnedFd> for Async<T> {
type Error = io::Error;
fn try_from(value: OwnedFd) -> Result<Self, Self::Error> {
Async::new(value.into())
}
}
#[cfg(unix)]
impl<T: Into<OwnedFd> + Send + 'static> TryFrom<Async<T>> for OwnedFd {
type Error = io::Error;
fn try_from(value: Async<T>) -> Result<Self, Self::Error> {
value.into_inner().map(Into::into)
}
}
#[cfg(windows)]
impl<T: AsRawSocket + Send + 'static> Async<T> {
impl<T: AsSocket + Send + 'static> Async<T> {
/// Creates an async I/O handle.
///
/// This method will put the handle in non-blocking mode and register it in
/// [epoll]/[kqueue]/[event ports]/[wepoll].
/// [epoll]/[kqueue]/[event ports]/[IOCP].
///
/// On Unix systems, the handle must implement `AsRawFd`, while on Windows it must implement
/// `AsRawSocket`.
@ -161,27 +197,24 @@ impl<T: AsRawSocket + Send + 'static> Async<T> {
/// [epoll]: https://en.wikipedia.org/wiki/Epoll
/// [kqueue]: https://en.wikipedia.org/wiki/Kqueue
/// [event ports]: https://illumos.org/man/port_create
/// [wepoll]: https://github.com/piscisaureus/wepoll
/// [IOCP]: https://learn.microsoft.com/en-us/windows/win32/fileio/i-o-completion-ports
pub fn new(io: T) -> io::Result<Async<T>> {
let sock = io.as_raw_socket();
let borrowed = io.as_socket();
// Put the socket in non-blocking mode.
unsafe {
use winapi::ctypes;
use winapi::um::winsock2;
//
// Safety: We assume `as_raw_socket()` returns a valid fd. When we can
// depend on Rust >= 1.63, where `AsFd` is stabilized, and when
// `TimerFd` implements it, we can remove this unsafe and simplify this.
rustix::io::ioctl_fionbio(borrowed, true)?;
let mut nonblocking = true as ctypes::c_ulong;
let res = winsock2::ioctlsocket(
sock as winsock2::SOCKET,
winsock2::FIONBIO,
&mut nonblocking,
);
if res != 0 {
return Err(io::Error::last_os_error());
}
}
// Create the registration.
//
// SAFETY: It is impossible to drop the I/O source while it is registered through
// this type.
let registration = unsafe { Registration::new(borrowed) };
let source = Reactor::with_mut(|reactor| reactor.insert_io(sock))?;
let source = Reactor::with_mut(|reactor| reactor.insert_io(registration))?;
Ok(Async {
source,
io: Some(io),
@ -195,7 +228,32 @@ impl<T: AsRawSocket + Send + 'static> Async<T> {
#[cfg(windows)]
impl<T: AsRawSocket + Send + 'static> AsRawSocket for Async<T> {
fn as_raw_socket(&self) -> RawSocket {
self.source.raw
self.get_ref().as_raw_socket()
}
}
#[cfg(windows)]
impl<T: AsSocket + Send + 'static> AsSocket for Async<T> {
fn as_socket(&self) -> BorrowedSocket<'_> {
self.get_ref().as_socket()
}
}
#[cfg(windows)]
impl<T: AsSocket + From<OwnedSocket> + Send + 'static> TryFrom<OwnedSocket> for Async<T> {
type Error = io::Error;
fn try_from(value: OwnedSocket) -> Result<Self, Self::Error> {
Async::new(value.into())
}
}
#[cfg(windows)]
impl<T: Into<OwnedSocket> + Send + 'static> TryFrom<Async<T>> for OwnedSocket {
type Error = io::Error;
fn try_from(value: Async<T>) -> Result<Self, Self::Error> {
value.into_inner().map(Into::into)
}
}
@ -380,7 +438,12 @@ impl<T: Send + 'static> Drop for Async<T> {
sched.spawn_and_unpark(async move {
Reactor::with_mut(|reactor| {
if let Err(err) = reactor.remove_io(&source) {
gst::error!(RUNTIME_CAT, "Failed to remove fd {}: {}", source.raw, err);
gst::error!(
RUNTIME_CAT,
"Failed to remove fd {:?}: {}",
source.registration,
err
);
}
});
drop(io);
@ -392,6 +455,94 @@ impl<T: Send + 'static> Drop for Async<T> {
}
}
/// Types whose I/O trait implementations do not drop the underlying I/O source.
///
/// The resource contained inside of the [`Async`] cannot be invalidated. This invalidation can
/// happen if the inner resource (the [`TcpStream`], [`UnixListener`] or other `T`) is moved out
/// and dropped before the [`Async`]. Because of this, functions that grant mutable access to
/// the inner type are unsafe, as there is no way to guarantee that the source won't be dropped
/// and a dangling handle won't be left behind.
///
/// Unfortunately this extends to implementations of [`Read`] and [`Write`]. Since methods on those
/// traits take `&mut`, there is no guarantee that the implementor of those traits won't move the
/// source out while the method is being run.
///
/// This trait is an antidote to this predicament. By implementing this trait, the user pledges
/// that using any I/O traits won't destroy the source. This way, [`Async`] can implement the
/// `async` version of these I/O traits, like [`AsyncRead`] and [`AsyncWrite`].
///
/// # Safety
///
/// Any I/O trait implementations for this type must not drop the underlying I/O source. Traits
/// affected by this trait include [`Read`], [`Write`], [`Seek`] and [`BufRead`].
///
/// This trait is implemented by default on top of `libstd` types. In addition, it is implemented
/// for immutable reference types, as it is impossible to invalidate any outstanding references
/// while holding an immutable reference, even with interior mutability. As Rust's current pinning
/// system relies on similar guarantees, I believe that this approach is robust.
///
/// [`BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html
/// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
/// [`Seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html
/// [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
///
/// [`AsyncRead`]: https://docs.rs/futures-io/latest/futures_io/trait.AsyncRead.html
/// [`AsyncWrite`]: https://docs.rs/futures-io/latest/futures_io/trait.AsyncWrite.html
pub unsafe trait IoSafe {}
/// Reference types can't be mutated.
///
/// The worst thing that can happen is that external state is used to change what kind of pointer
/// `as_fd()` returns. For instance:
///
/// ```
/// # #[cfg(unix)] {
/// use std::cell::Cell;
/// use std::net::TcpStream;
/// use std::os::unix::io::{AsFd, BorrowedFd};
///
/// struct Bar {
/// flag: Cell<bool>,
/// a: TcpStream,
/// b: TcpStream
/// }
///
/// impl AsFd for Bar {
/// fn as_fd(&self) -> BorrowedFd<'_> {
/// if self.flag.replace(!self.flag.get()) {
/// self.a.as_fd()
/// } else {
/// self.b.as_fd()
/// }
/// }
/// }
/// # }
/// ```
///
/// We solve this problem by only calling `as_fd()` once to get the original source. Implementations
/// like this are considered buggy (but not unsound) and are thus not really supported by `async-io`.
unsafe impl<T: ?Sized> IoSafe for &T {}
// Can be implemented on top of libstd types.
unsafe impl IoSafe for std::fs::File {}
unsafe impl IoSafe for std::io::Stderr {}
unsafe impl IoSafe for std::io::Stdin {}
unsafe impl IoSafe for std::io::Stdout {}
unsafe impl IoSafe for std::io::StderrLock<'_> {}
unsafe impl IoSafe for std::io::StdinLock<'_> {}
unsafe impl IoSafe for std::io::StdoutLock<'_> {}
unsafe impl IoSafe for std::net::TcpStream {}
#[cfg(unix)]
unsafe impl IoSafe for std::os::unix::net::UnixStream {}
unsafe impl<T: IoSafe + Read> IoSafe for std::io::BufReader<T> {}
unsafe impl<T: IoSafe + Write> IoSafe for std::io::BufWriter<T> {}
unsafe impl<T: IoSafe + Write> IoSafe for std::io::LineWriter<T> {}
unsafe impl<T: IoSafe + ?Sized> IoSafe for &mut T {}
unsafe impl<T: IoSafe + ?Sized> IoSafe for Box<T> {}
unsafe impl<T: Clone + IoSafe + ?Sized> IoSafe for std::borrow::Cow<'_, T> {}
impl<T: Read + Send + 'static> AsyncRead for Async<T> {
fn poll_read(
mut self: Pin<&mut Self>,
@ -422,6 +573,8 @@ impl<T: Read + Send + 'static> AsyncRead for Async<T> {
}
}
// Since this is through a reference, we can't mutate the inner I/O source.
// Therefore this is safe!
impl<T: Send + 'static> AsyncRead for &Async<T>
where
for<'a> &'a T: Read,
@ -886,7 +1039,7 @@ fn connect(addr: SockAddr, domain: Domain, protocol: Option<Protocol>) -> io::Re
match socket.connect(&addr) {
Ok(_) => {}
#[cfg(unix)]
Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => {}
Err(err) if err.raw_os_error() == Some(rustix::io::Errno::INPROGRESS.raw_os_error()) => {}
Err(err) if err.kind() == io::ErrorKind::WouldBlock => {}
Err(err) => return Err(err),
}

View file

@ -215,7 +215,7 @@ impl Context {
/// Executes the provided function relatively to this [`Context`].
///
/// Usefull to initialize i/o sources and timers from outside
/// Useful to initialize i/o sources and timers from outside
/// of a [`Context`].
///
/// # Panic

View file

@ -30,7 +30,7 @@ mod join;
pub use join::JoinHandle;
pub mod reactor;
use reactor::{Reactor, Readable, ReadableOwned, Source, Writable, WritableOwned};
use reactor::{Reactor, Readable, ReadableOwned, Registration, Source, Writable, WritableOwned};
// We need the `Mutex<bool>` to work in pair with `Condvar`.
#[allow(clippy::mutex_atomic)]

View file

@ -1,3 +1,4 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// This is based on https://github.com/smol-rs/async-io
// with adaptations by:
//
@ -7,7 +8,7 @@
use concurrent_queue::ConcurrentQueue;
use futures::ready;
use polling::{Event, Poller};
use polling::{Event, Events, Poller};
use slab::Slab;
use std::borrow::Borrow;
@ -18,10 +19,6 @@ use std::future::Future;
use std::io;
use std::marker::PhantomData;
use std::mem;
#[cfg(unix)]
use std::os::unix::io::RawFd;
#[cfg(windows)]
use std::os::windows::io::RawSocket;
use std::panic;
use std::pin::Pin;
use std::sync::atomic::{AtomicUsize, Ordering};
@ -29,6 +26,31 @@ use std::sync::{Arc, Mutex};
use std::task::{Context, Poll, Waker};
use std::time::{Duration, Instant};
// Choose the proper implementation of `Registration` based on the target platform.
cfg_if::cfg_if! {
if #[cfg(windows)] {
mod windows;
pub use windows::Registration;
} else if #[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "watchos",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
))] {
mod kqueue;
pub use kqueue::Registration;
} else if #[cfg(unix)] {
mod unix;
pub use unix::Registration;
} else {
compile_error!("unsupported platform");
}
}
use crate::runtime::{Async, RUNTIME_CAT};
const READ: usize = 0;
@ -71,7 +93,7 @@ pub(super) struct Reactor {
/// Temporary storage for I/O events when polling the reactor.
///
/// Holding a lock on this event list implies the exclusive right to poll I/O.
events: Vec<Event>,
events: Events,
/// An ordered map of registered regular timers.
///
@ -106,7 +128,7 @@ impl Reactor {
half_max_throttling: max_throttling / 2,
wakers: Vec::new(),
sources: Slab::new(),
events: Vec::new(),
events: Events::new(),
timers: BTreeMap::new(),
after_timers: BTreeMap::new(),
timer_ops: ConcurrentQueue::bounded(1000),
@ -210,16 +232,12 @@ impl Reactor {
}
/// Registers an I/O source in the reactor.
pub fn insert_io(
&mut self,
#[cfg(unix)] raw: RawFd,
#[cfg(windows)] raw: RawSocket,
) -> io::Result<Arc<Source>> {
pub fn insert_io(&mut self, raw: Registration) -> io::Result<Arc<Source>> {
// Create an I/O source for this file descriptor.
let source = {
let key = self.sources.vacant_entry().key();
let source = Arc::new(Source {
raw,
registration: raw,
key,
state: Default::default(),
});
@ -228,11 +246,11 @@ impl Reactor {
};
// Register the file descriptor.
if let Err(err) = self.poller.add(raw, Event::none(source.key)) {
if let Err(err) = source.registration.add(&self.poller, source.key) {
gst::error!(
crate::runtime::RUNTIME_CAT,
"Failed to register fd {}: {}",
source.raw,
"Failed to register fd {:?}: {}",
source.registration,
err,
);
self.sources.remove(source.key);
@ -245,7 +263,7 @@ impl Reactor {
/// Deregisters an I/O source from the reactor.
pub fn remove_io(&mut self, source: &Source) -> io::Result<()> {
self.sources.remove(source.key);
self.poller.delete(source.raw)
source.registration.delete(&self.poller)
}
/// Registers a regular timer in the reactor.
@ -414,14 +432,16 @@ impl Reactor {
// e.g. we were previously interested in both readability and writability,
// but only one of them was emitted.
if !state[READ].is_empty() || !state[WRITE].is_empty() {
self.poller.modify(
source.raw,
Event {
key: source.key,
readable: !state[READ].is_empty(),
writable: !state[WRITE].is_empty(),
},
)?;
// Create the event that we are interested in.
let event = {
let mut event = Event::none(source.key);
event.readable = !state[READ].is_empty();
event.writable = !state[WRITE].is_empty();
event
};
// Register interest in this event.
source.registration.modify(&self.poller, event)?;
}
}
}
@ -493,13 +513,8 @@ enum TimerOp {
/// A registered source of I/O events.
#[derive(Debug)]
pub(super) struct Source {
/// Raw file descriptor on Unix platforms.
#[cfg(unix)]
pub(super) raw: RawFd,
/// Raw socket handle on Windows.
#[cfg(windows)]
pub(super) raw: RawSocket,
/// This source's registration into the reactor.
pub(super) registration: Registration,
/// The key of this source obtained during registration.
key: usize,
@ -590,14 +605,15 @@ impl Source {
// Update interest in this I/O handle.
if was_empty {
reactor.poller.modify(
self.raw,
Event {
key: self.key,
readable: !state[READ].is_empty(),
writable: !state[WRITE].is_empty(),
},
)?;
let event = {
let mut event = Event::none(self.key);
event.readable = !state[READ].is_empty();
event.writable = !state[WRITE].is_empty();
event
};
// Register interest in it.
self.registration.modify(&reactor.poller, event)?;
}
Poll::Pending
@ -645,7 +661,11 @@ impl<T: Send + 'static> Future for Readable<'_, T> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
ready!(Pin::new(&mut self.0).poll(cx))?;
gst::trace!(RUNTIME_CAT, "readable: fd={}", self.0.handle.source.raw);
gst::trace!(
RUNTIME_CAT,
"readable: fd={:?}",
self.0.handle.source.registration
);
Poll::Ready(Ok(()))
}
}
@ -667,8 +687,8 @@ impl<T: Send + 'static> Future for ReadableOwned<T> {
ready!(Pin::new(&mut self.0).poll(cx))?;
gst::trace!(
RUNTIME_CAT,
"readable_owned: fd={}",
self.0.handle.source.raw
"readable_owned: fd={:?}",
self.0.handle.source.registration
);
Poll::Ready(Ok(()))
}
@ -689,7 +709,11 @@ impl<T: Send + 'static> Future for Writable<'_, T> {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
ready!(Pin::new(&mut self.0).poll(cx))?;
gst::trace!(RUNTIME_CAT, "writable: fd={}", self.0.handle.source.raw);
gst::trace!(
RUNTIME_CAT,
"writable: fd={:?}",
self.0.handle.source.registration
);
Poll::Ready(Ok(()))
}
}
@ -711,8 +735,8 @@ impl<T: Send + 'static> Future for WritableOwned<T> {
ready!(Pin::new(&mut self.0).poll(cx))?;
gst::trace!(
RUNTIME_CAT,
"writable_owned: fd={}",
self.0.handle.source.raw
"writable_owned: fd={:?}",
self.0.handle.source.registration
);
Poll::Ready(Ok(()))
}
@ -780,14 +804,19 @@ impl<H: Borrow<Async<T>> + Clone, T: Send + 'static> Future for Ready<H, T> {
// Update interest in this I/O handle.
if was_empty {
reactor.poller.modify(
handle.borrow().source.raw,
Event {
key: handle.borrow().source.key,
readable: !state[READ].is_empty(),
writable: !state[WRITE].is_empty(),
},
)?;
// Create the event that we are interested in.
let event = {
let mut event = Event::none(handle.borrow().source.key);
event.readable = !state[READ].is_empty();
event.writable = !state[WRITE].is_empty();
event
};
handle
.borrow()
.source
.registration
.modify(&reactor.poller, event)?;
}
Poll::Pending

View file

@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// This is based on https://github.com/smol-rs/async-io
use polling::{Event, Poller};
use std::fmt;
use std::io::Result;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
/// The raw registration into the reactor.
#[doc(hidden)]
pub struct Registration {
/// Raw file descriptor for readability/writability.
///
///
/// # Invariant
///
/// This describes a valid file descriptor that has not been `close`d. It will not be
/// closed while this object is alive.
raw: RawFd,
}
impl fmt::Debug for Registration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.raw, f)
}
}
impl Registration {
/// Add this file descriptor into the reactor.
///
/// # Safety
///
/// The provided file descriptor must be valid and not be closed while this object is alive.
pub(crate) unsafe fn new(f: impl AsFd) -> Self {
Self {
raw: f.as_fd().as_raw_fd(),
}
}
/// Registers the object into the reactor.
#[inline]
pub(crate) fn add(&self, poller: &Poller, token: usize) -> Result<()> {
// SAFETY: This object's existence validates the invariants of Poller::add
unsafe { poller.add(self.raw, Event::none(token)) }
}
/// Re-registers the object into the reactor.
#[inline]
pub(crate) fn modify(&self, poller: &Poller, interest: Event) -> Result<()> {
// SAFETY: self.raw is a valid file descriptor
let fd = unsafe { BorrowedFd::borrow_raw(self.raw) };
poller.modify(fd, interest)
}
/// Deregisters the object from the reactor.
#[inline]
pub(crate) fn delete(&self, poller: &Poller) -> Result<()> {
// SAFETY: self.raw is a valid file descriptor
let fd = unsafe { BorrowedFd::borrow_raw(self.raw) };
poller.delete(fd)
}
}

View file

@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// This is based on https://github.com/smol-rs/async-io
use polling::{Event, Poller};
use std::fmt;
use std::io::Result;
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
/// The raw registration into the reactor.
#[doc(hidden)]
pub struct Registration {
/// Raw file descriptor on Unix.
///
/// # Invariant
///
/// This describes a valid file descriptor that has not been `close`d. It will not be
/// closed while this object is alive.
raw: RawFd,
}
impl fmt::Debug for Registration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.raw, f)
}
}
impl Registration {
/// Add this file descriptor into the reactor.
///
/// # Safety
///
/// The provided file descriptor must be valid and not be closed while this object is alive.
pub(crate) unsafe fn new(f: impl AsFd) -> Self {
Self {
raw: f.as_fd().as_raw_fd(),
}
}
/// Registers the object into the reactor.
#[inline]
pub(crate) fn add(&self, poller: &Poller, token: usize) -> Result<()> {
// SAFETY: This object's existence validates the invariants of Poller::add
unsafe { poller.add(self.raw, Event::none(token)) }
}
/// Re-registers the object into the reactor.
#[inline]
pub(crate) fn modify(&self, poller: &Poller, interest: Event) -> Result<()> {
// SAFETY: self.raw is a valid file descriptor
let fd = unsafe { BorrowedFd::borrow_raw(self.raw) };
poller.modify(fd, interest)
}
/// Deregisters the object from the reactor.
#[inline]
pub(crate) fn delete(&self, poller: &Poller) -> Result<()> {
// SAFETY: self.raw is a valid file descriptor
let fd = unsafe { BorrowedFd::borrow_raw(self.raw) };
poller.delete(fd)
}
}

View file

@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// This is based on https://github.com/smol-rs/async-io
use polling::{Event, Poller};
use std::fmt;
use std::io::Result;
use std::os::windows::io::{AsRawSocket, AsSocket, BorrowedSocket, RawSocket};
/// The raw registration into the reactor.
#[doc(hidden)]
pub struct Registration {
/// Raw socket handle on Windows.
///
/// # Invariant
///
/// This describes a valid socket that has not been `close`d. It will not be
/// closed while this object is alive.
raw: RawSocket,
}
impl fmt::Debug for Registration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.raw, f)
}
}
impl Registration {
/// Add this file descriptor into the reactor.
///
/// # Safety
///
/// The provided file descriptor must be valid and not be closed while this object is alive.
pub(crate) unsafe fn new(f: impl AsSocket) -> Self {
Self {
raw: f.as_socket().as_raw_socket(),
}
}
/// Registers the object into the reactor.
#[inline]
pub(crate) fn add(&self, poller: &Poller, token: usize) -> Result<()> {
// SAFETY: This object's existence validates the invariants of Poller::add
unsafe { poller.add(self.raw, Event::none(token)) }
}
/// Re-registers the object into the reactor.
#[inline]
pub(crate) fn modify(&self, poller: &Poller, interest: Event) -> Result<()> {
// SAFETY: self.raw is a valid file descriptor
let fd = unsafe { BorrowedSocket::borrow_raw(self.raw) };
poller.modify(fd, interest)
}
/// Deregisters the object from the reactor.
#[inline]
pub(crate) fn delete(&self, poller: &Poller) -> Result<()> {
// SAFETY: self.raw is a valid file descriptor
let fd = unsafe { BorrowedSocket::borrow_raw(self.raw) };
poller.delete(fd)
}
}

View file

@ -394,7 +394,7 @@ impl Handle {
/// Executes the provided function relatively to this [`Scheduler`]'s [`Reactor`].
///
/// Usefull to initialize i/o sources and timers from outside
/// Useful to initialize i/o sources and timers from outside
/// of a [`Scheduler`].
///
/// # Panic

View file

@ -25,8 +25,6 @@ use gst::prelude::*;
use gst::glib::once_cell::sync::Lazy;
use gio::prelude::*;
use std::error;
use std::fmt;
use std::io;
@ -222,31 +220,57 @@ impl GioSocketWrapper {
}
}
#[cfg(unix)]
pub fn set_tos(&self, qos_dscp: i32) -> Result<(), glib::Error> {
use libc::{IPPROTO_IP, IPPROTO_IPV6, IPV6_TCLASS, IP_TOS};
#[cfg(any(
bsd,
linux_like,
target_os = "aix",
target_os = "fuchsia",
target_os = "haiku",
target_env = "newlib"
))]
pub fn set_tos(&self, qos_dscp: i32) -> rustix::io::Result<()> {
use gio::prelude::*;
use rustix::net::sockopt;
let tos = (qos_dscp & 0x3f) << 2;
let socket = self.as_socket();
socket.set_option(IPPROTO_IP, IP_TOS, tos)?;
sockopt::set_ip_tos(socket, tos)?;
if socket.family() == gio::SocketFamily::Ipv6 {
socket.set_option(IPPROTO_IPV6, IPV6_TCLASS, tos)?;
sockopt::set_ipv6_tclass(socket, tos)?;
}
Ok(())
}
#[cfg(not(unix))]
pub fn set_tos(&self, qos_dscp: i32) -> Result<(), glib::Error> {
#[cfg(not(any(
bsd,
linux_like,
target_os = "aix",
target_os = "fuchsia",
target_os = "haiku",
target_env = "newlib"
)))]
pub fn set_tos(&self, _qos_dscp: i32) -> rustix::io::Result<()> {
Ok(())
}
#[cfg(unix)]
#[cfg(not(windows))]
pub fn get<T: FromRawFd>(&self) -> T {
unsafe { FromRawFd::from_raw_fd(libc::dup(gio::ffi::g_socket_get_fd(self.socket))) }
unsafe {
let borrowed =
rustix::fd::BorrowedFd::borrow_raw(gio::ffi::g_socket_get_fd(self.socket));
let dupped = rustix::io::dup(borrowed).unwrap();
let res = FromRawFd::from_raw_fd(dupped.as_raw_fd());
// We transferred ownership to T so don't drop dupped
std::mem::forget(dupped);
res
}
}
#[cfg(windows)]
@ -308,7 +332,7 @@ unsafe fn dup_socket(socket: usize) -> usize {
pub fn wrap_socket(socket: &Async<UdpSocket>) -> Result<GioSocketWrapper, gst::ErrorMessage> {
#[cfg(unix)]
unsafe {
let fd = libc::dup(socket.as_raw_fd());
let dupped = rustix::io::dup(socket).unwrap();
// This is unsafe because it allows us to share the fd between the socket and the
// GIO socket below, but safety of this is the job of the application
@ -319,14 +343,18 @@ pub fn wrap_socket(socket: &Async<UdpSocket>) -> Result<GioSocketWrapper, gst::E
}
}
let fd = FdConverter(fd);
let fd = FdConverter(dupped.as_raw_fd());
let gio_socket = gio::Socket::from_fd(fd).map_err(|err| {
let gio_socket = gio::Socket::from_fd(fd);
// We transferred ownership to gio_socket so don't drop dupped
std::mem::forget(dupped);
let gio_socket = gio_socket.map_err(|err| {
gst::error_msg!(
gst::ResourceError::OpenWrite,
["Failed to create wrapped GIO socket: {}", err]
)
})?;
Ok(GioSocketWrapper::new(&gio_socket))
}
#[cfg(windows)]

View file

@ -375,6 +375,8 @@ impl TaskImpl for UdpSrcTask {
)
})?;
drop(settings);
self.socket = Some(
Socket::try_new(
self.element.clone().upcast(),

View file

@ -43,7 +43,7 @@ fn test_push() {
let handler = thread::spawn(move || {
use std::net;
let listener = net::TcpListener::bind("0.0.0.0:5000").unwrap();
let listener = net::TcpListener::bind("0.0.0.0:5010").unwrap();
listening_tx.send(()).unwrap();
let stream = listener.incoming().next().unwrap();
let buffer = [0; 160];
@ -59,7 +59,7 @@ fn test_push() {
let caps = gst::Caps::builder("foo/bar").build();
let tcpclientsrc = gst::ElementFactory::make("ts-tcpclientsrc")
.property("caps", &caps)
.property("port", 5000i32)
.property("port", 5010i32)
.build()
.unwrap();
let appsink = gst_app::AppSink::builder()

View file

@ -1,8 +1,8 @@
project('gst-plugins-rs',
'rust',
'c',
version: '0.11.0-alpha.1',
meson_version : '>= 0.60')
version: '0.11.3',
meson_version : '>= 1.1')
# dependencies.py needs a toml parsing module
python = import('python').find_installation(modules: ['tomllib'], required: false)
@ -20,7 +20,7 @@ endif
cargo = find_program('cargo', version:'>=1.40')
cargo_wrapper = find_program('cargo_wrapper.py')
cargo_c = find_program('cargo-cbuild', version:'>=0.9.3', required: false)
cargo_c = find_program('cargo-cbuild', version:'>=0.9.21', required: false)
rustc = meson.get_compiler('rust')
@ -62,15 +62,34 @@ deps = [
['gstreamer-app-1.0', 'gst-plugins-base', 'app_dep', 'gstapp'],
['gstreamer-audio-1.0', 'gst-plugins-base', 'audio_dep', 'gstaudio'],
['gstreamer-base-1.0', 'gstreamer', 'gst_base_dep', 'gst_base'],
['gstreamer-check-1.0', 'gstreamer', 'gst_check_dep', 'gst_check'],
['gstreamer-gl-1.0', 'gst-plugins-base', 'gst_gl_dep', 'gstgl'],
['gstreamer-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net'],
['gstreamer-rtp-1.0', 'gst-plugins-base', 'rtp_dep', 'gst_rtp'],
['gstreamer-video-1.0', 'gst-plugins-base', 'video_dep', 'gstvideo'],
['gstreamer-sdp-1.0', 'gst-plugins-base', 'sdp_dep', 'gstsdp'],
['gstreamer-webrtc-1.0', 'gst-plugins-bad', 'gstwebrtc_dep', 'gstwebrtc'],
['gstreamer-video-1.0', 'gst-plugins-base', 'video_dep', 'gstvideo']
]
webrtc_option = get_option('webrtc')
rtp_option = get_option('rtp').enable_if(webrtc_option.enabled(), error_message: 'webrtc option is enabled')
if get_option('threadshare').allowed() \
or get_option('onvif').allowed() \
or get_option('raptorq').allowed() \
or rtp_option.allowed() \
or webrtc_option.allowed()
deps += [['gstreamer-rtp-1.0', 'gst-plugins-base', 'rtp_dep', 'gst_rtp']]
endif
if get_option('webrtc').allowed() \
or get_option('webrtchttp').allowed()
deps += [['gstreamer-webrtc-1.0', 'gst-plugins-bad', 'gstwebrtc_dep', 'gstwebrtc']]
deps += [['gstreamer-sdp-1.0', 'gst-plugins-base', 'sdp_dep', 'gstsdp']]
endif
if get_option('tests').allowed()
deps += [['gstreamer-check-1.0', 'gstreamer', 'gst_check_dep', 'gst_check']]
endif
if get_option('gtk4').allowed()
deps += [['gstreamer-gl-1.0', 'gst-plugins-base', 'gst_gl_dep', 'gstgl']]
endif
if get_option('threadshare').allowed()
deps += [['gstreamer-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net']]
endif
glib_dep = dependency('glib-2.0', version: glib_req)
deps_cache += {'glib-2.0': glib_dep}
@ -142,21 +161,6 @@ plugins = {
'regex': {'library': 'libgstregex'},
'textwrap': {'library': 'libgsttextwrap'},
'fallbackswitch': {
'library': 'libgstfallbackswitch',
'examples': ['gtk-fallbackswitch'],
'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
},
'livesync': {
'library': 'libgstlivesync',
'examples': ['gtk-livesync'],
'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
},
'togglerecord': {
'library': 'libgsttogglerecord',
'examples': ['gtk-recording'],
'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
},
'tracers': {'library': 'libgstrstracers'},
'uriplaylistbin': {
'library': 'libgsturiplaylistbin',
@ -267,12 +271,29 @@ if get_option('gtk4').allowed()
gtk4_features += 'winegl'
endif
endif
plugins += {'gtk4': {
'library': 'libgstgtk4',
'examples': ['gtksink'],
'extra-deps': {'gtk4': '>=4.6'},
'features': gtk4_features,
}}
plugins += {
'gtk4': {
'library': 'libgstgtk4',
'examples': ['gtksink'],
'extra-deps': {'gtk4': '>=4.6'},
'features': gtk4_features,
},
'fallbackswitch': {
'library': 'libgstfallbackswitch',
'examples': ['gtk-fallbackswitch'],
'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
},
'livesync': {
'library': 'libgstlivesync',
'examples': ['gtk-livesync'],
'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
},
'togglerecord': {
'library': 'libgsttogglerecord',
'examples': ['gtk-recording'],
'features': ['gtk', 'gio', 'gst-plugin-gtk4'],
},
}
endif
# Process plugins list
@ -322,8 +343,17 @@ if get_option('rav1e').allowed() and find_program('nasm', required: false).found
features += 'gst-plugin-rav1e/asm'
endif
if get_option('default_library') == 'static'
extra_env += {
# Tell the pkg-config crate to think of all libraries as static
'PKG_CONFIG_ALL_STATIC': '1',
# Tell the system-deps crate to process linker flag for static deps
'SYSTEM_DEPS_LINK': 'static'
}
endif
foreach plugin_name, details: plugins
plugin_opt = get_option(plugin_name)
plugin_opt = get_variable(f'@plugin_name@_option', get_option(plugin_name))
if plugin_opt.allowed()
plugin_deps_found = true
foreach dep_name, dep_ver: details.get('extra-deps', {})
@ -335,11 +365,22 @@ foreach plugin_name, details: plugins
deps_cache += {dep_name: dep}
if not dep.found()
plugin_deps_found = false
break
endif
endforeach
plugin_features = details.get('features', [])
if plugin_deps_found
# Validate gst-plugin features
foreach feature: plugin_features
if feature.startswith('gst-plugin') and not packages.contains(feature)
plugin_deps_found = false
break
endif
endforeach
endif
if plugin_deps_found
packages += f'gst-plugin-@plugin_name@'
features += details.get('features', [])
features += plugin_features
extra_features = run_command('dependencies.py', meson.current_source_dir(), plugin_name,
'--feature', '--gst-version', gst_dep.version(), capture: true, check: true).stdout().strip()
if extra_features != ''
@ -372,7 +413,7 @@ if get_option('doc').disabled()
endif
# 'pkgconfig' is the entry in the machine file, if specified
pkg_config = find_program('pkgconfig', required: false)
pkg_config = find_program('pkgconfig', 'pkg-config', required: false)
if pkg_config.found()
extra_env += {'PKG_CONFIG': pkg_config.full_path()}
endif
@ -389,29 +430,31 @@ endif
# get cmdline for rust
extra_env += {'RUSTC': ' '.join(rustc.cmd_array())}
rs_plugins = custom_target('gst-plugins-rs',
build_by_default: true,
output: output,
console: true,
install: true,
install_dir: plugins_install_dir,
depends: depends,
depfile: 'gst-plugins-rs.dep',
env: extra_env,
command: [cargo_wrapper,
'build',
meson.current_build_dir(),
meson.current_source_dir(),
meson.global_build_root(),
target,
get_option('prefix'),
get_option('libdir'),
'--packages', packages,
'--depfile', '@DEPFILE@',
'--lib-suffixes', library_suffixes,
] + feature_args + extra_args)
plugins = rs_plugins.to_list()
plugins = []
if output.length() > 0
rs_plugins = custom_target('gst-plugins-rs',
build_by_default: true,
output: output,
console: true,
install: true,
install_dir: plugins_install_dir,
depends: depends,
depfile: 'gst-plugins-rs.dep',
env: extra_env,
command: [cargo_wrapper,
'build',
meson.current_build_dir(),
meson.current_source_dir(),
meson.global_build_root(),
target,
get_option('prefix'),
get_option('libdir'),
'--packages', packages,
'--depfile', '@DEPFILE@',
'--lib-suffixes', library_suffixes,
] + feature_args + extra_args)
plugins = rs_plugins.to_list()
endif
# This is used by GStreamer to static link Rust plugins into gst-full
gst_plugins = []
@ -480,14 +523,16 @@ subdir('docs')
# We don't need to pass a command as we depends on the target above
# but it is currently mandatory ( https://github.com/mesonbuild/meson/issues/8059 )
# so use python as it's guaranteed to be present on any setup
custom_target('gst-plugins-rs-pc-files',
build_by_default: true,
output: pc_files,
console: true,
install: true,
install_dir: pkgconfig_install_dir,
depends: rs_plugins,
command: [python, '-c', '""'])
if pc_files.length() > 0
custom_target('gst-plugins-rs-pc-files',
build_by_default: true,
output: pc_files,
console: true,
install: true,
install_dir: pkgconfig_install_dir,
depends: rs_plugins,
command: [python, '-c', '""'])
endif
if get_option('webrtc').allowed()
custom_target('gst-webrtc-signalling-server',
@ -535,18 +580,20 @@ if get_option('examples').allowed() and examples.length() > 0
] + feature_args)
endif
test('tests',
cargo_wrapper,
env: extra_env,
args: ['test',
meson.current_build_dir(),
meson.current_source_dir(),
meson.global_build_root(),
target,
get_option('prefix'),
get_option('libdir'),
'--packages', packages],
timeout: 600)
if get_option('tests').allowed()
test('tests',
cargo_wrapper,
env: extra_env,
args: ['test',
meson.current_build_dir(),
meson.current_source_dir(),
meson.global_build_root(),
target,
get_option('prefix'),
get_option('libdir'),
'--packages', packages],
timeout: 600)
endif
summary({
'Plugins': plugin_names,

View file

@ -28,7 +28,7 @@ option('onvif', type: 'feature', value: 'auto', description: 'Build onvif plugin
option('raptorq', type: 'feature', value: 'auto', description: 'Build raptorq plugin')
option('reqwest', type: 'feature', value: 'auto', description: 'Build reqwest plugin')
option('rtp', type: 'feature', value: 'auto', description: 'Build rtp plugin')
option('webrtc', type: 'feature', value: 'auto', description: 'Build webrtc plugin')
option('webrtc', type: 'feature', value: 'auto', yield: true, description: 'Build webrtc plugin')
option('webrtchttp', type: 'feature', value: 'auto', description: 'Build webrtchttp plugin')
# text
@ -62,3 +62,5 @@ option('doc', type: 'feature', value: 'auto', yield: true,
description: 'Enable documentation')
option('examples', type: 'feature', value: 'disabled', yield: true,
description: 'Build examples')
option('tests', type : 'feature', value : 'auto', yield : true,
description : 'Build and enable unit tests')

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-flavors"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MIT OR Apache-2.0"
@ -9,9 +9,9 @@ rust-version = "1.70"
description = "GStreamer Rust FLV Plugin"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
num-rational = { version = "0.4", default-features = false, features = [] }
nom = "7"
flavors = { git = "https://github.com/rust-av/flavors" }
@ -25,7 +25,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-fmp4"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
license = "MPL-2.0"
description = "GStreamer Fragmented MP4 Plugin"
@ -10,11 +10,11 @@ rust-version = "1.70"
[dependencies]
anyhow = "1"
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
[lib]
name = "gstfmp4"
@ -22,17 +22,17 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[dev-dependencies]
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
m3u8-rs = "5.0"
chrono = "0.4"
dash-mpd = { version = "0.13", default-features = false }
quick-xml = { version = "0.30", features = ["serialize"] }
dash-mpd = { version = "0.14", default-features = false }
quick-xml = { version = "0.31", features = ["serialize"] }
serde = "1"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
default = []

View file

@ -365,19 +365,21 @@ fn setup_appsink(appsink: &gst_app::AppSink, name: &str, path: &Path, is_video:
fn probe_encoder(state: Arc<Mutex<State>>, enc: gst::Element) {
enc.static_pad("src").unwrap().add_probe(
gst::PadProbeType::EVENT_DOWNSTREAM,
move |_pad, info| match info.data {
Some(gst::PadProbeData::Event(ref ev)) => match ev.view() {
gst::EventView::Caps(e) => {
let mime = gst_pbutils::codec_utils_caps_get_mime_codec(e.caps());
move |_pad, info| {
let Some(ev) = info.event() else {
return gst::PadProbeReturn::Ok;
};
let gst::EventView::Caps(ev) = ev.view() else {
return gst::PadProbeReturn::Ok;
};
let mut state = state.lock().unwrap();
state.all_mimes.push(mime.unwrap().into());
state.maybe_write_manifest();
gst::PadProbeReturn::Remove
}
_ => gst::PadProbeReturn::Ok,
},
_ => gst::PadProbeReturn::Ok,
let mime = gst_pbutils::codec_utils_caps_get_mime_codec(ev.caps());
let mut state = state.lock().unwrap();
state.all_mimes.push(mime.unwrap().into());
state.maybe_write_manifest();
gst::PadProbeReturn::Remove
},
);
}

View file

@ -260,19 +260,21 @@ fn setup_appsink(appsink: &gst_app::AppSink, name: &str, path: &Path, is_video:
fn probe_encoder(state: Arc<Mutex<State>>, enc: gst::Element) {
enc.static_pad("src").unwrap().add_probe(
gst::PadProbeType::EVENT_DOWNSTREAM,
move |_pad, info| match info.data {
Some(gst::PadProbeData::Event(ref ev)) => match ev.view() {
gst::EventView::Caps(e) => {
let mime = gst_pbutils::codec_utils_caps_get_mime_codec(e.caps());
move |_pad, info| {
let Some(ev) = info.event() else {
return gst::PadProbeReturn::Ok;
};
let gst::EventView::Caps(ev) = ev.view() else {
return gst::PadProbeReturn::Ok;
};
let mut state = state.lock().unwrap();
state.all_mimes.push(mime.unwrap().into());
state.maybe_write_manifest();
gst::PadProbeReturn::Remove
}
_ => gst::PadProbeReturn::Ok,
},
_ => gst::PadProbeReturn::Ok,
let mime = gst_pbutils::codec_utils_caps_get_mime_codec(ev.caps());
let mut state = state.lock().unwrap();
state.all_mimes.push(mime.unwrap().into());
state.maybe_write_manifest();
gst::PadProbeReturn::Remove
},
);
}

View file

@ -2827,7 +2827,7 @@ impl ObjectImpl for FMP4Mux {
vec![
glib::ParamSpecUInt64::builder("fragment-duration")
.nick("Fragment Duration")
.blurb("Duration for each FMP4 fragment")
.blurb("Duration for each FMP4 fragment in nanoseconds")
.default_value(DEFAULT_FRAGMENT_DURATION.nseconds())
.mutable_ready()
.build(),

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-mp4"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
license = "MPL-2.0"
description = "GStreamer Rust MP4 Plugin"
@ -10,11 +10,11 @@ rust-version = "1.70"
[dependencies]
anyhow = "1"
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
[lib]
name = "gstmp4"
@ -26,7 +26,7 @@ tempfile = "3"
url = "2"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
default = []

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-aws"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Arun Raghavan <arun@arunraghavan.net>",
"Jordan Petridis <jordan@centricular.com>",
"Mathieu Duponchelle <mathieu@centricular.com>"]
@ -13,22 +13,18 @@ rust-version = "1.70"
[dependencies]
async-stream = "0.3.4"
base32 = "0.4"
aws-config = "0.56.0"
aws-sdk-s3 = "0.29.0"
aws-sdk-transcribestreaming = "0.29.0"
aws-sdk-translate = "0.29.0"
aws-types = "0.56.0"
aws-credential-types = "0.56.0"
aws-sig-auth = "0.56.0"
aws-smithy-http = { version = "0.56.0", features = [ "rt-tokio" ] }
aws-smithy-types = "0.56.0"
aws-config = "1.0"
aws-sdk-s3 = "1.0"
aws-sdk-transcribestreaming = "1.0"
aws-sdk-translate = "1.0"
aws-types = "1.0"
aws-credential-types = "1.0"
bytes = "1.0"
futures = "0.3"
gio = { git = "https://github.com/gtk-rs/gtk-rs-core.git", package = "gio" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
http = "0.2.7"
gio = { git = "https://github.com/gtk-rs/gtk-rs-core.git", branch = "0.18", version = "0.18", package = "gio" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_16"] }
percent-encoding = "2"
tokio = { version = "1.0", features = [ "full" ] }
serde = "1"
@ -39,9 +35,9 @@ url = "2"
[dev-dependencies]
chrono = { version = "0.4", features = [ "alloc" ] }
env_logger = "0.10"
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
rand = "0.8"
test-with = { version = "0.10", default-features = false }
test-with = { version = "0.12", default-features = false }
[lib]
name = "gstaws"
@ -49,7 +45,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -642,9 +642,8 @@ impl ObjectImpl for S3HlsSink {
self.hlssink.connect("get-playlist-stream", false, {
let self_weak = self.downgrade();
move |args| -> Option<glib::Value> {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return None,
let Some(self_) = self_weak.upgrade() else {
return None;
};
let s3client = self_.s3client_from_settings();
@ -677,9 +676,8 @@ impl ObjectImpl for S3HlsSink {
self.hlssink.connect("get-fragment-stream", false, {
let self_weak = self.downgrade();
move |args| -> Option<glib::Value> {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return None,
let Some(self_) = self_weak.upgrade() else {
return None;
};
let s3client = self_.s3client_from_settings();
@ -712,9 +710,8 @@ impl ObjectImpl for S3HlsSink {
self.hlssink.connect("delete-fragment", false, {
let self_weak = self.downgrade();
move |args| -> Option<glib::Value> {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return None,
let Some(self_) = self_weak.upgrade() else {
return None;
};
let s3_client = self_.s3client_from_settings();

View file

@ -9,6 +9,7 @@
use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst_base::prelude::*;
use gst_base::subclass::prelude::*;
use aws_sdk_s3::{
@ -116,12 +117,13 @@ struct Settings {
impl Settings {
fn to_uri(&self) -> String {
format!(
"s3://{}/{}/{}",
self.region,
self.bucket.as_ref().unwrap(),
self.key.as_ref().unwrap()
)
GstS3Url {
region: self.region.clone(),
bucket: self.bucket.clone().unwrap(),
object: self.key.clone().unwrap(),
version: None,
}
.to_string()
}
fn to_metadata(&self, imp: &S3Sink) -> Option<HashMap<String, String>> {
@ -654,6 +656,12 @@ impl ObjectSubclass for S3Sink {
}
impl ObjectImpl for S3Sink {
fn constructed(&self) {
self.parent_constructed();
self.obj().set_sync(false);
}
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![

View file

@ -40,7 +40,7 @@ enum StreamingState {
Started {
url: GstS3Url,
client: Client,
size: u64,
size: Option<u64>,
},
}
@ -168,7 +168,11 @@ impl S3Src {
}
}
fn head(self: &S3Src, client: &Client, url: &GstS3Url) -> Result<u64, gst::ErrorMessage> {
fn head(
self: &S3Src,
client: &Client,
url: &GstS3Url,
) -> Result<Option<u64>, gst::ErrorMessage> {
let head_object = client
.head_object()
.set_bucket(Some(url.bucket.clone()))
@ -193,11 +197,11 @@ impl S3Src {
gst::info!(
CAT,
imp: self,
"HEAD success, content length = {}",
"HEAD success, content length = {:?}",
output.content_length
);
Ok(output.content_length as u64)
Ok(output.content_length.map(|size| size as u64))
}
/* Returns the bytes, Some(error) if one occurred, or a None error if interrupted */
@ -244,7 +248,7 @@ impl S3Src {
WaitError::Cancelled => None,
})?;
gst::debug!(CAT, imp: self, "Read {} bytes", output.content_length);
gst::debug!(CAT, imp: self, "Read {:?} bytes", output.content_length);
s3utils::wait_stream(&self.canceller, &mut output.body).map_err(|err| match err {
WaitError::FutureError(err) => Some(gst::error_msg!(
@ -461,7 +465,7 @@ impl BaseSrcImpl for S3Src {
let state = self.state.lock().unwrap();
match *state {
StreamingState::Stopped => None,
StreamingState::Started { size, .. } => Some(size),
StreamingState::Started { size, .. } => size,
}
}

View file

@ -83,15 +83,21 @@ pub fn parse_s3_url(url_str: &str) -> Result<GstS3Url, String> {
.next()
.ok_or_else(|| format!("Invalid empty object/bucket '{url}'"))?;
let mut object = percent_decode(o.as_bytes())
.decode_utf8()
.unwrap()
.to_string();
if o.is_empty() {
return Err(format!("Invalid empty object/bucket '{url}'"));
}
object = path.fold(object, |o, p| format!("{o}/{p}"));
let mut object = percent_decode(o.as_bytes())
.decode_utf8()
.unwrap()
.to_string();
object = path.fold(object, |o, p| {
format!(
"{o}/{}",
percent_decode(p.as_bytes()).decode_utf8().unwrap()
)
});
let mut q = url.query_pairs();
let v = q.next();

View file

@ -7,13 +7,13 @@
// SPDX-License-Identifier: MPL-2.0
use aws_config::meta::region::RegionProviderChain;
use aws_sdk_s3::config::{timeout::TimeoutConfig, Credentials, Region};
use aws_sdk_s3::{
config::{timeout::TimeoutConfig, Credentials, Region},
primitives::{ByteStream, ByteStreamError},
};
use aws_types::sdk_config::SdkConfig;
use aws_smithy_http::byte_stream::{error::Error, ByteStream};
use bytes::{buf::BufMut, Bytes, BytesMut};
use futures::stream::TryStreamExt;
use futures::{future, Future};
use gst::glib::once_cell::sync::Lazy;
use std::sync::Mutex;
@ -81,7 +81,7 @@ where
pub fn wait_stream(
canceller: &Mutex<Option<future::AbortHandle>>,
stream: &mut ByteStream,
) -> Result<Bytes, WaitError<Error>> {
) -> Result<Bytes, WaitError<ByteStreamError>> {
wait(canceller, async move {
let mut collect = BytesMut::new();
@ -90,7 +90,7 @@ pub fn wait_stream(
collect.put(item)
}
Ok::<Bytes, Error>(collect.freeze())
Ok::<Bytes, ByteStreamError>(collect.freeze())
})
}
@ -106,17 +106,17 @@ pub fn wait_config(
region: Region,
timeout_config: TimeoutConfig,
credentials: Option<Credentials>,
) -> Result<SdkConfig, WaitError<Error>> {
) -> Result<SdkConfig, WaitError<ByteStreamError>> {
let region_provider = RegionProviderChain::first_try(region)
.or_default_provider()
.or_else(Region::new(DEFAULT_S3_REGION));
let config_future = match credentials {
Some(cred) => aws_config::from_env()
Some(cred) => aws_config::defaults(aws_config::BehaviorVersion::latest())
.timeout_config(timeout_config)
.region(region_provider)
.credentials_provider(cred)
.load(),
None => aws_config::from_env()
None => aws_config::defaults(aws_config::BehaviorVersion::latest())
.timeout_config(timeout_config)
.region(region_provider)
.load(),

View file

@ -557,7 +557,7 @@ impl Transcriber {
}
_ => {
gst::debug!(CAT, imp: self, "Attempting to get credentials from env...");
aws_config::from_env()
aws_config::defaults(aws_config::BehaviorVersion::latest())
}
};
@ -1719,7 +1719,9 @@ impl TranslateSrcPad {
.to_stream_time(state.out_segment.position())
};
let Some(stream_time) = stream_time else { return false };
let Some(stream_time) = stream_time else {
return false;
};
q.set(stream_time);
true

View file

@ -261,7 +261,9 @@ impl TranscriberStream {
let discont_offset = self.discont_offset_tracker.lock().unwrap().discont_offset;
let Some(item) = TranscriptItem::from(item, self.lateness, discont_offset) else { continue };
let Some(item) = TranscriptItem::from(item, self.lateness, discont_offset) else {
continue;
};
gst::debug!(
CAT,
imp: self.imp,

View file

@ -85,12 +85,8 @@ impl TranslateLoop {
let found_output_lang = language_list
.languages()
.and_then(|langs| {
langs
.iter()
.find(|lang| lang.language_code() == Some(&self.output_lang))
})
.is_some();
.iter()
.any(|lang| lang.language_code() == self.output_lang);
if !found_output_lang {
let err = format!("Unknown output languages: {}", self.output_lang);
@ -151,8 +147,7 @@ impl TranslateLoop {
gst::info!(CAT, imp: self.pad, "{err}");
gst::error_msg!(gst::LibraryError::Failed, ["{err}"])
})?
.translated_text
.unwrap_or_default();
.translated_text;
gst::debug!(CAT, imp: self.pad, "Got translation {translated_text}");

View file

@ -8,72 +8,93 @@
// SPDX-License-Identifier: MPL-2.0
//
use gst::prelude::*;
const DEFAULT_S3_REGION: &str = "us-west-2";
fn init() {
use std::sync::Once;
static INIT: Once = Once::new();
INIT.call_once(|| {
gst::init().unwrap();
gstaws::plugin_register_static().unwrap();
});
}
// The test times out on Windows for some reason, skip until we figure out why
#[cfg(not(target_os = "windows"))]
#[test_with::env(AWS_ACCESS_KEY_ID)]
#[test_with::env(AWS_SECRET_ACCESS_KEY)]
#[tokio::test]
async fn test_s3() {
init();
// Makes it easier to get AWS SDK logs if needed
env_logger::init();
#[cfg(test)]
mod tests {
use gst::prelude::*;
let region = std::env::var("AWS_REGION").unwrap_or_else(|_| DEFAULT_S3_REGION.to_string());
let bucket =
std::env::var("AWS_S3_BUCKET").unwrap_or_else(|_| "gst-plugins-rs-tests".to_string());
let key = format!("s3-test-{:?}.txt", chrono::Utc::now());
let uri = format!("s3://{region}/{bucket}/{key}");
let content = "Hello, world!\n".as_bytes();
const DEFAULT_S3_REGION: &str = "us-west-2";
// Manually add the element so we can configure it before it goes to PLAYING
let mut h1 = gst_check::Harness::new_empty();
// Need to add_parse() because the Harness API / Rust bindings aren't conducive to creating and
// adding an element manually
h1.add_parse(format!("awss3sink uri={uri}").as_str());
fn init() {
use std::sync::Once;
static INIT: Once = Once::new();
h1.set_src_caps(gst::Caps::builder("text/plain").build());
h1.play();
INIT.call_once(|| {
gst::init().unwrap();
gstaws::plugin_register_static().unwrap();
// Makes it easier to get AWS SDK logs if needed
env_logger::init();
});
}
h1.push(gst::Buffer::from_slice(content)).unwrap();
h1.push_event(gst::event::Eos::new());
// Common helper
async fn do_s3_test(key_prefix: &str) {
init();
let mut h2 = gst_check::Harness::new("awss3src");
h2.element().unwrap().set_property("uri", uri.clone());
h2.play();
let region = std::env::var("AWS_REGION").unwrap_or_else(|_| DEFAULT_S3_REGION.to_string());
let bucket =
std::env::var("AWS_S3_BUCKET").unwrap_or_else(|_| "gst-plugins-rs-tests".to_string());
let key = format!("{key_prefix}-{:?}.txt", chrono::Utc::now());
let uri = format!("s3://{region}/{bucket}/{key}");
let content = "Hello, world!\n".as_bytes();
let buf = h2.pull_until_eos().unwrap().unwrap();
assert_eq!(
content,
buf.into_mapped_buffer_readable().unwrap().as_slice()
);
// Manually add the element so we can configure it before it goes to PLAYING
let mut h1 = gst_check::Harness::new_empty();
// Need to add_parse() because the Harness API / Rust bindings aren't conducive to creating and
// adding an element manually
h1.add_parse(format!("awss3sink uri=\"{uri}\"").as_str());
let region_provider = aws_config::meta::region::RegionProviderChain::first_try(
aws_sdk_s3::config::Region::new(region.clone()),
)
.or_default_provider();
h1.set_src_caps(gst::Caps::builder("text/plain").build());
h1.play();
let config = aws_config::from_env().region(region_provider).load().await;
let client = aws_sdk_s3::Client::new(&config);
h1.push(gst::Buffer::from_slice(content)).unwrap();
h1.push_event(gst::event::Eos::new());
client
.delete_object()
.bucket(bucket)
.key(key)
.send()
.await
.unwrap();
let mut h2 = gst_check::Harness::new("awss3src");
h2.element().unwrap().set_property("uri", uri.clone());
h2.play();
let buf = h2.pull_until_eos().unwrap().unwrap();
assert_eq!(
content,
buf.into_mapped_buffer_readable().unwrap().as_slice()
);
let region_provider = aws_config::meta::region::RegionProviderChain::first_try(
aws_sdk_s3::config::Region::new(region.clone()),
)
.or_default_provider();
let config = aws_config::defaults(aws_config::BehaviorVersion::latest())
.region(region_provider)
.load()
.await;
let client = aws_sdk_s3::Client::new(&config);
client
.delete_object()
.bucket(bucket)
.key(key)
.send()
.await
.unwrap();
}
#[tokio::test]
async fn test_s3_simple() {
do_s3_test("s3-test").await;
}
#[tokio::test]
async fn test_s3_whitespace() {
do_s3_test("s3 test").await;
}
#[tokio::test]
async fn test_s3_unicode() {
do_s3_test("s3 🧪 😱").await;
}
}

View file

@ -2,27 +2,26 @@
name = "gst-plugin-hlssink3"
description = "GStreamer HLS (HTTP Live Streaming) Plugin"
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Rafael Caricio <rafael@caricio.com>"]
edition = "2021"
license = "MPL-2.0"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
glib = { git = "https://github.com/gtk-rs/gtk-rs-core" }
gio = { git = "https://github.com/gtk-rs/gtk-rs-core" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18" }
once_cell = "1.7.2"
m3u8-rs = "5.0"
regex = "1"
chrono = "0.4"
sprintf = "0.1.3"
[dev-dependencies]
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[build-dependencies]
gst-plugin-version-helper = { path = "../../version-helper" }
gst-plugin-version-helper = { path = "../../version-helper", version = "0.8" }
[lib]
name = "gsthlssink3"

View file

@ -6,10 +6,11 @@
//
// SPDX-License-Identifier: MPL-2.0
use crate::playlist::{Playlist, SegmentFormatter};
use crate::playlist::Playlist;
use crate::HlsSink3PlaylistType;
use chrono::{DateTime, Duration, Utc};
use gio::prelude::*;
use glib::subclass::prelude::*;
use gst::glib;
use gst::glib::once_cell::sync::Lazy;
use gst::prelude::*;
use gst::subclass::prelude::*;
@ -17,7 +18,7 @@ use m3u8_rs::MediaPlaylistType;
use std::fs;
use std::io::Write;
use std::path;
use std::sync::{Arc, Mutex};
use std::sync::Mutex;
const DEFAULT_LOCATION: &str = "segment%05d.ts";
const DEFAULT_PLAYLIST_LOCATION: &str = "playlist.m3u8";
@ -26,6 +27,8 @@ const DEFAULT_TARGET_DURATION: u32 = 15;
const DEFAULT_PLAYLIST_LENGTH: u32 = 5;
const DEFAULT_PLAYLIST_TYPE: HlsSink3PlaylistType = HlsSink3PlaylistType::Unspecified;
const DEFAULT_I_FRAMES_ONLY_PLAYLIST: bool = false;
const DEFAULT_PROGRAM_DATE_TIME_TAG: bool = false;
const DEFAULT_CLOCK_TRACKING_FOR_PDT: bool = true;
const DEFAULT_SEND_KEYFRAME_REQUESTS: bool = true;
const SIGNAL_GET_PLAYLIST_STREAM: &str = "get-playlist-stream";
@ -60,7 +63,6 @@ impl From<Option<&MediaPlaylistType>> for HlsSink3PlaylistType {
struct Settings {
location: String,
segment_formatter: SegmentFormatter,
playlist_location: String,
playlist_root: Option<String>,
playlist_length: u32,
@ -68,6 +70,8 @@ struct Settings {
max_num_segment_files: usize,
target_duration: u32,
i_frames_only: bool,
enable_program_date_time: bool,
pdt_follows_pipeline_clock: bool,
send_keyframe_requests: bool,
splitmuxsink: gst::Element,
@ -86,9 +90,21 @@ impl Default for Settings {
.name("giostream_sink")
.build()
.expect("Could not make element giostreamsink");
// giostreamsink doesn't let go of its stream until the element is finalized, which might
// be too late for the calling application. Let's try to force it to close while tearing
// down the pipeline.
if giostreamsink.has_property("close-on-stop", Some(bool::static_type())) {
giostreamsink.set_property("close-on-stop", true);
} else {
gst::warning!(
CAT,
"hlssink3 may sometimes fail to write out the final playlist update. This can be fixed by using giostreamsink from GStreamer 1.24 or later."
)
}
Self {
location: String::from(DEFAULT_LOCATION),
segment_formatter: SegmentFormatter::new(DEFAULT_LOCATION).unwrap(),
playlist_location: String::from(DEFAULT_PLAYLIST_LOCATION),
playlist_root: None,
playlist_length: DEFAULT_PLAYLIST_LENGTH,
@ -97,6 +113,8 @@ impl Default for Settings {
target_duration: DEFAULT_TARGET_DURATION,
send_keyframe_requests: DEFAULT_SEND_KEYFRAME_REQUESTS,
i_frames_only: DEFAULT_I_FRAMES_ONLY_PLAYLIST,
enable_program_date_time: DEFAULT_PROGRAM_DATE_TIME_TAG,
pdt_follows_pipeline_clock: DEFAULT_CLOCK_TRACKING_FOR_PDT,
splitmuxsink,
giostreamsink,
@ -106,49 +124,25 @@ impl Default for Settings {
}
}
pub(crate) struct StartedState {
struct PlaylistContext {
pdt_base_utc: Option<DateTime<Utc>>,
pdt_base_running_time: Option<gst::ClockTime>,
playlist: Playlist,
fragment_opened_at: Option<gst::ClockTime>,
fragment_running_time: Option<gst::ClockTime>,
current_segment_location: Option<String>,
old_segment_locations: Vec<String>,
}
impl StartedState {
fn new(
target_duration: f32,
playlist_type: Option<MediaPlaylistType>,
i_frames_only: bool,
) -> Self {
Self {
playlist: Playlist::new(target_duration, playlist_type, i_frames_only),
current_segment_location: None,
fragment_opened_at: None,
old_segment_locations: Vec::new(),
}
}
fn fragment_duration_since(&self, fragment_closed: gst::ClockTime) -> f32 {
let segment_duration = fragment_closed - self.fragment_opened_at.unwrap();
segment_duration.mseconds() as f32 / 1_000f32
}
}
#[allow(clippy::large_enum_variant)]
enum State {
Stopped,
Started(StartedState),
}
impl Default for State {
fn default() -> Self {
Self::Stopped
}
#[derive(Default)]
struct State {
context: Option<PlaylistContext>,
}
#[derive(Default)]
pub struct HlsSink3 {
settings: Arc<Mutex<Settings>>,
state: Arc<Mutex<State>>,
settings: Mutex<Settings>,
state: Mutex<State>,
}
impl HlsSink3 {
@ -165,12 +159,24 @@ impl HlsSink3 {
};
let mut state = self.state.lock().unwrap();
if let State::Stopped = *state {
*state = State::Started(StartedState::new(
target_duration,
playlist_type,
i_frames_only,
));
state.context = Some(PlaylistContext {
pdt_base_utc: None,
pdt_base_running_time: None,
playlist: Playlist::new(target_duration, playlist_type, i_frames_only),
fragment_opened_at: None,
fragment_running_time: None,
current_segment_location: None,
old_segment_locations: Vec::new(),
});
}
fn stop(&self) {
let mut state = self.state.lock().unwrap();
if let Some(mut context) = state.context.take() {
if context.playlist.is_rendering() {
context.playlist.stop();
let _ = self.write_playlist(&mut context);
}
}
}
@ -182,15 +188,33 @@ impl HlsSink3 {
fragment_id
);
// TODO: Create method in state to simplify this boilerplate: `let state = self.state.started()?`
let mut state_guard = self.state.lock().unwrap();
let state = match &mut *state_guard {
State::Stopped => return Err("Not in Started state".to_string()),
State::Started(s) => s,
let mut state = self.state.lock().unwrap();
let context = match state.context.as_mut() {
Some(context) => context,
None => {
gst::error!(
CAT,
imp: self,
"Playlist is not configured",
);
return Err(String::from("Playlist is not configured"));
}
};
let settings = self.settings.lock().unwrap();
let segment_file_location = settings.segment_formatter.segment(fragment_id);
let segment_file_location = match sprintf::sprintf!(&settings.location, fragment_id) {
Ok(file_name) => file_name,
Err(err) => {
gst::error!(
CAT,
imp: self,
"Couldn't build file name, err: {:?}", err,
);
return Err(String::from("Invalid init segment file pattern"));
}
};
gst::trace!(
CAT,
imp: self,
@ -198,7 +222,7 @@ impl HlsSink3 {
segment_file_location
);
state.current_segment_location = Some(segment_file_location.clone());
context.current_segment_location = Some(segment_file_location.clone());
let fragment_stream = self
.obj()
@ -216,7 +240,7 @@ impl HlsSink3 {
CAT,
imp: self,
"New segment location: {:?}",
state.current_segment_location.as_ref()
context.current_segment_location.as_ref()
);
Ok(segment_file_location)
}
@ -254,29 +278,60 @@ impl HlsSink3 {
});
}
fn write_playlist(
&self,
fragment_closed_at: Option<gst::ClockTime>,
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
gst::info!(CAT, imp: self, "Preparing to write new playlist");
let mut state_guard = self.state.lock().unwrap();
let state = match &mut *state_guard {
State::Stopped => return Err(gst::StateChangeError),
State::Started(s) => s,
fn on_fragment_closed(&self, closed_at: gst::ClockTime, date_time: Option<DateTime<Utc>>) {
let mut state = self.state.lock().unwrap();
let context = match state.context.as_mut() {
Some(context) => context,
None => {
gst::error!(CAT, imp: self, "Playlist is not configured");
return;
}
};
gst::info!(CAT, imp: self, "COUNT {}", state.playlist.len());
let location = match context.current_segment_location.take() {
Some(location) => location,
None => {
gst::error!(CAT, imp: self, "Unknown segment location");
return;
}
};
// Only add fragment if it's complete.
if let Some(fragment_closed) = fragment_closed_at {
let segment_filename = self.segment_filename(state);
state.playlist.add_segment(
segment_filename.clone(),
state.fragment_duration_since(fragment_closed),
);
state.old_segment_locations.push(segment_filename);
}
let opened_at = match context.fragment_opened_at.take() {
Some(opened_at) => opened_at,
None => {
gst::error!(CAT, imp: self, "Unknown segment duration");
return;
}
};
let duration = ((closed_at - opened_at).mseconds() as f32) / 1_000f32;
let file_name = path::Path::new(&location)
.file_name()
.unwrap()
.to_str()
.unwrap();
let settings = self.settings.lock().unwrap();
let segment_file_name = if let Some(playlist_root) = &settings.playlist_root {
format!("{playlist_root}/{file_name}")
} else {
file_name.to_string()
};
drop(settings);
context
.playlist
.add_segment(segment_file_name, duration, date_time);
context.old_segment_locations.push(location);
let _ = self.write_playlist(context);
}
fn write_playlist(
&self,
context: &mut PlaylistContext,
) -> Result<gst::FlowSuccess, gst::FlowError> {
gst::info!(CAT, imp: self, "Preparing to write new playlist, COUNT {}", context.playlist.len());
let (playlist_location, max_num_segments, max_playlist_length) = {
let settings = self.settings.lock().unwrap();
@ -287,7 +342,7 @@ impl HlsSink3 {
)
};
state.playlist.update_playlist_state(max_playlist_length);
context.playlist.update_playlist_state(max_playlist_length);
// Acquires the playlist file handle so we can update it with new content. By default, this
// is expected to be the same file every time.
@ -303,11 +358,11 @@ impl HlsSink3 {
imp: self,
"Could not get stream to write playlist content",
);
gst::StateChangeError
gst::FlowError::Error
})?
.into_write();
state
context
.playlist
.write_to(&mut playlist_stream)
.map_err(|err| {
@ -317,7 +372,7 @@ impl HlsSink3 {
"Could not write new playlist: {}",
err.to_string()
);
gst::StateChangeError
gst::FlowError::Error
})?;
playlist_stream.flush().map_err(|err| {
gst::error!(
@ -326,14 +381,14 @@ impl HlsSink3 {
"Could not flush playlist: {}",
err.to_string()
);
gst::StateChangeError
gst::FlowError::Error
})?;
if state.playlist.is_type_undefined() {
if context.playlist.is_type_undefined() && max_num_segments > 0 {
// Cleanup old segments from filesystem
if state.old_segment_locations.len() > max_num_segments {
for _ in 0..state.old_segment_locations.len() - max_num_segments {
let old_segment_location = state.old_segment_locations.remove(0);
if context.old_segment_locations.len() > max_num_segments {
for _ in 0..context.old_segment_locations.len() - max_num_segments {
let old_segment_location = context.old_segment_locations.remove(0);
if !self
.obj()
.emit_by_name::<bool>(SIGNAL_DELETE_FRAGMENT, &[&old_segment_location])
@ -345,35 +400,7 @@ impl HlsSink3 {
}
gst::debug!(CAT, imp: self, "Wrote new playlist file!");
Ok(gst::StateChangeSuccess::Success)
}
fn segment_filename(&self, state: &mut StartedState) -> String {
assert!(state.current_segment_location.is_some());
let segment_filename = path_basename(state.current_segment_location.take().unwrap());
let settings = self.settings.lock().unwrap();
if let Some(playlist_root) = &settings.playlist_root {
format!("{playlist_root}/{segment_filename}")
} else {
segment_filename
}
}
fn write_final_playlist(&self) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
gst::debug!(CAT, imp: self, "Preparing to write final playlist");
self.write_playlist(None)
}
fn stop(&self) {
gst::debug!(CAT, imp: self, "Stopping");
let mut state = self.state.lock().unwrap();
if let State::Started(_) = *state {
*state = State::Stopped;
}
gst::debug!(CAT, imp: self, "Stopped");
Ok(gst::FlowSuccess::Ok)
}
}
@ -406,18 +433,76 @@ impl BinImpl for HlsSink3 {
if let Ok(new_fragment_opened_at) = s.get::<gst::ClockTime>("running-time")
{
let mut state = self.state.lock().unwrap();
match &mut *state {
State::Stopped => {}
State::Started(state) => {
state.fragment_opened_at = Some(new_fragment_opened_at)
}
};
if let Some(context) = state.context.as_mut() {
context.fragment_opened_at = Some(new_fragment_opened_at);
}
}
}
"splitmuxsink-fragment-closed" => {
let s = msg.structure().unwrap();
let settings = self.settings.lock().unwrap();
let mut state = self.state.lock().unwrap();
let context = match state.context.as_mut() {
Some(context) => context,
None => {
gst::error!(
CAT,
imp: self,
"Playlist is not configured",
);
return;
}
};
let fragment_pts = context
.fragment_running_time
.expect("fragment running time must be set by format-location-full");
if context.pdt_base_running_time.is_none() {
context.pdt_base_running_time = context.fragment_running_time;
}
// Calculate the mapping from running time to UTC
// calculate pdt_base_utc for each segment for !pdt_follows_pipeline_clock
// when pdt_follows_pipeline_clock is set, we calculate the base time every time
// this avoids the drift between pdt tag and external clock (if gst clock has skew w.r.t external clock)
if context.pdt_base_utc.is_none() || !settings.pdt_follows_pipeline_clock {
let now_utc = Utc::now();
let now_gst = settings.giostreamsink.clock().unwrap().time().unwrap();
let pts_clock_time =
fragment_pts + settings.giostreamsink.base_time().unwrap();
let diff = now_gst.checked_sub(pts_clock_time).expect("time between fragments running time and current running time overflow");
let pts_utc = now_utc
.checked_sub_signed(Duration::nanoseconds(diff.nseconds() as i64))
.expect("offsetting the utc with gstreamer clock-diff overflow");
context.pdt_base_utc = Some(pts_utc);
}
let fragment_date_time = if settings.enable_program_date_time
&& context.pdt_base_running_time.is_some()
{
// Add the diff of running time to UTC time
// date_time = first_segment_utc + (current_seg_running_time - first_seg_running_time)
context
.pdt_base_utc
.unwrap()
.checked_add_signed(Duration::nanoseconds(
context
.fragment_running_time
.opt_checked_sub(context.pdt_base_running_time)
.unwrap()
.unwrap()
.nseconds() as i64,
))
} else {
None
};
drop(state);
drop(settings);
if let Ok(fragment_closed_at) = s.get::<gst::ClockTime>("running-time") {
let _ = self.write_playlist(Some(fragment_closed_at));
self.on_fragment_closed(fragment_closed_at, fragment_date_time);
}
}
_ => {}
@ -469,6 +554,16 @@ impl ObjectImpl for HlsSink3 {
.blurb("Each video segments is single iframe, So put EXT-X-I-FRAMES-ONLY tag in the playlist")
.default_value(DEFAULT_I_FRAMES_ONLY_PLAYLIST)
.build(),
glib::ParamSpecBoolean::builder("enable-program-date-time")
.nick("add EXT-X-PROGRAM-DATE-TIME tag")
.blurb("put EXT-X-PROGRAM-DATE-TIME tag in the playlist")
.default_value(DEFAULT_PROGRAM_DATE_TIME_TAG)
.build(),
glib::ParamSpecBoolean::builder("pdt-follows-pipeline-clock")
.nick("Whether Program-Date-Time should follow the pipeline clock")
.blurb("As there might be drift between the wallclock and pipeline clock, this controls whether the Program-Date-Time markers should follow the pipeline clock rate (true), or be skewed to match the wallclock rate (false).")
.default_value(DEFAULT_CLOCK_TRACKING_FOR_PDT)
.build(),
glib::ParamSpecBoolean::builder("send-keyframe-requests")
.nick("Send Keyframe Requests")
.blurb("Send keyframe requests to ensure correct fragmentation. If this is disabled then the input must have keyframes in regular intervals.")
@ -488,9 +583,6 @@ impl ObjectImpl for HlsSink3 {
.get::<Option<String>>()
.expect("type checked upstream")
.unwrap_or_else(|| DEFAULT_LOCATION.into());
settings.segment_formatter = SegmentFormatter::new(&settings.location).expect(
"A string containing `%03d` pattern must be used (can be any number from 0-9)",
);
settings
.splitmuxsink
.set_property("location", &settings.location);
@ -536,6 +628,12 @@ impl ObjectImpl for HlsSink3 {
);
}
}
"enable-program-date-time" => {
settings.enable_program_date_time = value.get().expect("type checked upstream");
}
"pdt-follows-pipeline-clock" => {
settings.pdt_follows_pipeline_clock = value.get().expect("type checked upstream");
}
"send-keyframe-requests" => {
settings.send_keyframe_requests = value.get().expect("type checked upstream");
settings
@ -563,6 +661,8 @@ impl ObjectImpl for HlsSink3 {
playlist_type.to_value()
}
"i-frames-only" => settings.i_frames_only.to_value(),
"enable-program-date-time" => settings.enable_program_date_time.to_value(),
"pdt-follows-pipeline-clock" => settings.pdt_follows_pipeline_clock.to_value(),
"send-keyframe-requests" => settings.send_keyframe_requests.to_value(),
_ => unimplemented!(),
}
@ -660,27 +760,59 @@ impl ObjectImpl for HlsSink3 {
]);
obj.add(&settings.splitmuxsink).unwrap();
settings
.splitmuxsink
.connect("format-location-full", false, {
let imp_weak = self.downgrade();
move |args| {
let Some(imp) = imp_weak.upgrade() else {
return Some(None::<String>.to_value());
};
let fragment_id = args[1].get::<u32>().unwrap();
gst::info!(CAT, imp: imp, "Got fragment-id: {}", fragment_id);
settings.splitmuxsink.connect("format-location", false, {
let self_weak = self.downgrade();
move |args| {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return Some(None::<String>.to_value()),
};
let fragment_id = args[1].get::<u32>().unwrap();
let mut state = imp.state.lock().unwrap();
let context = match state.context.as_mut() {
Some(context) => context,
None => {
gst::error!(
CAT,
imp: imp,
"on format location called with Stopped state"
);
return Some("unknown_segment".to_value());
}
};
gst::info!(CAT, imp: self_, "Got fragment-id: {}", fragment_id);
let sample = args[2].get::<gst::Sample>().unwrap();
let buffer = sample.buffer();
if let Some(buffer) = buffer {
let segment = sample
.segment()
.expect("segment not available")
.downcast_ref::<gst::ClockTime>()
.expect("no time segment");
context.fragment_running_time =
segment.to_running_time(buffer.pts().unwrap());
} else {
gst::warning!(
CAT,
imp: imp,
"buffer null for fragment-id: {}",
fragment_id
);
}
drop(state);
match self_.on_format_location(fragment_id) {
Ok(segment_location) => Some(segment_location.to_value()),
Err(err) => {
gst::error!(CAT, imp: self_, "on format-location handler: {}", err);
Some("unknown_segment".to_value())
match imp.on_format_location(fragment_id) {
Ok(segment_location) => Some(segment_location.to_value()),
Err(err) => {
gst::error!(CAT, imp: imp, "on format-location handler: {}", err);
Some("unknown_segment".to_value())
}
}
}
}
});
});
}
}
@ -732,35 +864,24 @@ impl ElementImpl for HlsSink3 {
&self,
transition: gst::StateChange,
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
if let gst::StateChange::NullToReady = transition {
if transition == gst::StateChange::ReadyToPaused {
self.start();
}
let ret = self.parent_change_state(transition)?;
match transition {
gst::StateChange::PausedToReady => {
let write_final = {
let mut state = self.state.lock().unwrap();
match &mut *state {
State::Stopped => false,
State::Started(state) => {
if state.playlist.is_rendering() {
state.playlist.stop();
true
} else {
false
}
}
}
};
if write_final {
// Don't fail transitioning to READY if this fails
let _ = self.write_final_playlist();
gst::StateChange::PlayingToPaused => {
let mut state = self.state.lock().unwrap();
if let Some(context) = state.context.as_mut() {
// reset mapping from rt to utc. during pause
// rt is stopped but utc keep moving so need to
// calculate the mapping again
context.pdt_base_running_time = None;
context.pdt_base_utc = None
}
}
gst::StateChange::ReadyToNull => {
gst::StateChange::PausedToReady => {
self.stop();
}
_ => (),
@ -856,26 +977,3 @@ impl ElementImpl for HlsSink3 {
}
}
}
/// The content of the last item of a path separated by `/` character.
fn path_basename(name: impl AsRef<str>) -> String {
name.as_ref().split('/').last().unwrap().to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_extract_basenames() {
for (input, output) in [
("", ""),
("value", "value"),
("/my/nice/path.ts", "path.ts"),
("file.ts", "file.ts"),
("https://localhost/output/file.vtt", "file.vtt"),
] {
assert_eq!(path_basename(input), output);
}
}
}

View file

@ -12,8 +12,7 @@
*
* Since: plugins-rs-0.8.0
*/
use glib::prelude::*;
#[cfg(feature = "doc")]
use gst::glib;
use gst::prelude::*;
mod imp;

View file

@ -6,16 +6,13 @@
//
// SPDX-License-Identifier: MPL-2.0
use gst::glib::once_cell::sync::Lazy;
use chrono::{DateTime, Utc};
use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment};
use regex::Regex;
use std::io::Write;
const GST_M3U8_PLAYLIST_V3: usize = 3;
const GST_M3U8_PLAYLIST_V4: usize = 4;
static SEGMENT_IDX_PATTERN: Lazy<regex::Regex> = Lazy::new(|| Regex::new(r"(%0(\d+)d)").unwrap());
/// An HLS playlist.
///
/// Controls the changes that needs to happen in the playlist as new segments are added. This
@ -68,8 +65,11 @@ impl Playlist {
}
/// Adds a new segment to the playlist.
pub fn add_segment(&mut self, uri: String, duration: f32) {
pub fn add_segment(&mut self, uri: String, duration: f32, date_time: Option<DateTime<Utc>>) {
self.start();
// We are adding date-time to each segment.Putting date-time-tag only for the first segment in the playlist
// is also valid. But it is better to put program-date-time tag for every segment to take care of any drift.
// FFMPEG also put PDT tag for each segment.
self.inner.segments.push(MediaSegment {
uri,
duration,
@ -78,7 +78,7 @@ impl Playlist {
discontinuity: false,
key: None,
map: None,
program_date_time: None,
program_date_time: date_time.map(|d| d.into()),
daterange: None,
unknown_tags: vec![],
});
@ -155,119 +155,3 @@ pub enum PlaylistRenderState {
Init,
Started,
}
/// A formatter for segment locations.
///
/// The formatting is based on a string that must contain the placeholder `%0Xd` where `X` is a
/// the number of zero prefixes you want to have in the segment name. The placeholder is only
/// replaced once in the string, other placements are not going to be processed.
///
/// # Examples
///
/// In this example we want to have segment files with the following names:
/// ```text
/// part001.ts
/// part002.ts
/// part003.ts
/// part004.ts
/// ```
/// Then we can use the segment pattern value as `"part%03d.ts"`:
///
/// ```rust,ignore
/// let formatter = SegmentFormatter::new("part%03d.ts").unwrap();
/// assert_eq!(formatter.segment(1), "part001.ts");
/// assert_eq!(formatter.segment(2), "part002.ts");
/// assert_eq!(formatter.segment(3), "part003.ts");
/// assert_eq!(formatter.segment(4), "part004.ts");
/// ```
pub struct SegmentFormatter {
prefix: String,
suffix: String,
padding_len: u32,
}
impl SegmentFormatter {
/// Processes the segment name containing a placeholder. It can be used
/// repeatedly to format segment names.
///
/// If an invalid placeholder is provided, then `None` is returned.
pub fn new<S: AsRef<str>>(segment_pattern: S) -> Option<Self> {
let segment_pattern = segment_pattern.as_ref();
let caps = SEGMENT_IDX_PATTERN.captures(segment_pattern)?;
let number_placement_match = caps.get(1)?;
let zero_pad_match = caps.get(2)?;
let padding_len = zero_pad_match
.as_str()
.parse::<u32>()
.expect("valid number matched by regex");
let prefix = segment_pattern[..number_placement_match.start()].to_string();
let suffix = segment_pattern[number_placement_match.end()..].to_string();
Some(Self {
prefix,
suffix,
padding_len,
})
}
/// Returns the segment location formatted for the provided id.
#[inline]
pub fn segment(&self, id: u32) -> String {
let padded_number = left_pad_zeroes(self.padding_len, id);
format!("{}{}{}", self.prefix, padded_number, self.suffix)
}
}
/// Transforms a number to a zero padded string representation.
///
/// The zero padding is added to the left of the number which is converted to a string. For the
/// case that the length number converted to string is larger than the requested padding, the
/// number representation is returned and no padding is added. The length of the returned string is
/// the maximum value between the desired padding and the length of the number.
///
/// # Examples
///
/// ```rust,ignore
/// let padded_number = left_pad_zeroes(4, 10);
/// assert_eq!(padded_number, "0010");
/// ```
#[inline]
pub(crate) fn left_pad_zeroes(padding: u32, number: u32) -> String {
let numerical_repr = number.to_string();
let mut padded = String::with_capacity(padding.max(numerical_repr.len() as u32) as usize);
let pad_zeroes = padding as i32 - numerical_repr.len() as i32;
if pad_zeroes > 0 {
for _ in 0..pad_zeroes {
padded.push('0');
}
}
padded.push_str(&numerical_repr);
padded
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn segment_is_correctly_formatted() {
let formatter = SegmentFormatter::new("segment%05d.ts").unwrap();
assert_eq!("segment00001.ts", formatter.segment(1));
assert_eq!("segment00016.ts", formatter.segment(16));
assert_eq!("segment01827.ts", formatter.segment(1827));
assert_eq!("segment98765.ts", formatter.segment(98765));
let formatter = SegmentFormatter::new("part-%03d.ts").unwrap();
assert_eq!("part-010.ts", formatter.segment(10));
assert_eq!("part-9999.ts", formatter.segment(9999));
}
#[test]
fn padding_numbers() {
assert_eq!("001", left_pad_zeroes(3, 1));
assert_eq!("010", left_pad_zeroes(3, 10));
assert_eq!("100", left_pad_zeroes(3, 100));
assert_eq!("1000", left_pad_zeroes(3, 1000));
assert_eq!("987654321", left_pad_zeroes(3, 987654321));
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-ndi"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Ruben Gonzalez <rubenrua@teltek.es>", "Daniel Vilar <daniel.peiteado@teltek.es>", "Sebastian Dröge <sebastian@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,18 +9,18 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
glib = { git = "https://github.com/gtk-rs/gtk-rs-core"}
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
glib = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18"}
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
byte-slice-cast = "1"
byteorder = "1.0"
atomic_refcell = "0.1"
libloading = "0.8"
[build-dependencies]
gst-plugin-version-helper = { path = "../../version-helper" }
gst-plugin-version-helper = { path = "../../version-helper", version = "0.8" }
[features]
default = ["interlaced-fields", "sink"]

View file

@ -87,9 +87,8 @@ impl DeviceProviderImpl for DeviceProvider {
let mut first = true;
*thread_guard = Some(thread::spawn(move || {
{
let imp = match imp_weak.upgrade() {
None => return,
Some(imp) => imp,
let Some(imp) = imp_weak.upgrade() else {
return;
};
let mut find_guard = imp.find.lock().unwrap();
@ -109,9 +108,8 @@ impl DeviceProviderImpl for DeviceProvider {
}
loop {
let imp = match imp_weak.upgrade() {
None => return,
Some(imp) => imp,
let Some(imp) = imp_weak.upgrade() else {
return;
};
if !imp.is_running.load(atomic::Ordering::SeqCst) {

View file

@ -76,6 +76,7 @@ impl<'a> FindBuilder<'a> {
#[derive(Debug)]
pub struct FindInstance(ptr::NonNull<::std::os::raw::c_void>);
unsafe impl Send for FindInstance {}
impl FindInstance {
@ -250,7 +251,7 @@ impl<'a> RecvBuilder<'a> {
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct RecvInstance(ptr::NonNull<::std::os::raw::c_void>);
unsafe impl Send for RecvInstance {}

View file

@ -194,7 +194,7 @@ pub enum ReceiverItem {
Error(gst::FlowError),
}
pub struct ReceiverInner {
struct ReceiverInner {
queue: ReceiverQueue,
max_queue_length: usize,
@ -747,14 +747,12 @@ impl Receiver {
// Capture until error or shutdown
loop {
let receiver = match receiver.upgrade().map(Receiver) {
None => break,
Some(receiver) => receiver,
let Some(receiver) = receiver.upgrade().map(Receiver) else {
break;
};
let element = match receiver.0.element.upgrade() {
None => return,
Some(element) => element,
let Some(element) = receiver.0.element.upgrade() else {
break;
};
let flushing = {
@ -1615,10 +1613,12 @@ impl Receiver {
if [NDIlib_FourCC_audio_type_FLTp].contains(&fourcc) {
let channels = audio_frame.no_channels() as u32;
let mut positions = [gst_audio::AudioChannelPosition::None; 64];
let _ = gst_audio::AudioChannelPosition::positions_from_mask(
gst_audio::AudioChannelPosition::fallback_mask(channels),
&mut positions[..channels as usize],
);
if channels <= 8 {
let _ = gst_audio::AudioChannelPosition::positions_from_mask(
gst_audio::AudioChannelPosition::fallback_mask(channels),
&mut positions[..channels as usize],
);
}
let builder = gst_audio::AudioInfo::builder(
gst_audio::AUDIO_FORMAT_F32,
@ -1671,8 +1671,9 @@ impl Receiver {
});
}
#[cfg(feature = "advanced-sdk")]
if [NDIlib_FourCC_audio_type_Opus].contains(&fourcc) {}
// FIXME: Needs testing with an actual stream to understand how it works
// #[cfg(feature = "advanced-sdk")]
// if [NDIlib_FourCC_audio_type_Opus].contains(&fourcc) {}
gst::element_error!(
element,

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-onvif"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,15 +9,15 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
xmlparser = "0.13"
chrono = { version = "0.4", default-features = false }
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", features=["use_glib"] }
pango = { git = "https://github.com/gtk-rs/gtk-rs-core" }
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core" }
chrono = { version = "0.4.31", default-features = false }
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18", features=["use_glib"] }
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18" }
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18" }
xmltree = "0.10"
[lib]
@ -26,7 +26,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -359,8 +359,19 @@ impl OnvifMetadataParse {
gst::FlowError::Error
})?;
let dt_unix_ns =
(dt.timestamp_nanos() as u64).nseconds() + crate::PRIME_EPOCH_OFFSET;
let dt_unix_ns = dt
.timestamp_nanos_opt()
.and_then(|ns| u64::try_from(ns).ok())
.and_then(|ns| ns.nseconds().checked_add(crate::PRIME_EPOCH_OFFSET));
let Some(dt_unix_ns) = dt_unix_ns else {
gst::warning!(CAT,
imp: self,
"Frame with unrepresentable UTC time {}",
dt,
);
continue;
};
gst::trace!(
CAT,

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-raptorq"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Tomasz Andrzejak <andreiltd@gmail.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
description = "GStreamer RaptorQ FEC Plugin"
@ -9,13 +9,13 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
raptorq = "1.7"
[dev-dependencies]
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_18"] }
rand = "0.8"
[lib]
@ -24,7 +24,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -477,7 +477,7 @@ impl RaptorqDec {
state
.repair_packets
.entry(this_seq)
.or_insert_with(Vec::new)
.or_default()
.push(RepairPacketItem {
payload_id: id,
payload: payload[7..].to_vec(), // without PayloadId

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-reqwest"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MIT OR Apache-2.0"
@ -14,13 +14,13 @@ reqwest = { version = "0.11", features = ["cookies", "gzip"] }
futures = "0.3"
headers = "0.3"
mime = "0.3"
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
tokio = { version = "1.0", default-features = false, features = ["time", "rt-multi-thread"] }
[dev-dependencies]
hyper = { version = "0.14", features = ["server"] }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gstreqwest"
@ -28,7 +28,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-rtp"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Vivienne Watermeier <vwatermeier@igalia.com>", "Sebastian Dröge <sebastian@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,16 +9,16 @@ description = "GStreamer Rust RTP Plugin"
rust-version = "1.70"
[dependencies]
bitstream-io = "1.3"
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"]}
bitstream-io = "2.0"
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"]}
chrono = { version = "0.4", default-features = false }
[dev-dependencies]
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_20"] }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21", features = ["v1_20"] }
[build-dependencies]
gst-plugin-version-helper = { path = "../../version-helper" }
gst-plugin-version-helper = { path = "../../version-helper", version = "0.8" }
[lib]
name = "gstrsrtp"

View file

@ -33,6 +33,8 @@ struct State {
marked_packet: bool,
/// if the next output buffer needs the DISCONT flag set
needs_discont: bool,
/// if we saw a valid OBU since the last reset
found_valid_obu: bool,
/// holds data for a fragment
obu_fragment: Option<(UnsizedObu, Vec<u8>)>,
}
@ -43,6 +45,7 @@ impl Default for State {
last_timestamp: None,
marked_packet: false,
needs_discont: true,
found_valid_obu: false,
obu_fragment: None,
}
}
@ -291,6 +294,21 @@ impl RTPAv1Depay {
let (element_size, is_last_obu) =
self.find_element_info(rtp, &mut reader, &aggr_header, idx)?;
if idx == 0 && aggr_header.leading_fragment {
if state.found_valid_obu {
gst::error!(
CAT,
imp: self,
"invalid packet: unexpected leading OBU fragment"
);
}
reader
.seek(SeekFrom::Current(element_size as i64))
.map_err(err_flow!(self, buf_read))?;
idx += 1;
continue;
}
let header_pos = reader.position();
let mut bitreader = BitReader::endian(&mut reader, ENDIANNESS);
let obu = UnsizedObu::parse(&mut bitreader).map_err(err_flow!(self, obu_read))?;
@ -299,6 +317,8 @@ impl RTPAv1Depay {
.seek(SeekFrom::Start(header_pos))
.map_err(err_flow!(self, buf_read))?;
state.found_valid_obu = true;
// ignore these OBU types
if matches!(obu.obu_type, ObuType::TemporalDelimiter | ObuType::TileList) {
reader
@ -333,7 +353,7 @@ impl RTPAv1Depay {
}
// now push all the complete OBUs
let buffer = if !ready_obus.is_empty() {
let buffer = if !ready_obus.is_empty() && ready_obus != TEMPORAL_DELIMITER {
gst::log!(
CAT,
imp: self,

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-webrtc"
version = "0.11.0-alpha.1"
version = "0.11.3"
edition = "2021"
authors = ["Mathieu Duponchelle <mathieu@centricular.com>", "Thibault Saunier <tsaunier@igalia.com>"]
license = "MPL-2.0"
@ -9,14 +9,14 @@ repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
rust-version = "1.70"
[dependencies]
gst = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer", features = ["v1_20", "serde"] }
gst-app = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-app", features = ["v1_20"] }
gst-video = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-video", features = ["v1_20", "serde"] }
gst-webrtc = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-webrtc", features = ["v1_20"] }
gst-sdp = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-sdp", features = ["v1_20"] }
gst-rtp = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-rtp", features = ["v1_20"] }
gst-utils = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-utils" }
gst-base = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", package = "gstreamer-base" }
gst = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", package = "gstreamer", features = ["v1_20", "serde"] }
gst-app = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", package = "gstreamer-app", features = ["v1_20"] }
gst-video = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", package = "gstreamer-video", features = ["v1_20", "serde"] }
gst-webrtc = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", package = "gstreamer-webrtc", features = ["v1_20"] }
gst-sdp = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", package = "gstreamer-sdp", features = ["v1_20"] }
gst-rtp = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", package = "gstreamer-rtp", features = ["v1_20"] }
gst-utils = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", package = "gstreamer-utils" }
gst-base = { git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", package = "gstreamer-base" }
uuid = { version = "1", features = ["v4"] }
anyhow = "1"
@ -25,23 +25,23 @@ futures = "0.3"
tokio = { version = "1", features = ["fs", "macros", "rt-multi-thread", "time"] }
tokio-native-tls = "0.3.0"
tokio-stream = "0.1.11"
async-tungstenite = { version = "0.23", features = ["tokio-runtime", "tokio-native-tls"] }
async-tungstenite = { version = "0.24", features = ["tokio-runtime", "tokio-native-tls"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
fastrand = "2.0"
gst_plugin_webrtc_protocol = { path="protocol", package = "gst-plugin-webrtc-signalling-protocol" }
gst_plugin_webrtc_protocol = { path="protocol", package = "gst-plugin-webrtc-signalling-protocol", version = "0.11" }
human_bytes = "0.4"
url = "2"
aws-config = "0.56.0"
aws-types = "0.56.0"
aws-credential-types = "0.56.0"
aws-sig-auth = "0.56.0"
aws-smithy-http = { version = "0.56.0", features = [ "rt-tokio" ] }
aws-smithy-types = "0.56.0"
aws-sdk-kinesisvideo = "0.29.0"
aws-sdk-kinesisvideosignaling = "0.29.0"
http = "0.2.7"
aws-config = "1.0"
aws-types = "1.0"
aws-credential-types = "1.0"
aws-sigv4 = "1.0"
aws-smithy-http = { version = "0.60", features = [ "rt-tokio" ] }
aws-smithy-types = "1.0"
aws-sdk-kinesisvideo = "1.0"
aws-sdk-kinesisvideosignaling = "1.0"
http = "0.2"
chrono = "0.4"
data-encoding = "2.3.3"
url-escape = "0.1.1"
@ -51,13 +51,13 @@ reqwest = { version = "0.11", features = ["default-tls"] }
parse_link_header = {version = "0.3", features = ["url"]}
async-recursion = "1.0.0"
livekit-protocol = { version = "0.1.3" }
livekit-api = { version = "0.1.3", default-features = false, features = ["signal-client", "access-token", "native-tls"] }
livekit-protocol = { version = "0.2" }
livekit-api = { version = "0.2", default-features = false, features = ["signal-client", "access-token", "native-tls"] }
[dev-dependencies]
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
tracing-log = "0.1"
tracing-log = "0.2"
clap = { version = "4", features = ["derive"] }
[lib]
@ -66,7 +66,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path = "../../version-helper" }
gst-plugin-version-helper = { path = "../../version-helper", version = "0.8" }
[features]
static = []
@ -93,3 +93,6 @@ name = "webrtcsink-stats-server"
[[example]]
name = "webrtcsink-high-quality-tune"
[[example]]
name = "webrtcsink-custom-signaller"

View file

@ -28,10 +28,10 @@ set of streams to any number of consumers, `webrtcsink` (which wraps
* Application-provided signalling: `webrtcsink` can be instantiated by an
application with a custom signaller. That signaller must be a GObject, and
must implement the `Signallable` interface as defined
[here](src/webrtcsink/mod.rs). The [default signaller](src/signaller/mod.rs)
[here](src/signaller/mod.rs). The [default signaller](src/signaller/imp.rs)
can be used as an example.
An [example project] is also available to use as a boilerplate for
An [example](examples/webrtcsink-custom-signaller/README.md) is also available to use as a boilerplate for
implementing and using a custom signaller.
* Sandboxed consumers: when a consumer is added, its encoder / payloader /
@ -85,6 +85,7 @@ cargo build
Open three terminals. In the first one, run the signalling server:
``` shell
cd signalling
WEBRTCSINK_SIGNALLING_SERVER_LOG=debug cargo run --bin gst-webrtc-signalling-server
```
@ -99,7 +100,7 @@ npm start
In the third one, run a webrtcsink producer from a GStreamer pipeline:
``` shell
export GST_PLUGIN_PATH=$PWD/target/debug:$GST_PLUGIN_PATH
export GST_PLUGIN_PATH=<path-to-gst-plugins-rs>/target/debug:$GST_PLUGIN_PATH
gst-launch-1.0 webrtcsink name=ws meta="meta,name=gst-stream" videotestsrc ! ws. audiotestsrc ! ws.
```
@ -114,7 +115,7 @@ a GStreamer pipeline. Click on the "Start Capture" button and copy the
Then open a new terminal and run:
``` shell
export GST_PLUGIN_PATH=$PWD/target/debug:$GST_PLUGIN_PATH
export GST_PLUGIN_PATH=<path-to-gst-plugins-rs>/target/debug:$GST_PLUGIN_PATH
gst-launch-1.0 playbin uri=gstwebrtc://127.0.0.1:8443?peer-id=[Client ID]
```

View file

@ -1,6 +1,6 @@
# webrtcsink examples
Collection (1-sized for now) of webrtcsink examples
Collection of webrtcsink examples
## webrtcsink-stats-server
@ -16,3 +16,8 @@ cargo run --example webrtcsink-stats-server
Once it is running, follow the instruction in the webrtcsink-stats folder to
run an example client.
## webrtcsink-custom-signaller
An example of custom signaller implementation, see the corresponding
[README](webrtcsink-custom-signaller/README.md) for more details on code and usage.

View file

@ -0,0 +1,23 @@
# WebRTCSink custom signaller
A simple application that consist of two parts:
* main executable, which demonstrates how to instantiate WebRTCSink with a custom signaller
* `signaller` module, which provides all the required boilerplate code
and stub implementations needed to create a custom signaller
Run with:
``` shell
cargo run --example webrtcsink-custom-signaller
```
The expected output is a not-implemented panic (from `imp::Signaller::start` function):
```text
thread 'tokio-runtime-worker' panicked at 'not implemented', net/webrtc/examples/webrtcsink-custom-signaller/signaller/imp.rs:14:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
Simply implement the methods in [imp.rs](signaller/imp.rs) and you should be good
to go!

View file

@ -0,0 +1,41 @@
mod signaller;
// from outside the plugin repository, one would need to add plugin package as follows:
// [dependencies]
// gstrswebrtc = { package = "gst-plugin-webrtc", git = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/" }
extern crate gstrswebrtc;
use anyhow::Error;
use gst::prelude::*;
use gstrswebrtc::signaller as signaller_interface;
use gstrswebrtc::webrtcsink;
fn main() -> Result<(), Error> {
gst::init()?;
let custom_signaller = signaller::MyCustomSignaller::new();
let webrtcsink = webrtcsink::BaseWebRTCSink::with_signaller(
signaller_interface::Signallable::from(custom_signaller),
);
let pipeline = gst::Pipeline::new();
let video_src = gst::ElementFactory::make("videotestsrc").build().unwrap();
pipeline
.add_many([&video_src, webrtcsink.upcast_ref()])
.unwrap();
video_src
.link(webrtcsink.upcast_ref::<gst::Element>())
.unwrap();
let bus = pipeline.bus().unwrap();
pipeline.set_state(gst::State::Playing).unwrap();
let _msg = bus.timed_pop_filtered(gst::ClockTime::NONE, &[gst::MessageType::Eos]);
pipeline.set_state(gst::State::Null).unwrap();
Ok(())
}

View file

@ -0,0 +1,48 @@
use gst::glib;
use gst::subclass::prelude::*;
use gst_webrtc::WebRTCSessionDescription;
use gstrswebrtc::signaller::{Signallable, SignallableImpl};
#[derive(Default)]
pub struct Signaller {}
impl Signaller {}
impl SignallableImpl for Signaller {
fn start(&self) {
unimplemented!()
}
fn stop(&self) {
unimplemented!()
}
fn send_sdp(&self, _session_id: &str, _sdp: &WebRTCSessionDescription) {
unimplemented!()
}
fn add_ice(
&self,
_session_id: &str,
_candidate: &str,
_sdp_m_line_index: u32,
_sdp_mid: Option<String>,
) {
unimplemented!()
}
fn end_session(&self, _session_id: &str) {
unimplemented!()
}
}
#[glib::object_subclass]
impl ObjectSubclass for Signaller {
const NAME: &'static str = "MyCustomWebRTCSinkSignaller";
type Type = super::MyCustomSignaller;
type ParentType = glib::Object;
type Interfaces = (Signallable,);
}
impl ObjectImpl for Signaller {}

View file

@ -0,0 +1,23 @@
use gst::glib;
use gstrswebrtc::signaller::Signallable;
mod imp;
glib::wrapper! {
pub struct MyCustomSignaller(ObjectSubclass<imp::Signaller>) @implements Signallable;
}
unsafe impl Send for MyCustomSignaller {}
unsafe impl Sync for MyCustomSignaller {}
impl MyCustomSignaller {
pub fn new() -> Self {
glib::Object::new()
}
}
impl Default for MyCustomSignaller {
fn default() -> Self {
MyCustomSignaller::new()
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-webrtc-signalling-protocol"
version = "0.11.0-alpha.1"
version = "0.11.3"
edition = "2021"
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
license = "MPL-2.0"

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-webrtc-signalling"
version = "0.11.0-alpha.1"
version = "0.11.3"
edition = "2021"
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
license = "MPL-2.0"
@ -12,19 +12,19 @@ rust-version = "1.70"
anyhow = "1"
tokio = { version = "1", features = ["fs", "io-util", "macros", "rt-multi-thread", "time"] }
tokio-native-tls = "0.3.0"
async-tungstenite = { version = "0.23", features = ["tokio-runtime", "tokio-native-tls"] }
async-tungstenite = { version = "0.24", features = ["tokio-runtime", "tokio-native-tls"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
clap = { version = "4", features = ["derive"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["registry", "env-filter"] }
tracing-log = "0.1"
tracing-log = "0.2"
futures = "0.3"
uuid = { version = "1", features = ["v4"] }
thiserror = "1"
test-log = { version = "0.2", features = ["trace"], default-features = false }
pin-project-lite = "0.2"
gst_plugin_webrtc_protocol = { path="../protocol", package = "gst-plugin-webrtc-signalling-protocol" }
gst_plugin_webrtc_protocol = { path="../protocol", package = "gst-plugin-webrtc-signalling-protocol", version = "0.11" }
[[bin]]
name = "gst-webrtc-signalling-server"

View file

@ -53,7 +53,7 @@ fn initialize_logging(envvar_name: &str) -> Result<(), Error> {
#[tokio::main]
async fn main() -> Result<(), Error> {
let args = Args::parse();
let server = Server::spawn(|stream| Handler::new(stream));
let server = Server::spawn(Handler::new);
initialize_logging("WEBRTCSINK_SIGNALLING_SERVER_LOG")?;

View file

@ -292,11 +292,11 @@ impl Handler {
);
self.consumer_sessions
.entry(consumer_id.to_string())
.or_insert_with(HashSet::new)
.or_default()
.insert(session_id.clone());
self.producer_sessions
.entry(producer_id.to_string())
.or_insert_with(HashSet::new)
.or_default()
.insert(session_id.clone());
self.items.push_back((
consumer_id.to_string(),

View file

@ -22,10 +22,10 @@ use aws_sdk_kinesisvideo::{
Client,
};
use aws_sdk_kinesisvideosignaling::Client as SignalingClient;
use aws_sig_auth::signer::{self, HttpSignatureType, OperationSigningConfig, RequestConfig};
use aws_smithy_http::body::SdkBody;
use aws_types::{region::SigningRegion, SigningService};
use chrono::prelude::*;
use aws_sigv4::http_request::{
sign, SignableBody, SignableRequest, SignatureLocation, SigningSettings,
};
use aws_sigv4::sign::v4;
use data_encoding::BASE64;
use http::Uri;
use std::time::{Duration, SystemTime};
@ -228,10 +228,12 @@ impl Signaller {
Ok(credentials) => credentials,
};
let Some(channel_name) = settings.channel_name else { anyhow::bail!("Channel name cannot be None!"); };
let Some(channel_name) = settings.channel_name else {
anyhow::bail!("Channel name cannot be None!");
};
let client = Client::new(
&aws_config::from_env()
&aws_config::defaults(aws_config::BehaviorVersion::latest())
.credentials_provider(credentials.clone())
.load()
.await,
@ -243,11 +245,15 @@ impl Signaller {
.send()
.await?;
let Some(cinfo) = resp.channel_info() else { anyhow::bail!("No description found for {channel_name}"); };
let Some(cinfo) = resp.channel_info() else {
anyhow::bail!("No description found for {channel_name}");
};
gst::debug!(CAT, "Channel description: {cinfo:?}");
let Some(channel_arn) = cinfo.channel_arn() else { anyhow::bail!("No channel ARN found for {channel_name}"); };
let Some(channel_arn) = cinfo.channel_arn() else {
anyhow::bail!("No channel ARN found for {channel_name}");
};
let config = SingleMasterChannelEndpointConfiguration::builder()
.set_protocols(Some(vec![ChannelProtocol::Wss, ChannelProtocol::Https]))
@ -263,41 +269,31 @@ impl Signaller {
gst::debug!(CAT, "Endpoints: {:?}", resp.resource_endpoint_list());
let endpoint_wss_uri =
match resp
.resource_endpoint_list()
.unwrap()
.iter()
.find_map(|endpoint| {
if endpoint.protocol == Some(ChannelProtocol::Wss) {
Some(endpoint.resource_endpoint().unwrap().to_owned())
} else {
None
}
}) {
Some(endpoint_uri_str) => Uri::from_maybe_shared(endpoint_uri_str).unwrap(),
None => {
anyhow::bail!("No WSS endpoint found for {channel_name}");
}
};
let endpoint_wss_uri = match resp.resource_endpoint_list().iter().find_map(|endpoint| {
if endpoint.protocol == Some(ChannelProtocol::Wss) {
Some(endpoint.resource_endpoint().unwrap().to_owned())
} else {
None
}
}) {
Some(endpoint_uri_str) => Uri::from_maybe_shared(endpoint_uri_str).unwrap(),
None => {
anyhow::bail!("No WSS endpoint found for {channel_name}");
}
};
let endpoint_https_uri =
match resp
.resource_endpoint_list()
.unwrap()
.iter()
.find_map(|endpoint| {
if endpoint.protocol == Some(ChannelProtocol::Https) {
Some(endpoint.resource_endpoint().unwrap().to_owned())
} else {
None
}
}) {
Some(endpoint_uri_str) => endpoint_uri_str,
None => {
anyhow::bail!("No HTTPS endpoint found for {channel_name}");
}
};
let endpoint_https_uri = match resp.resource_endpoint_list().iter().find_map(|endpoint| {
if endpoint.protocol == Some(ChannelProtocol::Https) {
Some(endpoint.resource_endpoint().unwrap().to_owned())
} else {
None
}
}) {
Some(endpoint_uri_str) => endpoint_uri_str,
None => {
anyhow::bail!("No HTTPS endpoint found for {channel_name}");
}
};
gst::debug!(
CAT,
@ -307,7 +303,7 @@ impl Signaller {
);
let signaling_config = aws_sdk_kinesisvideosignaling::config::Builder::from(
&aws_config::from_env()
&aws_config::defaults(aws_config::BehaviorVersion::latest())
.credentials_provider(credentials.clone())
.load()
.await,
@ -325,7 +321,6 @@ impl Signaller {
let ice_servers: Vec<String> = resp
.ice_server_list()
.unwrap()
.iter()
.filter_map(|server| {
Option::zip(server.username(), server.password())
@ -334,7 +329,6 @@ impl Signaller {
.flat_map(|(username, password, server)| {
server
.uris()
.unwrap()
.iter()
.filter_map(move |uri| {
uri.split_once(':').map(|(protocol, host)| {
@ -367,20 +361,20 @@ impl Signaller {
}),
);
let current_time = Utc::now();
let signer = signer::SigV4Signer::new();
let mut operation_config = OperationSigningConfig::default_config();
operation_config.signature_type = HttpSignatureType::HttpRequestQueryParams;
operation_config.expires_in = Some(Duration::from_secs(5 * 60)); // See commit a3db85d.
let request_config = RequestConfig {
request_ts: SystemTime::from(current_time),
region: &SigningRegion::from(region),
service: &SigningService::from_static("kinesisvideo"),
payload_override: None,
};
let mut signing_settings = SigningSettings::default();
signing_settings.signature_location = SignatureLocation::QueryParams;
signing_settings.expires_in = Some(Duration::from_secs(5 * 60));
let identity = credentials.clone().into();
let region_string = region.to_string();
let signing_params = v4::SigningParams::builder()
.identity(&identity)
.region(&region_string)
.name("kinesisvideo")
.time(SystemTime::now())
.settings(signing_settings)
.build()
.unwrap()
.into();
let transcribe_uri = Uri::builder()
.scheme("wss")
.authority(endpoint_wss_uri.authority().unwrap().to_owned())
@ -393,22 +387,23 @@ impl Signaller {
gst::error!(CAT, imp: self, "Failed to build HTTP request URI: {err}");
anyhow!("Failed to build HTTP request URI: {err}")
})?;
// Convert the HTTP request into a signable request
let signable_request = SignableRequest::new(
"GET",
transcribe_uri.to_string(),
std::iter::empty(),
SignableBody::Bytes(&[]),
)
.expect("signable request");
let mut request = http::Request::builder()
.uri(transcribe_uri)
.body(SdkBody::empty())
.body(aws_smithy_types::body::SdkBody::empty())
.expect("Failed to build valid request");
let _signature = signer
.sign(
&operation_config,
&request_config,
&credentials,
&mut request,
)
.map_err(|err| {
gst::error!(CAT, imp: self, "Failed to sign HTTP request: {err}");
anyhow!("Failed to sign HTTP request: {err}")
})?;
let (signing_instructions, _signature) =
sign(signable_request, &signing_params)?.into_parts();
signing_instructions.apply_to_request_http0x(&mut request);
let url = request.uri().to_string();

View file

@ -97,9 +97,8 @@ impl Signaller {
loop {
match wait_async(&self.signal_task_canceller, signal_events.recv(), 0).await {
Ok(Some(signal)) => match signal {
signal_client::SignalEvent::Open => {}
signal_client::SignalEvent::Signal(signal) => {
self.on_signal_event(signal).await;
signal_client::SignalEvent::Message(signal) => {
self.on_signal_event(*signal).await;
}
signal_client::SignalEvent::Close => {
gst::debug!(CAT, imp: self, "Close");
@ -138,21 +137,9 @@ impl Signaller {
.emit_by_name::<()>("session-description", &[&"unique", &answer]);
}
proto::signal_response::Message::Trickle(trickle) => {
let target = if let Some(target) = proto::SignalTarget::from_i32(trickle.target) {
target
} else {
gst::warning!(
CAT,
imp: self,
"Received ice_candidate {:?} from invalid target, ignoring",
trickle
);
return;
};
gst::debug!(CAT, imp: self, "Received ice_candidate {:?}", trickle);
if target == proto::SignalTarget::Publisher {
if trickle.target() == proto::SignalTarget::Publisher {
if let Ok(json) =
serde_json::from_str::<IceCandidateJson>(&trickle.candidate_init)
{

View file

@ -39,6 +39,7 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
const CUDA_MEMORY_FEATURE: &str = "memory:CUDAMemory";
const GL_MEMORY_FEATURE: &str = "memory:GLMemory";
const NVMM_MEMORY_FEATURE: &str = "memory:NVMM";
const D3D11_MEMORY_FEATURE: &str = "memory:D3D11Memory";
const RTP_TWCC_URI: &str =
"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
@ -85,29 +86,17 @@ struct Settings {
signaller: Signallable,
}
/// Type of discovery, used to differentiate between initial discovery
/// and discovery initiated by client offer
#[derive(Debug, Clone, PartialEq, Eq)]
enum DiscoveryType {
/// Initial discovery of our input streams
Initial,
/// Discovery to select a specific codec as requested by the remote peer
CodecSelection,
}
#[derive(Debug, Clone)]
struct DiscoveryInfo {
id: String,
type_: DiscoveryType,
caps: gst::Caps,
srcs: Arc<Mutex<Vec<gst_app::AppSrc>>>,
}
impl DiscoveryInfo {
fn new(type_: DiscoveryType, caps: gst::Caps) -> Self {
fn new(caps: gst::Caps) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
type_,
caps,
srcs: Default::default(),
}
@ -129,6 +118,66 @@ impl DiscoveryInfo {
}
}
// Same gst::bus::BusStream but hooking context message from the thread
// where the message is posted, so that GstContext can be shared
#[derive(Debug)]
struct CustomBusStream {
bus: glib::WeakRef<gst::Bus>,
receiver: futures::channel::mpsc::UnboundedReceiver<gst::Message>,
}
impl CustomBusStream {
fn new(bin: &super::BaseWebRTCSink, bus: &gst::Bus) -> Self {
let (sender, receiver) = futures::channel::mpsc::unbounded();
let bin_weak = bin.downgrade();
bus.set_sync_handler(move |_, msg| {
match msg.view() {
gst::MessageView::NeedContext(..) | gst::MessageView::HaveContext(..) => {
if let Some(bin) = bin_weak.upgrade() {
let _ = bin.post_message(msg.to_owned());
}
}
_ => {
let _ = sender.unbounded_send(msg.to_owned());
}
}
gst::BusSyncReply::Drop
});
Self {
bus: bus.downgrade(),
receiver,
}
}
}
impl Drop for CustomBusStream {
fn drop(&mut self) {
if let Some(bus) = self.bus.upgrade() {
bus.unset_sync_handler();
}
}
}
impl futures::Stream for CustomBusStream {
type Item = gst::Message;
fn poll_next(
mut self: std::pin::Pin<&mut Self>,
context: &mut std::task::Context,
) -> std::task::Poll<Option<Self::Item>> {
self.receiver.poll_next_unpin(context)
}
}
impl futures::stream::FusedStream for CustomBusStream {
fn is_terminated(&self) -> bool {
self.receiver.is_terminated()
}
}
/// Wrapper around our sink pads
#[derive(Debug, Clone)]
struct InputStream {
@ -144,8 +193,8 @@ struct InputStream {
serial: u32,
/// Whether the input stream is video or not
is_video: bool,
/// Information about currently running codec discoveries
discoveries: Vec<DiscoveryInfo>,
/// Whether initial discovery has started
initial_discovery_started: bool,
}
/// Wrapper around webrtcbin pads
@ -235,10 +284,141 @@ struct SignallerSignals {
shutdown: glib::SignalHandlerId,
}
struct IceCandidate {
sdp_m_line_index: u32,
candidate: String,
}
/// Wrapper around `Session`.
///
/// This makes it possible for the `Session` to be taken out of the `State`,
/// without removing the entry in the `sessions` `HashMap`, thus allowing
/// the `State` lock to be released, e.g. before calling a `Signal`.
///
/// Taking the `Session`, replaces it with a placeholder which can enqueue
/// items (currently ICE candidates) received while the `Session` is taken.
/// In which case, the enqueued items will be processed when the `Session` is
/// restored.
enum SessionWrapper {
/// The `Session` is available in the `SessionWrapper`.
InPlace(Session),
/// The `Session` was taken out the `SessionWrapper`.
Taken(Vec<IceCandidate>),
}
impl SessionWrapper {
/// Unwraps a reference to the `Session` of this `SessionWrapper`.
///
/// # Panics
///
/// Panics is the `Session` was taken.
fn unwrap(&self) -> &Session {
match self {
SessionWrapper::InPlace(session) => session,
_ => panic!("Session is not In Place"),
}
}
/// Unwraps a mutable reference to the `Session` of this `SessionWrapper`.
///
/// # Panics
///
/// Panics is the `Session` was taken.
fn unwrap_mut(&mut self) -> &mut Session {
match self {
SessionWrapper::InPlace(session) => session,
_ => panic!("Session is not In Place"),
}
}
/// Consumes the `SessionWrapper`, returning the wrapped `Session`.
///
/// # Panics
///
/// Panics is the `Session` was taken.
fn into_inner(self) -> Session {
match self {
SessionWrapper::InPlace(session) => session,
_ => panic!("Session is not In Place"),
}
}
/// Takes the `Session` out of this `SessionWrapper`, leaving it in the `Taken` state.
///
/// # Panics
///
/// Panics is the `Session` was taken.
fn take(&mut self) -> Session {
use SessionWrapper::*;
match std::mem::replace(self, Taken(Vec::new())) {
InPlace(session) => session,
_ => panic!("Session is not In Place"),
}
}
/// Restores a `Session` to this `SessionWrapper`.
///
/// Processes any pending items enqueued while the `Session` was taken.
///
/// # Panics
///
/// Panics is the `Session` is already in place.
fn restore(&mut self, session: Session) {
let SessionWrapper::Taken(ref cands) = self else {
panic!("Session is already in place");
};
if !cands.is_empty() {
gst::trace!(
CAT,
"handling {} pending ice candidates for session {}",
cands.len(),
session.id,
);
for cand in cands {
session.webrtcbin.emit_by_name::<()>(
"add-ice-candidate",
&[&cand.sdp_m_line_index, &cand.candidate],
);
}
}
*self = SessionWrapper::InPlace(session);
}
/// Adds an ICE candidate to this `SessionWrapper`.
///
/// If the `Session` is in place, the ICE candidate is added immediately,
/// otherwise, it will be added when the `Session` is restored.
fn add_ice_candidate(&mut self, session_id: &str, sdp_m_line_index: u32, candidate: &str) {
match self {
SessionWrapper::InPlace(session) => {
gst::trace!(CAT, "adding ice candidate for session {session_id}");
session
.webrtcbin
.emit_by_name::<()>("add-ice-candidate", &[&sdp_m_line_index, &candidate]);
}
SessionWrapper::Taken(cands) => {
gst::trace!(CAT, "queuing ice candidate for session {session_id}");
cands.push(IceCandidate {
sdp_m_line_index,
candidate: candidate.to_string(),
});
}
}
}
}
impl From<Session> for SessionWrapper {
fn from(session: Session) -> Self {
SessionWrapper::InPlace(session)
}
}
/* Our internal state */
struct State {
signaller_state: SignallerState,
sessions: HashMap<String, Session>,
sessions: HashMap<String, SessionWrapper>,
codecs: BTreeMap<i32, Codec>,
/// Used to abort codec discovery
codecs_abort_handles: Vec<futures::future::AbortHandle>,
@ -250,6 +430,7 @@ struct State {
audio_serial: u32,
video_serial: u32,
streams: HashMap<String, InputStream>,
discoveries: HashMap<String, Vec<DiscoveryInfo>>,
navigation_handler: Option<NavigationEventHandler>,
mids: HashMap<String, String>,
signaller_signals: Option<SignallerSignals>,
@ -352,6 +533,7 @@ impl Default for State {
audio_serial: 0,
video_serial: 0,
streams: HashMap::new(),
discoveries: HashMap::new(),
navigation_handler: None,
mids: HashMap::new(),
signaller_signals: Default::default(),
@ -389,6 +571,14 @@ fn make_converter_for_video_caps(caps: &gst::Caps, codec: &Codec) -> Result<gst:
gst::Element::link_many([&queue, &nvconvert])?;
(queue, nvconvert)
} else if feature.contains(D3D11_MEMORY_FEATURE) {
let d3d11upload = make_element("d3d11upload", None)?;
let d3d11convert = make_element("d3d11convert", None)?;
ret.add_many([&d3d11upload, &d3d11convert])?;
d3d11upload.link(&d3d11convert)?;
(d3d11upload, d3d11convert)
} else if feature.contains(CUDA_MEMORY_FEATURE) {
if let Some(convert_factory) = gst::ElementFactory::find("cudaconvert") {
let cudaupload = make_element("cudaupload", None)?;
@ -554,6 +744,13 @@ fn configure_encoder(enc: &gst::Element, start_bitrate: u32) {
enc.set_property_from_str("control-rate", "constant_bitrate");
add_nv4l2enc_force_keyunit_workaround(enc);
}
"qsvh264enc" => {
enc.set_property("bitrate", start_bitrate / 1000);
enc.set_property("gop-size", 2560u32);
enc.set_property("low-latency", true);
enc.set_property("disable-hrd-conformance", true);
enc.set_property_from_str("rate-control", "cbr");
}
_ => (),
}
}
@ -684,10 +881,13 @@ impl EncodingChainBuilder {
* provide feedback for audio packets.
*/
if let Some(idx) = self.twcc {
let twcc_extension =
gst_rtp::RTPHeaderExtension::create_from_uri(RTP_TWCC_URI).unwrap();
twcc_extension.set_id(idx);
pay.emit_by_name::<()>("add-extension", &[&twcc_extension]);
if let Some(twcc_extension) = gst_rtp::RTPHeaderExtension::create_from_uri(RTP_TWCC_URI)
{
twcc_extension.set_id(idx);
pay.emit_by_name::<()>("add-extension", &[&twcc_extension]);
} else {
anyhow::bail!("Failed to add TWCC extension, make sure 'gst-plugins-good:rtpmanager' is installed");
}
}
elements.push(pay);
@ -744,7 +944,7 @@ impl VideoEncoder {
fn bitrate(&self) -> i32 {
match self.factory_name.as_str() {
"vp8enc" | "vp9enc" => self.element.property::<i32>("target-bitrate"),
"x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" => {
"x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" | "qsvh264enc" => {
(self.element.property::<u32>("bitrate") * 1000) as i32
}
"nvv4l2h264enc" | "nvv4l2vp8enc" | "nvv4l2vp9enc" => {
@ -771,7 +971,7 @@ impl VideoEncoder {
pub(crate) fn set_bitrate(&mut self, element: &super::BaseWebRTCSink, bitrate: i32) {
match self.factory_name.as_str() {
"vp8enc" | "vp9enc" => self.element.set_property("target-bitrate", bitrate),
"x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" => self
"x264enc" | "nvh264enc" | "vaapih264enc" | "vaapivp8enc" | "qsvh264enc" => self
.element
.set_property("bitrate", (bitrate / 1000) as u32),
"nvv4l2h264enc" | "nvv4l2vp8enc" | "nvv4l2vp9enc" => {
@ -895,7 +1095,8 @@ impl State {
}
fn end_session(&mut self, session_id: &str) -> Option<Session> {
if let Some(mut session) = self.sessions.remove(session_id) {
if let Some(session) = self.sessions.remove(session_id) {
let mut session = session.into_inner();
self.finalize_session(&mut session);
Some(session)
} else {
@ -908,6 +1109,27 @@ impl State {
&& element.current_state() >= gst::State::Paused
&& self.codec_discovery_done
}
fn queue_discovery(&mut self, stream_name: &str, discovery_info: DiscoveryInfo) {
if let Some(discos) = self.discoveries.get_mut(stream_name) {
discos.push(discovery_info);
} else {
self.discoveries
.insert(stream_name.to_string(), vec![discovery_info]);
}
}
fn remove_discovery(&mut self, stream_name: &str, discovery_info: &DiscoveryInfo) {
if let Some(discos) = self.discoveries.get_mut(stream_name) {
let position = discos
.iter()
.position(|d| d.id == discovery_info.id)
.expect(
"We expect discovery to always be in the list of discoverers when removing",
);
discos.remove(position);
}
}
}
impl Session {
@ -1195,26 +1417,12 @@ impl InputStream {
}
}
fn create_discovery(&mut self, type_: DiscoveryType) -> DiscoveryInfo {
let discovery_info = DiscoveryInfo::new(
type_,
fn create_discovery(&self) -> DiscoveryInfo {
DiscoveryInfo::new(
self.in_caps.clone().expect(
"We should never create a discovery for a stream that doesn't have caps set",
),
);
self.discoveries.push(discovery_info.clone());
discovery_info
}
fn remove_discovery(&mut self, discovery: &DiscoveryInfo) {
let id = self
.discoveries
.iter()
.position(|d| d.id == discovery.id)
.expect("We expect discovery to always be in the list of discoverers when removing");
self.discoveries.remove(id);
)
}
}
@ -1271,9 +1479,15 @@ impl BaseWebRTCSink {
let ssrc = BaseWebRTCSink::generate_ssrc(element, webrtc_pads);
let media_idx = webrtc_pads.len() as i32;
let pad = webrtcbin
.request_pad_simple(&format!("sink_{}", media_idx))
.unwrap();
let Some(pad) = webrtcbin.request_pad_simple(&format!("sink_{}", media_idx)) else {
gst::error!(CAT, obj: element, "Failed to request pad from webrtcbin");
gst::element_error!(
element,
gst::StreamError::Failed,
["Failed to request pad from webrtcbin"]
);
return;
};
let transceiver = pad.property::<gst_webrtc::WebRTCRTPTransceiver>("transceiver");
@ -1315,7 +1529,7 @@ impl BaseWebRTCSink {
let mut payloader_caps = match media {
Some(media) => {
let discovery_info = stream.create_discovery(DiscoveryType::CodecSelection);
let discovery_info = stream.create_discovery();
let codec = BaseWebRTCSink::select_codec(
element,
@ -1327,8 +1541,6 @@ impl BaseWebRTCSink {
)
.await;
stream.remove_discovery(&discovery_info);
match codec {
Some(codec) => {
gst::debug!(
@ -1368,9 +1580,15 @@ impl BaseWebRTCSink {
payloader_caps
);
let pad = webrtcbin
.request_pad_simple(&format!("sink_{}", media_idx))
.unwrap();
let Some(pad) = webrtcbin.request_pad_simple(&format!("sink_{}", media_idx)) else {
gst::error!(CAT, obj: element, "Failed to request pad from webrtcbin");
gst::element_error!(
element,
gst::StreamError::Failed,
["Failed to request pad from webrtcbin"]
);
return;
};
let transceiver = pad.property::<gst_webrtc::WebRTCRTPTransceiver>("transceiver");
@ -1607,6 +1825,7 @@ impl BaseWebRTCSink {
if let Some(session) = state.sessions.get(session_id) {
session
.unwrap()
.webrtcbin
.emit_by_name::<()>("set-local-description", &[&offer, &None::<gst::Promise>]);
drop(state);
@ -1626,7 +1845,8 @@ impl BaseWebRTCSink {
drop(settings);
let mut state = self.state.lock().unwrap();
if let Some(mut session) = state.sessions.remove(session_id) {
if let Some(session) = state.sessions.get_mut(session_id) {
let mut session = session.take();
let sdp = answer.sdp();
session.sdp = Some(sdp.to_owned());
@ -1638,13 +1858,20 @@ impl BaseWebRTCSink {
.and_then(|format| format.parse::<i32>().ok());
}
drop(state);
session
.webrtcbin
.emit_by_name::<()>("set-local-description", &[&answer, &None::<gst::Promise>]);
let mut state = self.state.lock().unwrap();
let session_id = session.id.clone();
state.sessions.insert(session.id.clone(), session);
if let Some(session_wrapper) = state.sessions.get_mut(&session_id) {
session_wrapper.restore(session);
} else {
gst::warning!(CAT, "Session {session_id} was removed");
}
drop(state);
signaller.send_sdp(&session_id, &answer);
@ -1709,6 +1936,7 @@ impl BaseWebRTCSink {
});
session
.unwrap()
.webrtcbin
.emit_by_name::<()>("create-answer", &[&None::<gst::Structure>, &promise]);
}
@ -1843,6 +2071,7 @@ impl BaseWebRTCSink {
gst::debug!(CAT, obj: element, "Negotiating for session {}", session_id);
if let Some(session) = state.sessions.get(session_id) {
let session = session.unwrap();
gst::trace!(CAT, "WebRTC pads: {:?}", session.webrtc_pads);
if let Some(offer) = offer {
@ -2144,7 +2373,7 @@ impl BaseWebRTCSink {
let state = this.state.lock().unwrap();
if let Some(session) = state.sessions.get(&session_id_clone) {
for webrtc_pad in session.webrtc_pads.values() {
for webrtc_pad in session.unwrap().webrtc_pads.values() {
if let Some(srcpad) = webrtc_pad.pad.peer() {
srcpad.send_event(
gst_video::UpstreamForceKeyUnitEvent::builder()
@ -2210,7 +2439,7 @@ impl BaseWebRTCSink {
let element = element.expect("on-new-ssrc emitted when webrtcsink has been disposed?");
let mut state = element.imp().state.lock().unwrap();
if let Some(session) = state.sessions.get_mut(&session_id_str) {
let session = session.unwrap_mut();
if session.stats_sigid.is_none() {
let session_id_str = session_id_str.clone();
let element = element.downgrade();
@ -2234,15 +2463,20 @@ impl BaseWebRTCSink {
pipeline.set_start_time(gst::ClockTime::NONE);
pipeline.set_base_time(element.base_time().unwrap());
let mut bus_stream = pipeline.bus().unwrap().stream();
let bus = pipeline.bus().unwrap();
let mut bus_stream = CustomBusStream::new(&element, &bus);
let element_clone = element.downgrade();
let pipeline_clone = pipeline.downgrade();
let session_id_clone = session_id.clone();
RUNTIME.spawn(async move {
while let Some(msg) = bus_stream.next().await {
let Some(element) = element_clone.upgrade() else { break; };
let Some(pipeline) = pipeline_clone.upgrade() else { break; };
let Some(element) = element_clone.upgrade() else {
break;
};
let Some(pipeline) = pipeline_clone.upgrade() else {
break;
};
let this = element.imp();
match msg.view() {
gst::MessageView::Error(err) => {
@ -2285,7 +2519,9 @@ impl BaseWebRTCSink {
}
});
state.sessions.insert(session_id.to_string(), session);
state
.sessions
.insert(session_id.to_string(), session.into());
let mut streams: Vec<InputStream> = state.streams.values().cloned().collect();
@ -2365,12 +2601,12 @@ impl BaseWebRTCSink {
{
let mut state = this.state.lock().unwrap();
if let Some(mut session) = state.sessions.remove(&session_id) {
if let Some(session) = state.sessions.get_mut(&session_id) {
let session = session.unwrap_mut();
session.webrtc_pads = webrtc_pads;
if offer_clone.is_some() {
session.codecs = Some(codecs);
}
state.sessions.insert(session_id.to_owned(), session);
}
}
@ -2460,6 +2696,7 @@ impl BaseWebRTCSink {
) {
let mut state = element.imp().state.lock().unwrap();
if let Some(session) = state.sessions.get_mut(session_id) {
let session = session.unwrap_mut();
if let Some(congestion_controller) = session.congestion_controller.as_mut() {
congestion_controller.loss_control(element, stats, &mut session.encoders);
}
@ -2480,6 +2717,7 @@ impl BaseWebRTCSink {
let mut state = element.imp().state.lock().unwrap();
if let Some(session) = state.sessions.get_mut(&session_id) {
let session = session.unwrap_mut();
if let Some(congestion_controller) = session.congestion_controller.as_mut() {
congestion_controller.delay_control(&element, stats, &mut session.encoders,);
}
@ -2501,7 +2739,7 @@ impl BaseWebRTCSink {
let mut state = element.imp().state.lock().unwrap();
if let Some(session) = state.sessions.get_mut(session_id) {
session.rtprtxsend = Some(rtprtxsend);
session.unwrap_mut().rtprtxsend = Some(rtprtxsend);
}
}
@ -2510,6 +2748,8 @@ impl BaseWebRTCSink {
let mut state = element.imp().state.lock().unwrap();
if let Some(session) = state.sessions.get_mut(session_id) {
let session = session.unwrap_mut();
let n_encoders = session.encoders.len();
let fec_ratio = {
@ -2544,13 +2784,17 @@ impl BaseWebRTCSink {
let mut remove = false;
let codecs = state.codecs.clone();
if let Some(mut session) = state.sessions.remove(&session_id) {
if let Some(session) = state.sessions.get_mut(&session_id) {
let mut session = session.take();
for webrtc_pad in session.webrtc_pads.clone().values() {
let transceiver = webrtc_pad
.pad
.property::<gst_webrtc::WebRTCRTPTransceiver>("transceiver");
let Some(ref stream_name) = webrtc_pad.stream_name else { continue; };
let Some(ref stream_name) = webrtc_pad.stream_name else {
continue;
};
if let Some(mid) = transceiver.mid() {
state.mids.insert(mid.to_string(), stream_name.clone());
@ -2617,14 +2861,17 @@ impl BaseWebRTCSink {
}));
if remove {
let _ = state.sessions.remove(&session_id);
state.finalize_session(&mut session);
drop(state);
let settings = self.settings.lock().unwrap();
let signaller = settings.signaller.clone();
drop(settings);
signaller.end_session(&session_id);
} else if let Some(session_wrapper) = state.sessions.get_mut(&session_id) {
session_wrapper.restore(session);
} else {
state.sessions.insert(session.id.clone(), session);
gst::warning!(CAT, "Session {session_id} was removed");
}
}
}
@ -2637,7 +2884,7 @@ impl BaseWebRTCSink {
_sdp_mid: Option<String>,
candidate: &str,
) {
let state = self.state.lock().unwrap();
let mut state = self.state.lock().unwrap();
let sdp_m_line_index = match sdp_m_line_index {
Some(sdp_m_line_index) => sdp_m_line_index,
@ -2647,11 +2894,8 @@ impl BaseWebRTCSink {
}
};
if let Some(session) = state.sessions.get(session_id) {
gst::trace!(CAT, "adding ice candidate for session {}", session_id);
session
.webrtcbin
.emit_by_name::<()>("add-ice-candidate", &[&sdp_m_line_index, &candidate]);
if let Some(session_wrapper) = state.sessions.get_mut(session_id) {
session_wrapper.add_ice_candidate(session_id, sdp_m_line_index, candidate);
} else {
gst::warning!(CAT, "No consumer with ID {session_id}");
}
@ -2666,6 +2910,8 @@ impl BaseWebRTCSink {
let mut state = self.state.lock().unwrap();
if let Some(session) = state.sessions.get_mut(session_id) {
let session = session.unwrap_mut();
let sdp = desc.sdp();
session.sdp = Some(sdp.to_owned());
@ -2838,81 +3084,96 @@ impl BaseWebRTCSink {
.link(&sink)
.with_context(|| format!("Running discovery pipeline for caps {input_caps}"))?;
let mut stream = pipe.0.bus().unwrap().stream();
let bus = pipe.0.bus().unwrap();
let mut stream = CustomBusStream::new(element, &bus);
pipe.0
.set_state(gst::State::Playing)
.with_context(|| format!("Running discovery pipeline for caps {input_caps}"))?;
while let Some(msg) = stream.next().await {
match msg.view() {
gst::MessageView::Error(err) => {
gst::error!(CAT, "Error in discovery pipeline: {err:#?}");
pipe.0.debug_to_dot_file_with_ts(
gst::DebugGraphDetails::all(),
"webrtcsink-discovery-error",
);
return Err(err.error().into());
}
gst::MessageView::StateChanged(s) => {
if msg.src() == Some(pipe.0.upcast_ref()) {
pipe.0.debug_to_dot_file_with_ts(
gst::DebugGraphDetails::all(),
format!(
"webrtcsink-discovery-{}-{:?}-{:?}",
pipe.0.name(),
s.old(),
s.current()
),
);
}
continue;
}
gst::MessageView::Application(appmsg) => {
let caps = match appmsg.structure() {
Some(s) => {
if s.name().as_str() != "payloaded_caps" {
continue;
}
s.get::<gst::Caps>("caps").unwrap()
}
_ => continue,
};
gst::info!(CAT, "Discovery pipeline got caps {caps:?}");
pipe.0.debug_to_dot_file_with_ts(
gst::DebugGraphDetails::all(),
"webrtcsink-discovery-done",
);
if let Some(s) = caps.structure(0) {
let mut s = s.to_owned();
s.remove_fields([
"timestamp-offset",
"seqnum-offset",
"ssrc",
"sprop-parameter-sets",
"a-framerate",
]);
s.set("payload", codec.payload().unwrap());
gst::debug!(
CAT,
obj: element,
"Codec discovery pipeline for caps {input_caps} with codec {codec:?} succeeded: {s}"
);
return Ok(s);
} else {
return Err(anyhow!("Discovered empty caps"));
}
}
_ => {
continue;
}
}
{
let mut state = element.imp().state.lock().unwrap();
state.queue_discovery(stream_name, discovery_info.clone());
}
unreachable!()
let ret = {
loop {
if let Some(msg) = stream.next().await {
match msg.view() {
gst::MessageView::Error(err) => {
gst::warning!(CAT, "Error in discovery pipeline: {err:#?}");
pipe.0.debug_to_dot_file_with_ts(
gst::DebugGraphDetails::all(),
"webrtcsink-discovery-error",
);
break Err(err.error().into());
}
gst::MessageView::StateChanged(s) => {
if msg.src() == Some(pipe.0.upcast_ref()) {
pipe.0.debug_to_dot_file_with_ts(
gst::DebugGraphDetails::all(),
format!(
"webrtcsink-discovery-{}-{:?}-{:?}",
pipe.0.name(),
s.old(),
s.current()
),
);
}
continue;
}
gst::MessageView::Application(appmsg) => {
let caps = match appmsg.structure() {
Some(s) => {
if s.name().as_str() != "payloaded_caps" {
continue;
}
s.get::<gst::Caps>("caps").unwrap()
}
_ => continue,
};
gst::info!(CAT, "Discovery pipeline got caps {caps:?}");
pipe.0.debug_to_dot_file_with_ts(
gst::DebugGraphDetails::all(),
format!("webrtcsink-discovery-{}-done", pipe.0.name()),
);
if let Some(s) = caps.structure(0) {
let mut s = s.to_owned();
s.remove_fields([
"timestamp-offset",
"seqnum-offset",
"ssrc",
"sprop-parameter-sets",
"a-framerate",
]);
s.set("payload", codec.payload().unwrap());
gst::debug!(
CAT,
obj: element,
"Codec discovery pipeline for caps {input_caps} with codec {codec:?} succeeded: {s}"
);
break Ok(s);
} else {
break Err(anyhow!("Discovered empty caps"));
}
}
_ => {
continue;
}
}
} else {
unreachable!()
}
}
};
let mut state = element.imp().state.lock().unwrap();
state.remove_discovery(stream_name, discovery_info);
ret
}
async fn lookup_caps(
@ -3011,7 +3272,12 @@ impl BaseWebRTCSink {
.unwrap()
.sessions
.iter()
.map(|(name, consumer)| (name.as_str(), consumer.gather_stats().to_send_value())),
.map(|(name, consumer)| {
(
name.as_str(),
consumer.unwrap().gather_stats().to_send_value(),
)
}),
)
}
@ -3068,36 +3334,36 @@ impl BaseWebRTCSink {
gst::Pad::event_default(pad, Some(element), event)
}
fn start_stream_discovery_if_needed(&self, stream_name: &str, buffer: &gst::Buffer) {
let (codecs, discovery_info) = {
let mut state = self.state.lock().unwrap();
let stream = state.streams.get_mut(stream_name).unwrap();
fn feed_discoveries(&self, stream_name: &str, buffer: &gst::Buffer) {
let state = self.state.lock().unwrap();
// Discovery already happened... nothing to do here.
if stream.out_caps.is_some() {
return;
}
let mut discovery_started = false;
for discovery_info in stream.discoveries.iter() {
if matches!(discovery_info.type_, DiscoveryType::Initial) {
discovery_started = true;
}
if let Some(discos) = state.discoveries.get(stream_name) {
for discovery_info in discos.iter() {
for src in discovery_info.srcs() {
if let Err(err) = src.push_buffer(buffer.clone()) {
gst::log!(CAT, obj: src, "Failed to push buffer: {}", err);
}
}
}
}
}
if discovery_started {
// Discovery already started, we pushed the buffer to keep it
// going
return;
}
fn start_stream_discovery_if_needed(&self, stream_name: &str) {
let (codecs, discovery_info) = {
let mut state = self.state.lock().unwrap();
let discovery_info = stream.create_discovery(DiscoveryType::Initial);
stream.discoveries.push(discovery_info.clone());
let discovery_info = {
let stream = state.streams.get_mut(stream_name).unwrap();
// Initial discovery already happened... nothing to do here.
if stream.initial_discovery_started {
return;
}
stream.initial_discovery_started = true;
stream.create_discovery()
};
let codecs = if !state.codecs.is_empty() {
Codecs::from_map(&state.codecs)
@ -3121,8 +3387,8 @@ impl BaseWebRTCSink {
let (fut, handle) = futures::future::abortable(
Self::lookup_caps(
element,
discovery_info,
stream_name_clone,
discovery_info.clone(),
stream_name_clone.clone(),
gst::Caps::new_any(),
&codecs,
));
@ -3162,12 +3428,9 @@ impl BaseWebRTCSink {
_ => (),
}
let _ = codecs_done_sender.send(());
}));
let mut state = self.state.lock().unwrap();
let stream = state.streams.get_mut(stream_name).unwrap();
stream.remove_discovery(&discovery_info);
}
fn chain(
@ -3175,7 +3438,8 @@ impl BaseWebRTCSink {
pad: &gst::GhostPad,
buffer: gst::Buffer,
) -> Result<gst::FlowSuccess, gst::FlowError> {
self.start_stream_discovery_if_needed(pad.name().as_str(), &buffer);
self.start_stream_discovery_if_needed(pad.name().as_str());
self.feed_discoveries(pad.name().as_str(), &buffer);
gst::ProxyPad::chain_default(pad, Some(&*self.obj()), buffer)
}
@ -3594,6 +3858,10 @@ impl ElementImpl for BaseWebRTCSink {
.structure_with_features(
gst::Structure::builder("video/x-raw").build(),
gst::CapsFeatures::new([NVMM_MEMORY_FEATURE]),
)
.structure_with_features(
gst::Structure::builder("video/x-raw").build(),
gst::CapsFeatures::new([D3D11_MEMORY_FEATURE]),
);
for codec in Codecs::video_codecs() {
@ -3688,7 +3956,7 @@ impl ElementImpl for BaseWebRTCSink {
clocksync: None,
is_video,
serial,
discoveries: Default::default(),
initial_discovery_started: false,
},
);

View file

@ -31,6 +31,7 @@ const RTP_TWCC_URI: &str =
struct Settings {
stun_server: Option<String>,
turn_servers: gst::Array,
signaller: Signallable,
meta: Option<gst::Structure>,
video_codecs: Vec<Codec>,
@ -58,11 +59,24 @@ impl ObjectImpl for WebRTCSrc {
static PROPS: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
vec![
glib::ParamSpecString::builder("stun-server")
.nick("The STUN server to use")
.blurb("The STUN server of the form stun://host:port")
.flags(glib::ParamFlags::READWRITE)
.default_value(DEFAULT_STUN_SERVER)
.mutable_ready()
.build(),
gst::ParamSpecArray::builder("turn-servers")
.nick("List of TURN servers to use")
.blurb("The TURN servers of the form <\"turn(s)://username:password@host:port\", \"turn(s)://username1:password1@host1:port1\">")
.element_spec(&glib::ParamSpecString::builder("turn-server")
.nick("TURN Server")
.blurb("The TURN server of the form turn(s)://username:password@host:port.")
.build()
)
.mutable_ready()
.build(),
glib::ParamSpecObject::builder::<Signallable>("signaller")
.flags(glib::ParamFlags::READWRITE | gst::PARAM_FLAG_MUTABLE_READY)
.flags(glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY)
.blurb("The Signallable object to use to handle WebRTC Signalling")
.build(),
glib::ParamSpecBoxed::builder::<gst::Structure>("meta")
@ -92,35 +106,45 @@ impl ObjectImpl for WebRTCSrc {
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"signaller" => {
self.settings.lock().unwrap().signaller =
value.get::<Signallable>().expect("type checked upstream");
let signaller = value
.get::<Option<Signallable>>()
.expect("type checked upstream");
if let Some(signaller) = signaller {
self.settings.lock().unwrap().signaller = signaller;
}
// else: signaller not set as a construct property
// => use default Signaller
}
"video-codecs" => {
self.settings.lock().unwrap().video_codecs = value
.get::<gst::ArrayRef>()
.expect("Type checked upstream")
.expect("type checked upstream")
.as_slice()
.iter()
.filter_map(|codec_name| {
Codecs::find(codec_name.get::<&str>().expect("Type checked upstream"))
Codecs::find(codec_name.get::<&str>().expect("type checked upstream"))
})
.collect::<Vec<Codec>>()
}
"audio-codecs" => {
self.settings.lock().unwrap().audio_codecs = value
.get::<gst::ArrayRef>()
.expect("Type checked upstream")
.expect("type checked upstream")
.as_slice()
.iter()
.filter_map(|codec_name| {
Codecs::find(codec_name.get::<&str>().expect("Type checked upstream"))
Codecs::find(codec_name.get::<&str>().expect("type checked upstream"))
})
.collect::<Vec<Codec>>()
}
"stun-server" => {
self.settings.lock().unwrap().stun_server = value
.get::<Option<String>>()
.expect("type checked upstream")
.expect("type checked upstream");
}
"turn-servers" => {
let mut settings = self.settings.lock().unwrap();
settings.turn_servers = value.get::<gst::Array>().expect("type checked upstream");
}
"meta" => {
self.settings.lock().unwrap().meta = value
@ -153,6 +177,7 @@ impl ObjectImpl for WebRTCSrc {
)
.to_value(),
"stun-server" => self.settings.lock().unwrap().stun_server.to_value(),
"turn-servers" => self.settings.lock().unwrap().turn_servers.to_value(),
"meta" => self.settings.lock().unwrap().meta.to_value(),
name => panic!("{} getter not implemented", name),
}
@ -209,6 +234,7 @@ impl Default for Settings {
Self {
stun_server: DEFAULT_STUN_SERVER.map(|v| v.to_string()),
turn_servers: Default::default(),
signaller: signaller.upcast(),
meta: Default::default(),
audio_codecs: Codecs::audio_codecs()
@ -414,8 +440,16 @@ impl WebRTCSrc {
.build()
.with_context(|| "Failed to make element webrtcbin".to_string())?;
if let Some(stun_server) = self.settings.lock().unwrap().stun_server.as_ref() {
webrtcbin.set_property("stun-server", stun_server);
{
let settings = self.settings.lock().unwrap();
if let Some(stun_server) = settings.stun_server.as_ref() {
webrtcbin.set_property("stun-server", stun_server);
}
for turn_server in settings.turn_servers.iter() {
webrtcbin.emit_by_name::<bool>("add-turn-server", &[&turn_server]);
}
}
let bin = gst::Bin::new();
@ -495,6 +529,8 @@ impl WebRTCSrc {
}
fn connect_signaller(&self, signaller: &Signallable) {
let instance = &*self.obj();
let _ = self
.state
.lock()
@ -504,10 +540,10 @@ impl WebRTCSrc {
error: signaller.connect_closure(
"error",
false,
glib::closure!(@to-owned self as this => move |
glib::closure!(@watch instance => move |
_signaller: glib::Object, error: String| {
gst::element_error!(
this.obj(),
instance,
gst::StreamError::Failed,
["Signalling error: {}", error]
);
@ -517,12 +553,13 @@ impl WebRTCSrc {
session_started: signaller.connect_closure(
"session-started",
false,
glib::closure!(@to-owned self as this => move |
glib::closure!(@watch instance => move |
_signaller: glib::Object,
session_id: &str,
_peer_id: &str| {
gst::info!(CAT, imp: this, "Session started: {session_id}");
this.state.lock().unwrap().session_id =
let imp = instance.imp();
gst::info!(CAT, imp: imp, "Session started: {session_id}");
imp.state.lock().unwrap().session_id =
Some(session_id.to_string());
}),
),
@ -530,9 +567,9 @@ impl WebRTCSrc {
session_ended: signaller.connect_closure(
"session-ended",
false,
glib::closure!(@to-owned self as this => move |_signaler: glib::Object, _session_id: &str|{
this.state.lock().unwrap().session_id = None;
this.obj().iterate_src_pads().into_iter().for_each(|pad|
glib::closure!(@watch instance => move |_signaler: glib::Object, _session_id: &str|{
instance.imp().state.lock().unwrap().session_id = None;
instance.iterate_src_pads().into_iter().for_each(|pad|
{ if let Err(e) = pad.map(|pad| pad.push_event(gst::event::Eos::new())) {
gst::error!(CAT, "Could not send EOS: {e:?}");
}}
@ -545,24 +582,22 @@ impl WebRTCSrc {
request_meta: signaller.connect_closure(
"request-meta",
false,
glib::closure!(@to-owned self as this => move |
glib::closure!(@watch instance => move |
_signaller: glib::Object| -> Option<gst::Structure> {
let meta = this.settings.lock().unwrap().meta.clone();
meta
instance.imp().settings.lock().unwrap().meta.clone()
}),
),
session_description: signaller.connect_closure(
"session-description",
false,
glib::closure!(@to-owned self as this => move |
glib::closure!(@watch instance => move |
_signaller: glib::Object,
_peer_id: &str,
desc: &gst_webrtc::WebRTCSessionDescription| {
assert_eq!(desc.type_(), gst_webrtc::WebRTCSDPType::Offer);
this.handle_offer(desc);
instance.imp().handle_offer(desc);
}),
),
@ -572,13 +607,13 @@ impl WebRTCSrc {
handle_ice: signaller.connect_closure(
"handle-ice",
false,
glib::closure!(@to-owned self as this => move |
glib::closure!(@watch instance => move |
_signaller: glib::Object,
peer_id: &str,
sdp_m_line_index: u32,
_sdp_mid: Option<String>,
candidate: &str| {
this.handle_ice(peer_id, Some(sdp_m_line_index), None, candidate);
instance.imp().handle_ice(peer_id, Some(sdp_m_line_index), None, candidate);
}),
),
});

View file

@ -107,28 +107,37 @@ impl Signaller {
);
let timeout;
let endpoint;
{
let settings = self.settings.lock().unwrap();
timeout = settings.timeout;
endpoint =
reqwest::Url::parse(settings.whip_endpoint.as_ref().unwrap().as_str()).unwrap();
drop(settings);
}
if let Err(e) =
wait_async(&self.canceller, self.do_post(offer_sdp, webrtcbin), timeout).await
if let Err(e) = wait_async(
&self.canceller,
self.do_post(offer_sdp, webrtcbin, endpoint),
timeout,
)
.await
{
self.handle_future_error(e);
}
}
#[async_recursion]
async fn do_post(&self, offer: gst_webrtc::WebRTCSessionDescription, webrtcbin: &gst::Element) {
async fn do_post(
&self,
offer: gst_webrtc::WebRTCSessionDescription,
webrtcbin: &gst::Element,
endpoint: reqwest::Url,
) {
let auth_token;
let endpoint;
{
let settings = self.settings.lock().unwrap();
endpoint =
reqwest::Url::parse(settings.whip_endpoint.as_ref().unwrap().as_str()).unwrap();
auth_token = settings.auth_token.clone();
drop(settings);
}
@ -324,7 +333,7 @@ impl Signaller {
redirect_url.as_str()
);
self.do_post(offer, webrtcbin).await
self.do_post(offer, webrtcbin, redirect_url).await
}
Err(e) => self.raise_error(e.to_string()),
}
@ -422,9 +431,8 @@ impl SignallableImpl for Signaller {
webrtcbin: &gst::Element| {
let obj_weak = signaller.downgrade();
webrtcbin.connect_notify(Some("ice-gathering-state"), move |webrtcbin, _pspec| {
let obj = match obj_weak.upgrade() {
Some(obj) => obj,
None => return,
let Some(obj) = obj_weak.upgrade() else {
return;
};
let state =

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-webrtchttp"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Taruntej Kanakamalla <taruntej@asymptotic.io"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -11,9 +11,9 @@ rust-version = "1.70"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-webrtc = { package = "gstreamer-webrtc", git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
gst-sdp = { package = "gstreamer-sdp", git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-webrtc = { package = "gstreamer-webrtc", git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21", features = ["v1_18"] }
gst-sdp = { package = "gstreamer-sdp", git="https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch="0.21", version="0.21" }
reqwest = { version = "0.11", features = ["default-tls"] }
parse_link_header = {version = "0.3", features = ["url"]}
tokio = { version = "1.20.1", default-features = false, features = ["time", "rt-multi-thread"] }
@ -27,7 +27,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -426,9 +426,8 @@ impl WhepSrc {
let self_weak = self.downgrade();
self.webrtcbin
.connect_notify(Some("ice-gathering-state"), move |webrtcbin, _pspec| {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return,
let Some(self_) = self_weak.upgrade() else {
return;
};
let state = webrtcbin.property::<WebRTCICEGatheringState>("ice-gathering-state");
@ -458,9 +457,8 @@ impl WhepSrc {
let self_weak = self.downgrade();
self.webrtcbin
.connect_notify(Some("ice-connection-state"), move |webrtcbin, _pspec| {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return,
let Some(self_) = self_weak.upgrade() else {
return;
};
let state = webrtcbin.property::<WebRTCICEConnectionState>("ice-connection-state");
@ -489,9 +487,8 @@ impl WhepSrc {
let self_weak = self.downgrade();
self.webrtcbin
.connect_notify(Some("connection-state"), move |webrtcbin, _pspec| {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return,
let Some(self_) = self_weak.upgrade() else {
return;
};
let state = webrtcbin.property::<WebRTCPeerConnectionState>("connection-state");
@ -520,9 +517,8 @@ impl WhepSrc {
let self_weak = self.downgrade();
self.webrtcbin.connect_pad_added(move |_, pad| {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return,
let Some(self_) = self_weak.upgrade() else {
return;
};
gst::debug!(
@ -547,9 +543,8 @@ impl WhepSrc {
let self_weak = self.downgrade();
self.webrtcbin.connect("on-negotiation-needed", false, {
move |_| {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return None,
let Some(self_) = self_weak.upgrade() else {
return None;
};
let settings = self_.settings.lock().unwrap();
@ -748,7 +743,7 @@ impl WhepSrc {
redirect_url.as_str()
);
self.do_post(sess_desc).await
self.do_post(sess_desc, redirect_url).await
}
Err(e) => self.raise_error(gst::ResourceError::Failed, e.to_string()),
}
@ -780,9 +775,8 @@ impl WhepSrc {
fn generate_offer(&self) {
let self_weak = self.downgrade();
let promise = gst::Promise::with_change_func(move |reply| {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return,
let Some(self_) = self_weak.upgrade() else {
return;
};
let reply = match reply {
@ -918,26 +912,29 @@ impl WhepSrc {
let sess_desc = WebRTCSessionDescription::new(WebRTCSDPType::Offer, offer_sdp.sdp());
let timeout;
let endpoint;
{
let settings = self.settings.lock().unwrap();
timeout = settings.timeout;
endpoint =
reqwest::Url::parse(settings.whep_endpoint.as_ref().unwrap().as_str()).unwrap();
drop(settings);
}
if let Err(e) = wait_async(&self.canceller, self.do_post(sess_desc), timeout).await {
if let Err(e) =
wait_async(&self.canceller, self.do_post(sess_desc, endpoint), timeout).await
{
self.handle_future_error(e);
}
}
#[async_recursion]
async fn do_post(&self, offer: WebRTCSessionDescription) {
async fn do_post(&self, offer: WebRTCSessionDescription, endpoint: reqwest::Url) {
let auth_token;
let endpoint;
{
let settings = self.settings.lock().unwrap();
endpoint =
reqwest::Url::parse(settings.whep_endpoint.as_ref().unwrap().as_str()).unwrap();
auth_token = settings.auth_token.clone();
drop(settings);
}

View file

@ -350,9 +350,8 @@ impl ObjectImpl for WhipSink {
let self_weak = self.downgrade();
self.webrtcbin
.connect_notify(Some("ice-gathering-state"), move |webrtcbin, _pspec| {
let self_ = match self_weak.upgrade() {
Some(self_) => self_,
None => return,
let Some(self_) = self_weak.upgrade() else {
return;
};
let state = webrtcbin.property::<WebRTCICEGatheringState>("ice-gathering-state");
@ -550,26 +549,29 @@ impl WhipSink {
);
let timeout;
let endpoint;
{
let settings = self.settings.lock().unwrap();
timeout = settings.timeout;
endpoint =
reqwest::Url::parse(settings.whip_endpoint.as_ref().unwrap().as_str()).unwrap();
drop(settings);
}
if let Err(e) = wait_async(&self.canceller, self.do_post(offer_sdp), timeout).await {
if let Err(e) =
wait_async(&self.canceller, self.do_post(offer_sdp, endpoint), timeout).await
{
self.handle_future_error(e);
}
}
#[async_recursion]
async fn do_post(&self, offer: gst_webrtc::WebRTCSessionDescription) {
async fn do_post(&self, offer: gst_webrtc::WebRTCSessionDescription, endpoint: reqwest::Url) {
let auth_token;
let endpoint;
{
let settings = self.settings.lock().unwrap();
endpoint =
reqwest::Url::parse(settings.whip_endpoint.as_ref().unwrap().as_str()).unwrap();
auth_token = settings.auth_token.clone();
drop(settings);
}
@ -774,7 +776,7 @@ impl WhipSink {
redirect_url.as_str()
);
self.do_post(offer).await
self.do_post(offer, redirect_url).await
}
Err(e) => self.raise_error(gst::ResourceError::Failed, e.to_string()),
}

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-textahead"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Guillaume Desmottes <guillaume@desmottes.be>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,7 +9,7 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gsttextahead"
@ -17,7 +17,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-json"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
license = "MPL-2.0"
edition = "2021"
@ -14,6 +14,8 @@ serde_json = { version = "1.0", features = ["raw_value"] }
[dependencies.gst]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
branch = "0.21"
version = "0.21"
package="gstreamer"
features=["serde"]
@ -23,10 +25,12 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[dev-dependencies.gst-check]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
branch = "0.21"
version = "0.21"
package="gstreamer-check"
[features]

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-regex"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
license = "MPL-2.0"
edition = "2021"
@ -13,6 +13,8 @@ regex = "1.5"
[dependencies.gst]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
branch = "0.21"
version = "0.21"
package="gstreamer"
[lib]
@ -21,10 +23,12 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[dev-dependencies.gst-check]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
branch = "0.21"
version = "0.21"
package="gstreamer-check"
[features]

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-textwrap"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
license = "MPL-2.0"
edition = "2021"
@ -14,6 +14,8 @@ hyphenation = "0.8"
[dependencies.gst]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
branch = "0.21"
version = "0.21"
package="gstreamer"
[lib]
@ -22,10 +24,12 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[dev-dependencies.gst-check]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
branch = "0.21"
version = "0.21"
package="gstreamer-check"
[features]

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-tutorial"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MIT OR Apache-2.0"
@ -9,10 +9,10 @@ rust-version = "1.70"
description = "GStreamer Rust Tutorial Plugin"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
byte-slice-cast = "1.0"
num-traits = "0.2"
@ -22,4 +22,4 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../version-helper" }
gst-plugin-version-helper = { path="../version-helper", version = "0.8" }

View file

@ -6,17 +6,17 @@ In this first part were going to write a plugin that contains a video filter
This will show the basics of how to write a GStreamer plugin and element in Rust: the basic setup for registering a type and implementing it in Rust, and how to use the various GStreamer API and APIs from the Rust standard library to do the processing.
The final code for this plugin can be found [here](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/tree/main/gst-plugin-tutorial), and it is based on latest git version of the [gstreamer](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs). At least Rust 1.32 is required for all this. You also need to have GStreamer (at least version 1.8) installed for your platform, see e.g. the GStreamer bindings [installation instructions](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/blob/main/README.md).
The final code for this plugin can be found [here](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/tree/main/tutorial), and it is based on latest git version of the [gstreamer](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs). At least Rust 1.32 is required for all this. You also need to have GStreamer (at least version 1.8) installed for your platform, see e.g. the GStreamer bindings [installation instructions](https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/blob/main/README.md).
# Table of contents
1. [Project Structure](#project-structure)
2. [Plugin Initialization](#plugin-initialization)
3. [Type Registration](#type-registration)
4. [Type Class and Instance Initialization](#type-class-and-instance-initialization)
5. [Caps and Pad Templates](#caps-and-pad-templates)
6. [Caps Handling Part 1](#caps-handling-part-1)
7. [Caps Handling Part 2](#caps-handling-part-2)
4. [Type Class & Instance Initialization](#type-class-instance-initialization)
5. [Debug Category](#debug-category)
6. [Caps & Pad Templates](#caps-pad-templates)
7. [Caps Handling](#caps-handling)
8. [Conversion of BGRx Video Frames to Grayscale](#conversion-of-bgrx-video-frames-to-grayscale)
9. [Testing the new element](#testing-the-new-element)
10. [Properties](#properties)
@ -53,7 +53,7 @@ path = "src/lib.rs"
gst-plugin-version-helper = { git = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs" }
```
We depend on the `gstreamer`, `gstreamer-base` and `gstreamer-video` crates for various GStreamer APIs that will be used later, and the `glib` crate to be able to use some GLib API that well need. GStreamer is building upon GLib, and this leaks through in various places. We also have one build dependency on the `gst-plugin-version-helper` crate, which helps to get some information about the plugin for the `gst::plugin_define!` macro automatically, and the `once_cell` crate, which allows to declare lazily initialized global variables.
We depend on the `gstreamer`, `gstreamer-base` and `gstreamer-video` crates for various GStreamer APIs that will be used later. GStreamer is building upon GLib, and this leaks through in various places. We also have one build dependency on the `gst-plugin-version-helper` crate, which helps to get some information about the plugin for the `gst::plugin_define!` macro automatically.
With the basic project structure being set-up, we should be able to compile the project with `cargo build` now, which will download and build all dependencies and then creates a file called `target/debug/libgstrstutorial.so` (or .dll on Windows, .dylib on macOS). This is going to be our GStreamer plugin.
@ -145,9 +145,9 @@ With that our `src/lib.rs` is complete, and all following code is only in `src/r
```rust
use gst::glib;
use gst_base::subclass::prelude::*;
use gst::prelude::*;
use gst_video::subclass::prelude::*;
use std::i32;
use std::sync::Mutex;
use gst::glib::once_cell::sync::Lazy;
@ -169,13 +169,23 @@ impl Rgb2Gray {}
impl ObjectSubclass for Rgb2Gray {
const NAME: &'static str = "GstRsRgb2Gray";
type Type = super::Rgb2Gray;
type ParentType = gst_base::BaseTransform;
type ParentType = gst_video::VideoFilter;
}
```
This defines a struct `Rgb2Gray` which is empty for now and an empty implementation of the struct which will later be used. The `ObjectSubclass` trait is implemented on the struct `Rgb2Gray` for providing static information about the type to the type system. By implementing `ObjectSubclass` we allow registering our struct with the GObject object system.
`ObjectSubclass` has an associated constant which contains the name of the type, some associated types, and functions for initializing/returning a new instance of our element (`new`) and for initializing the class metadata (`class_init`, more on that later). We simply let those functions proxy to associated functions on the `Rgb2Gray` struct that were going to define at a later time.
`ObjectSubclass` has an associated constant which contains the name of the type and some associated types.
Additionally we need to implement various traits on the Rgb2Gray struct, which will later be used to override virtual methods of the various parent classes of our element. For now we can keep the trait implementations empty. There is one trait implementation required per parent class.
```rust
impl ObjectImpl for Rgb2Gray {}
impl GstObjectImpl for Rgb2Gray {}
impl ElementImpl for Rgb2Gray {}
impl BaseTransformImpl for Rgb2Gray {}
impl VideoFilterImpl for Rgb2Gray {}
```
We also add the following code is in `src/rgb2gray/mod.rs`:
@ -209,7 +219,7 @@ In addition, we also define a `register` function (the one that is already calle
## Type Class & Instance Initialization
As a next step we implement the `new` function and `class_init` functions. In the first version, this struct is empty for now but we will later use it to store all state of our element.
As the next step we implement the `metadata` function and configure the `BaseTransform` class.
```rust
use gst::glib;
@ -222,36 +232,38 @@ impl Rgb2Gray {}
#[glib::object_subclass]
impl ObjectSubclass for Rgb2Gray {
[...]
}
fn class_init(klass: &mut Self::Class) {
klass.set_metadata(
"RGB-GRAY Converter",
"Filter/Effect/Converter/Video",
"Converts RGB to GRAY or grayscale RGB",
"Sebastian Dröge <sebastian@centricular.com>",
);
impl ObjectImpl for Rgb2Gray {}
klass.configure(
gst_base::subclass::BaseTransformMode::NeverInPlace,
false,
false,
);
impl GstObjectImpl for Rgb2Gray {}
impl ElementImpl for Rgb2Gray {
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"RGB-GRAY Converter",
"Filter/Effect/Converter/Video",
"Converts RGB to GRAY or grayscale RGB",
"Sebastian Dröge <sebastian@centricular.com>",
)
});
Some(&*ELEMENT_METADATA)
}
}
impl BaseTransformImpl for Rgb2Gray {
const MODE: gst_base::subclass::BaseTransformMode =
gst_base::subclass::BaseTransformMode::NeverInPlace;
const PASSTHROUGH_ON_SAME_CAPS: bool = false;
const TRANSFORM_IP_ON_PASSTHROUGH: bool = false;
}
```
In the `metadata` function we set up `ElementMetadata` structure for our new element. In this case it contains a description, a classification of our element, a longer description and the author. The metadata can later be retrieved and made use of via the [`Registry`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/struct.Registry.html) and [`PluginFeature`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/struct.PluginFeature.html)/[`ElementFactory`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/struct.ElementFactory.html) API. We employ the `once_cell::sync::Lazy` type from the `once_cell` crate here. This type allows to declare lazily initialized global variables, i.e. on the very first use the provided code will be executed and the result will be stored for all later uses.
In the `new` function we return our empty struct.
In the `class_init` function we, again, set up some metadata for our new element. In this case these are a description, a classification of our element, a longer description and the author. The metadata can later be retrieved and made use of via the [`Registry`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/struct.Registry.html) and [`PluginFeature`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/struct.PluginFeature.html)/[`ElementFactory`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/struct.ElementFactory.html) API. We also configure the `BaseTransform` class and define that we will never operate in-place (producing our output in the input buffer), and that we dont want to work in passthrough mode if the input/output formats are the same.
Additionally we need to implement various traits on the Rgb2Gray struct, which will later be used to override virtual methods of the various parent classes of our element. For now we can keep the trait implementations empty. There is one trait implementation required per parent class.
```rust
impl ObjectImpl for Rgb2Gray {}
impl ElementImpl for Rgb2Gray {}
impl BaseTransformImpl for Rgb2Gray {}
```
We also implement the required `BaseTransformImpl` trait items, which define that we will never operate in-place (producing our output in the input buffer), and that we dont want to work in passthrough mode if the input/output formats are the same.
With all this defined, `gst-inspect-1.0` should be able to show some more information about our element already but will still complain that its not complete yet.
@ -269,14 +281,23 @@ impl Rgb2Gray {}
impl ObjectSubclass for Rgb2Gray {
const NAME: &'static str = "GstRsRgb2Gray";
type Type = super::Rgb2Gray;
type ParentType = gst_base::BaseTransform;
type ParentType = gst_base::VideoFilter;
}
impl ObjectImpl for Rgb2Gray {}
impl GstObjectImpl for Rgb2Gray {}
impl ElementImpl for Rgb2Gray {}
impl BaseTransformImpl for Rgb2Gray {}
impl BaseTransformImpl for Rgb2Gray {
const MODE: gst_base::subclass::BaseTransformMode =
gst_base::subclass::BaseTransformMode::NeverInPlace;
const PASSTHROUGH_ON_SAME_CAPS: bool = false;
const TRANSFORM_IP_ON_PASSTHROUGH: bool = false;
}
impl VideoFilterImpl for Rgb2Gray {}
```
```rust
@ -303,10 +324,7 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
## Debug Category
To be able to later have a debug category available for our new element that
can be used for logging, we make use of the `once_cell::sync::Lazy` type from the
`once_cell` crate. This type allows to declare lazily initialized
global variables, i.e. on the very first use the provided code will be
executed and the result will be stored for all later uses.
can be used for logging, we again make use of the `once_cell::sync::Lazy` type.
```rust
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
@ -325,131 +343,55 @@ the convention with GStreamer elements.
Data flow of GStreamer elements is happening via pads, which are the input(s) and output(s) (or sinks and sources in GStreamer terminology) of an element. Via the pads, buffers containing actual media data, events or queries are transferred. An element can have any number of sink and source pads, but our new element will only have one of each.
To be able to declare what kinds of pads an element can create (they are not necessarily all static but could be created at runtime by the element or the application), it is necessary to install so-called pad templates during the class initialization (in the `class_init` function). These pad templates contain the name (or rather “name template”, it could be something like `src_%u` for e.g. pad templates that declare multiple possible pads), the direction of the pad (sink or source), the availability of the pad (is it always there, sometimes added/removed by the element or to be requested by the application) and all the possible media types (called caps) that the pad can consume (sink pads) or produce (src pads).
To be able to declare what kinds of pads an element can create (they are not necessarily all static but could be created at runtime by the element or the application), it is necessary to install so-called pad templates during the class initialization. These pad templates contain the name (or rather “name template”, it could be something like `src_%u` for e.g. pad templates that declare multiple possible pads), the direction of the pad (sink or source), the availability of the pad (is it always there, sometimes added/removed by the element or to be requested by the application) and all the possible media types (called caps) that the pad can consume (sink pads) or produce (src pads).
In our case we only have always pads, one sink pad called “sink”, on which we can only accept RGB (BGRx to be exact) data with any width/height/framerate and one source pad called “src”, on which we will produce either RGB (BGRx) data or GRAY8 (8-bit grayscale) data. We do this by adding the following code to the class_init function.
In our case we only have always pads, one sink pad called “sink”, on which we can only accept RGB (BGRx to be exact) data with any width/height/framerate and one source pad called “src”, on which we will produce either RGB (BGRx) data or GRAY8 (8-bit grayscale) data. We do this by implementing `pad_templates` method from `ElementImpl` trait.
```rust
let caps = gst_video::VideoCapsBuilder::new()
.format_list([gst_video::VideoFormat::Bgrx.to_str(),
gst_video::VideoFormat::Gray8.to_str(),
])
.build();
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps,
)
.unwrap();
klass.add_pad_template(src_pad_template);
impl ElementImpl for Rgb2Gray {
[...]
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
let caps = gst_video::VideoCapsBuilder::new()
.format_list([gst_video::VideoFormat::Bgrx, gst_video::VideoFormat::Gray8])
.build();
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps,
)
.unwrap();
let caps = gst_video::VideoCapsBuilder::new()
.format(gst_video::VideoFormat::Bgrx)
.build();
let sink_pad_template = gst::PadTemplate::new(
"sink",
gst::PadDirection::Sink,
gst::PadPresence::Always,
&caps,
)
.unwrap();
klass.add_pad_template(sink_pad_template);
let caps = gst_video::VideoCapsBuilder::new()
.format(gst_video::VideoFormat::Bgrx)
.build();
let sink_pad_template = gst::PadTemplate::new(
"sink",
gst::PadDirection::Sink,
gst::PadPresence::Always,
&caps,
)
.unwrap();
vec![src_pad_template, sink_pad_template]
});
PAD_TEMPLATES.as_ref()
}
}
```
The names “src” and “sink” are pre-defined by the `BaseTransform` class and this base-class will also create the actual pads with those names from the templates for us whenever a new element instance is created. Otherwise we would have to do that in our `new` function but here this is not needed.
If you now run gst-inspect-1.0 on the rsrgb2gray element, these pad templates with their caps should also show up.
## Caps Handling Part 1
## Caps Handling
As a next step we will add caps handling to our new element. This involves overriding 4 virtual methods from the BaseTransformImpl trait, and actually storing the configured input and output caps inside our element struct. Lets start with the latter
As the next step we will add caps handling to our new element. It is required that we implement a function that is converting caps into the corresponding caps in the other direction. That is, we should convert BGRx to BGRx or GRAY8. Similarly, if the element downstream of ours can accept GRAY8 with a specific width/height from our source pad, we have to convert this to BGRx with that very same width/height. For example, if we receive BGRx caps with some width/height on the sinkpad, we should convert this into new caps with the same width/height but BGRx or GRAY8.
```rust
struct State {
in_info: gst_video::VideoInfo,
out_info: gst_video::VideoInfo,
}
#[derive(Default)]
pub struct Rgb2Gray {
state: Mutex<Option<State>>
}
impl Rgb2Gray{}
#[glib::object_subclass]
impl ObjectSubclass for Rgb2Gray {
[...]
}
```
We define a new struct State that contains the input and output caps, stored in a [`VideoInfo`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer_video/struct.VideoInfo.html). `VideoInfo` is a struct that contains various fields like width/height, framerate and the video format and allows to conveniently with the properties of (raw) video formats. We have to store it inside a [`Mutex`](https://doc.rust-lang.org/std/sync/struct.Mutex.html) in our `Rgb2Gray` struct as this can (in theory) be accessed from multiple threads at the same time.
Whenever input/output caps are configured on our element, the `set_caps` virtual method of `BaseTransform` is called with both caps (i.e. in the very beginning before the data flow and whenever it changes), and all following video frames that pass through our element should be according to those caps. Once the element is shut down, the `stop` virtual method is called and it would make sense to release the `State` as it only contains stream-specific information. Were doing this by adding the following to the `BaseTransformImpl` trait implementation
```rust
impl BaseTransformImpl for Rgb2Gray {
fn set_caps(
&self,
incaps: &gst::Caps,
outcaps: &gst::Caps,
) -> Result<(), gst::LoggableError> {
let in_info = match gst_video::VideoInfo::from_caps(incaps) {
None => return Err(gst::loggable_error!(CAT, "Failed to parse input caps")),
Some(info) => info,
};
let out_info = match gst_video::VideoInfo::from_caps(outcaps) {
None => return Err(gst::loggable_error!(CAT, "Failed to parse output caps")),
Some(info) => info,
};
gst::debug!(
CAT,
imp: self,
"Configured for caps {} to {}",
incaps,
outcaps
);
*self.state.lock().unwrap() = Some(State { in_info, out_info });
Ok(())
}
fn stop(&self) -> Result<(), gst::ErrorMessage> {
// Drop state
let _ = self.state.lock().unwrap().take();
gst::info!(CAT, imp: self, "Stopped");
Ok(())
}
}
```
This code should be relatively self-explanatory. In `set_caps` were parsing the two caps into a `VideoInfo` and then store this in our `State`, in `stop` we drop the `State` and replace it with `None`. In addition we make use of our debug category here and use the `gst::info!` and `gst::debug!` macros to output the current caps configuration to the GStreamer debug logging system. This information can later be useful for debugging any problems once the element is running.
Next we have to provide information to the `BaseTransform` base class about the size in bytes of a video frame with specific caps. This is needed so that the base class can allocate an appropriately sized output buffer for us, that we can then fill later. This is done with the `get_unit_size` virtual method, which is required to return the size of one processing unit in specific caps. In our case, one processing unit is one video frame. In the case of raw audio it would be the size of one sample multiplied by the number of channels.
```rust
impl BaseTransformImpl for Rgb2Gray {
fn get_unit_size(&self, caps: &gst::Caps) -> Option<usize> {
gst_video::VideoInfo::from_caps(caps).map(|info| info.size())
}
}
```
We simply make use of the `VideoInfo` API here again, which conveniently gives us the size of one video frame already.
Instead of `get_unit_size` it would also be possible to implement the `transform_size` virtual method, which is getting passed one size and the corresponding caps, another caps and is supposed to return the size converted to the second caps. Depending on how your element works, one or the other can be easier to implement.
## Caps Handling Part 2
Were not done yet with caps handling though. As a very last step it is required that we implement a function that is converting caps into the corresponding caps in the other direction. That is, we should convert BGRx to BGRx or GRAY8. Similarly, if the element downstream of ours can accept GRAY8 with a specific width/height from our source pad, we have to convert this to BGRx with that very same width/height. For example, if we receive BGRx caps with some width/height on the sinkpad, we should convert this into new caps with the same width/height but BGRx or GRAY8.
This has to be implemented in the `transform_caps` virtual method, and looks as follows
This has to be implemented in the `transform_caps` virtual method from the `BaseTransformImpl` trait, and looks as follows
```rust
impl BaseTransformImpl for Rgb2Gray {
@ -538,43 +480,15 @@ This function works by extracting the blue, green and red components from each p
Note: This is only doing the actual conversion from linear RGB to grayscale (and in [BT.601](https://en.wikipedia.org/wiki/YUV#SDTV_with_BT.601) colorspace). To do this conversion correctly you need to know your colorspaces and use the correct coefficients for conversion, and also do [gamma correction](https://en.wikipedia.org/wiki/Gamma_correction). See [this](https://web.archive.org/web/20161024090830/http://www.4p8.com/eric.brasseur/gamma.html) about why it is important.
Afterwards we have to actually call this function on every pixel. For this the transform virtual method is implemented, which gets a input and output buffer passed and were supposed to read the input buffer and fill the output buffer. The implementation looks as follows, and is going to be our biggest function for this element
Afterwards we have to actually call this function on every pixel. For this the `transform_frame` virtual method from the `VideoFilterImpl` trait is implemented, which gets a input and output video frame passed and were supposed to read the input frame and fill the output frame. The implementation looks as follows, and is going to be our biggest function for this element
```rust
impl BaseTransformImpl for Rgb2Gray {
fn transform(
impl VideoFilterImpl for Rgb2Gray {
fn transform_frame(
&self,
inbuf: &gst::Buffer,
outbuf: &mut gst::BufferRef,
) -> Result<gst::FlowSuccess, gst::FlowError> {
let mut state_guard = self.state.lock().unwrap();
let state = state_guard.as_mut().ok_or_else(|| {
gst::element_imp_error!(self, gst::CoreError::Negotiation, ["Have no state yet"]);
gst::FlowError::NotNegotiated
})?;
let in_frame =
gst_video::VideoFrameRef::from_buffer_ref_readable(inbuf.as_ref(), &state.in_info)
.ok_or_else(|| {
gst::element_imp_error!(
self,
gst::CoreError::Failed,
["Failed to map input buffer readable"]
);
gst::FlowError::Error
})?;
let mut out_frame =
gst_video::VideoFrameRef::from_buffer_ref_writable(outbuf, &state.out_info)
.ok_or_else(|| {
gst::element_imp_error!(
self,
gst::CoreError::Failed,
["Failed to map output buffer writable"]
);
gst::FlowError::Error
})?;
in_frame: &gst_video::VideoFrameRef<&gst::BufferRef>,
out_frame: &mut gst_video::VideoFrameRef<&mut gst::BufferRef>,
) -> Result <gst::FlowSuccess, gst::FlowError> {
let width = in_frame.width() as usize;
let in_stride = in_frame.plane_stride()[0] as usize;
let in_data = in_frame.plane_data(0).unwrap();
@ -640,11 +554,7 @@ impl BaseTransformImpl for Rgb2Gray {
}
```
What happens here is that we first of all lock our state (the input/output `VideoInfo`) and error out if we dont have any yet (which cant really happen unless other elements have a bug, but better safe than sorry). After that we map the input buffer readable and the output buffer writable with the [`VideoFrameRef`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer_video/video_frame/struct.VideoFrameRef.html) API. By mapping the buffers we get access to the underlying bytes of them, and the mapping operation could for example make GPU memory available or just do nothing and give us access to a normally allocated memory area. We have access to the bytes of the buffer until the `VideoFrameRef` goes out of scope.
Instead of `VideoFrameRef` we couldve also used the [`gst::Buffer::map_readable()`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/buffer/struct.BufferRef.html#method.map_readable) and [`gst::Buffer::map_writable()`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/buffer/struct.BufferRef.html#method.map_writable) API, but different to those the `VideoFrameRef` API also extracts various metadata from the raw video buffers and makes them available. For example we can directly access the different planes as slices without having to calculate the offsets ourselves, or we get directly access to the width and height of the video frame.
After mapping the buffers, we store various information were going to need later in local variables to save some typing later. This is the width (same for input and output as we never changed the width in `transform_caps`), the input and out (row-) stride (the number of bytes per row/line, which possibly includes some padding at the end of each line for alignment reasons), the output format (which can be BGRx or GRAY8 because of how we implemented `transform_caps`) and the pointers to the first plane of the input and output (which in this case also is the only plane, BGRx and GRAY8 both have only a single plane containing all the RGB/gray components).
First we store various information were going to need later in local variables to save some typing later. This is the width (same for input and output as we never changed the width in `transform_caps`), the input and out (row-) stride (the number of bytes per row/line, which possibly includes some padding at the end of each line for alignment reasons), the output format (which can be BGRx or GRAY8 because of how we implemented `transform_caps`) and the pointers to the first plane of the input and output (which in this case also is the only plane, BGRx and GRAY8 both have only a single plane containing all the RGB/gray components).
Then based on whether the output suppose to be BGRx or GRAY8, we iterate over all pixels. The code is basically the same in both cases, so we're only going to go through the case where BGRx is output.
@ -736,10 +646,10 @@ impl ObjectImpl for Rgb2Gray {
[...]
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.get_name() {
match pspec.name() {
"invert" => {
let mut settings = self.settings.lock().unwrap();
let invert = value.get_some().expect("type checked upstream");
let invert = value.get().expect("type checked upstream");
gst::info!(
CAT,
imp: self,
@ -751,7 +661,7 @@ impl ObjectImpl for Rgb2Gray {
}
"shift" => {
let mut settings = self.settings.lock().unwrap();
let shift = value.get_some().expect("type checked upstream");
let shift = value.get().expect("type checked upstream");
gst::info!(
CAT,
imp: self,
@ -765,8 +675,8 @@ impl ObjectImpl for Rgb2Gray {
}
}
fn get_property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.get_name() {
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"invert" => {
let settings = self.settings.lock().unwrap();
settings.invert.to_value()

View file

@ -1,6 +1,6 @@
# How to write GStreamer Elements in Rust Part 2: A raw audio sine wave source
In this part, a raw audio sine wave source element is going to be written. The final code can be found [here](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/blob/main/gst-plugin-tutorial/src/sinesrc.rs).
In this part, a raw audio sine wave source element is going to be written. The final code can be found [here](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/tree/main/tutorial/src/sinesrc).
### Table of Contents
@ -15,9 +15,9 @@ In this part, a raw audio sine wave source element is going to be written. The f
The first part here will be all the boilerplate required to set up the element. You can safely [skip](#caps-negotiation) this if you remember all this from the [previous tutorial](tutorial-1.md).
Our sine wave element is going to produce raw audio, with a number of channels and any possible sample rate with both 32 bit and 64 bit floating point samples. It will produce a simple sine wave with a configurable frequency, volume/mute and number of samples per audio buffer. In addition it will be possible to configure the element in (pseudo) live mode, meaning that it will only produce data in real-time according to the pipeline clock. And it will be possible to seek to any time/sample position on our source element. It will basically be a more simply version of the [`audiotestsrc`](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-audiotestsrc.html) element from gst-plugins-base.
Our sine wave element is going to produce raw audio, with a number of channels and any possible sample rate with both 32 bit and 64 bit floating point samples. It will produce a simple sine wave with a configurable frequency, volume/mute and number of samples per audio buffer. In addition it will be possible to configure the element in (pseudo) live mode, meaning that it will only produce data in real-time according to the pipeline clock. And it will be possible to seek to any time/sample position on our source element. It will basically be a more simple version of the [`audiotestsrc`](https://gstreamer.freedesktop.org/documentation/audiotestsrc/index.html) element from gst-plugins-base.
So let's get started with all the boilerplate. This time our element will be based on the [`PushSrc`](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstPushSrc.html) base class instead of [`BaseTransform`](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstBaseTransform.html). `PushSrc` is a subclass of the [`BaseSrc`](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstBaseSrc.html) base class that only works in push mode, i.e. creates buffers as they arrive instead of allowing downstream elements to explicitly pull them.
So let's get started with all the boilerplate. This time our element will be based on the [`PushSrc`](https://gstreamer.freedesktop.org/documentation/base/gstpushsrc.html#GstPushSrc) base class instead of [`VideoFilter`](https://gstreamer.freedesktop.org/documentation/video/gstvideofilter.html#GstVideoFilter). `PushSrc` is a subclass of the [`BaseSrc`](https://gstreamer.freedesktop.org/documentation/base/gstbasesrc.html#GstBaseSrc) base class that only works in push mode, i.e. creates buffers as they arrive instead of allowing downstream elements to explicitly pull them.
In `src/sinesrc/imp.rs`:
@ -26,6 +26,7 @@ use gst::glib;
use gst::prelude::*;
use gst::subclass::prelude::*;
use gst_base::prelude::*;
use gst_base::subclass::base_src::CreateSuccess;
use gst_base::subclass::prelude::*;
use byte_slice_cast::*;
@ -108,53 +109,6 @@ impl ObjectSubclass for SineSrc {
const NAME: &'static str = "GstRsSineSrc";
type Type = super::SineSrc;
type ParentType = gst_base::PushSrc;
// Called exactly once when registering the type. Used for
// setting up metadata for all instances, e.g. the name and
// classification and the pad templates with their caps.
//
// Actual instances can create pads based on those pad templates
// with a subset of the caps given here. In case of basesrc,
// a "src" and "sink" pad template are required here and the base class
// will automatically instantiate pads for them.
//
// Our element here can output f32 and f64
fn class_init(klass: &mut Self::Class) {
// Set 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.
klass.set_metadata(
"Sine Wave Source",
"Source/Audio",
"Creates a sine wave",
"Sebastian Dröge <sebastian@centricular.com>",
);
// Create and add 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.
// On the src pad, we can produce F32/F64 with any sample rate
// and any number of channels (default)
let caps = gst_audio::AudioCapsBuilder::new_interleaved()
.format_list([
gst_audio::AUDIO_FORMAT_F32,
gst_audio::AUDIO_FORMAT_F64,
])
.build();
// The src pad template must be named "src" for basesrc
// and specific a pad that is always there
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps,
)
.unwrap();
klass.add_pad_template(src_pad_template);
}
}
impl ObjectImpl for SineSrc {
@ -208,7 +162,7 @@ impl ObjectImpl for SineSrc {
// Initialize live-ness and notify the base class that
// we'd like to operate in Time format
let obj = self.instance();
let obj = self.obj();
obj.set_live(DEFAULT_IS_LIVE);
obj.set_format(gst::Format::Time);
}
@ -221,10 +175,10 @@ impl ObjectImpl for SineSrc {
value: &glib::Value,
pspec: &glib::ParamSpec,
) {
match pspec.get_name() {
match pspec.name() {
"samples-per-buffer" => {
let mut settings = self.settings.lock().unwrap();
let samples_per_buffer = value.get_some().expect("type checked upstream");
let samples_per_buffer = value.get().expect("type checked upstream");
gst::info!(
CAT,
imp: self,
@ -235,11 +189,11 @@ impl ObjectImpl for SineSrc {
settings.samples_per_buffer = samples_per_buffer;
drop(settings);
let _ = self.instance().post_message(gst::message::Latency::builder().src(&*self.instance()).build());
let _ = self.obj().post_message(gst::message::Latency::builder().src(&*self.obj()).build());
}
"freq" => {
let mut settings = self.settings.lock().unwrap();
let freq = value.get_some().expect("type checked upstream");
let freq = value.get().expect("type checked upstream");
gst::info!(
CAT,
imp: self,
@ -251,7 +205,7 @@ impl ObjectImpl for SineSrc {
}
"volume" => {
let mut settings = self.settings.lock().unwrap();
let volume = value.get_some().expect("type checked upstream");
let volume = value.get().expect("type checked upstream");
gst::info!(
CAT,
imp: self,
@ -263,7 +217,7 @@ impl ObjectImpl for SineSrc {
}
"mute" => {
let mut settings = self.settings.lock().unwrap();
let mute = value.get_some().expect("type checked upstream");
let mute = value.get().expect("type checked upstream");
gst::info!(
CAT,
imp: self,
@ -275,7 +229,7 @@ impl ObjectImpl for SineSrc {
}
"is-live" => {
let mut settings = self.settings.lock().unwrap();
let is_live = value.get_some().expect("type checked upstream");
let is_live = value.get().expect("type checked upstream");
gst::info!(
CAT,
imp: self,
@ -292,7 +246,7 @@ impl ObjectImpl for SineSrc {
// Called whenever a value of a property is read. It can be called
// at any time from any thread.
fn get_property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.get_name() {
match pspec.name() {
"samples-per-buffer" => {
let settings = self.settings.lock().unwrap();
settings.samples_per_buffer.to_value()
@ -318,18 +272,62 @@ impl ObjectImpl for SineSrc {
}
}
// Virtual methods of gst::Element. We override none
impl ElementImpl for SineSrc { }
// Virtual methods of gst::Element.
impl ElementImpl for SineSrc {
// Set 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: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
gst::subclass::ElementMetadata::new(
"Sine Wave Source",
"Source/Audio",
"Creates a sine wave",
"Sebastian Dröge <sebastian@centricular.com>",
)
});
Some(&*ELEMENT_METADATA)
}
// Create and add 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.
fn pad_templates() -> &'static [gst::PadTemplate] {
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
// On the src pad, we can produce F32/F64 with any sample rate
// and any number of channels
let caps = gst_audio::AudioCapsBuilder::new_interleaved()
.format_list([gst_audio::AUDIO_FORMAT_F32, gst_audio::AUDIO_FORMAT_F64])
.build();
// The src pad template must be named "src" for basesrc
// and specific a pad that is always there
let src_pad_template = gst::PadTemplate::new(
"src",
gst::PadDirection::Src,
gst::PadPresence::Always,
&caps,
)
.unwrap();
vec![src_pad_template]
});
PAD_TEMPLATES.as_ref()
}
}
impl BaseSrcImpl for SineSrc {
// Called when starting, so we can initialize all stream-related state to its defaults
fn start(&self) -> bool {
fn start(&self) -> Result<(), gst::ErrorMessage> {
// Reset state
*self.state.lock().unwrap() = Default::default();
gst::info!(CAT, imp: self, "Started");
true
Ok(())
}
// Called when shutting down the element so we can release all stream-related state
@ -360,7 +358,7 @@ glib::wrapper! {
// Registers the type for our element, and then registers in GStreamer under
// the name "rssinesrc" for being able to instantiate it via e.g.
// gst::ElementFactory::make().
pub fn register(plugin: &gst::Plugin) {
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
gst::Element::register(
Some(plugin),
"rssinesrc",
@ -378,20 +376,20 @@ With all of the above and a small addition to `src/lib.rs` this should compile
mod sinesrc;
[...]
fn plugin_init(plugin: &gst::Plugin) -> bool {
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
[...]
sinesrc::register(plugin);
true
Ok(())
}
```
Also a couple of new crates have to be added to `Cargo.toml` and `src/lib.rs`, but you best check the code in the [repository](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/tree/main/gst-plugin-tutorial) for details.
Also a couple of new crates have to be added to `Cargo.toml` and `src/lib.rs`, but you best check the code in the [repository](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/tree/main/tutorial) for details.
### Caps Negotiation
The first part that we have to implement, just like last time, is caps negotiation. We already notified the base class about any caps that we can potentially handle via the caps in the pad template in `class_init` but there are still two more steps of behaviour left that we have to implement.
The first part that we have to implement, just like last time, is caps negotiation. We already notified the base class about any caps that we can potentially handle via the caps in the pad template returned from `pad_templates` function, but there are still two more steps of behaviour left that we have to implement.
First of all, we need to get notified whenever the caps that our source is configured for are changing. This will happen once in the very beginning and then whenever the pipeline topology or state changes and new caps would be more optimal for the new situation. This notification happens via the `BaseTransform::set_caps` virtual method.
First of all, we need to get notified whenever the caps that our source is configured for are changing. This will happen once in the very beginning and then whenever the pipeline topology or state changes and new caps would be more optimal for the new situation. This notification happens via the `BaseSrcImpl::set_caps` virtual method.
```rust
fn set_caps(&self, caps: &gst::Caps) -> Result<(), gst::LoggableError> {
@ -403,7 +401,7 @@ First of all, we need to get notified whenever the caps that our source is confi
gst::debug!(CAT, imp: self, "Configuring for caps {}", caps);
self.instance().set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer);
self.obj().set_blocksize(info.bpf() * (*self.settings.lock().unwrap()).samples_per_buffer);
let settings = *self.settings.lock().unwrap();
let mut state = self.state.lock().unwrap();
@ -412,7 +410,7 @@ First of all, we need to get notified whenever the caps that our source is confi
// in nanoseconds
let old_rate = match state.info {
Some(ref info) => info.rate() as u64,
None => gst::ClockTime::SECOND,
None => *gst::ClockTime::SECOND,
};
// Update sample offset and accumulator based on the previous values and the
@ -438,15 +436,15 @@ First of all, we need to get notified whenever the caps that our source is confi
drop(state);
let _ = self.instance().post_message(&gst::Message::new_latency().src(Some(&*self.instance())).build());
let _ = self.obj().post_message(gst::message::Latency::builder().src(&*self.obj()).build());
Ok(())
}
```
In here we parse the caps into a [`AudioInfo`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer_audio/struct.AudioInfo.html) and then store that in our internal state, while updating various fields. We tell the base class about the number of bytes each buffer is usually going to hold, and update our current sample position, the stop sample position (when a seek with stop position happens, we need to know when to stop) and our accumulator. This happens by scaling both positions by the old and new sample rate. If we don't have an old sample rate, we assume nanoseconds (this will make more sense once seeking is implemented). The scaling is done with the help of the [`muldiv`](https://crates.io/crates/muldiv) crate, which implements scaling of integer types by a fraction with protection against overflows by doing up to 128 bit integer arithmetic for intermediate values.
In here we parse the caps into a [`AudioInfo`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer_audio/struct.AudioInfo.html) and then store that in our internal state, while updating various fields. We tell the base class about the number of bytes each buffer is usually going to hold, and update our current sample position, the stop sample position (when a seek with stop position happens, we need to know when to stop) and our accumulator. This happens by scaling both positions by the old and new sample rate. If we don't have an old sample rate, we assume nanoseconds (this will make more sense once seeking is implemented). The scaling is done with the help of the [`muldiv`](https://crates.io/crates/muldiv) crate, which implements scaling of integer types by a fraction with protection against overflows by doing up to 128 bit integer arithmetic for intermediate values.
The accumulator is the updated based on the current phase of the sine wave at the current sample position.
The accumulator is then updated based on the current phase of the sine wave at the current sample position.
As a last step we post a new `LATENCY` message on the bus whenever the sample rate has changed. Our latency (in live mode) is going to be the duration of a single buffer, but more about that later.
@ -464,7 +462,7 @@ As a last step we post a new `LATENCY` message on the bus whenever the sample
caps.truncate();
{
let caps = caps.make_mut();
let s = caps.get_mut_structure(0).unwrap();
let s = caps.structure_mut(0).unwrap();
s.fixate_field_nearest_int("rate", 48_000);
s.fixate_field_nearest_int("channels", 1);
}
@ -475,7 +473,7 @@ As a last step we post a new `LATENCY` message on the bus whenever the sample
}
```
Here we take the caps that are passed in, truncate them (i.e. remove all but the very first [`Structure`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/gstreamer/structure/struct.Structure.html)) and then manually fixate the sample rate to the closest value to 48kHz. By default, caps fixation would result in the lowest possible sample rate but this is usually not desired.
Here we take the caps that are passed in, truncate them (i.e. remove all but the very first [`Structure`](https://gstreamer.pages.freedesktop.org/gstreamer-rs/stable/latest/docs/gstreamer/structure/struct.Structure.html)) and then manually fixate the sample rate to the closest value to 48kHz. By default, caps fixation would result in the lowest possible sample rate but this is usually not desired.
For good measure, we also fixate the number of channels to the closest value to 1, but this would already be the default behaviour anyway. And then chain up to the parent class' implementation of `fixate`, which for now basically does the same as `caps.fixate()`. After this, the caps are fixated, i.e. there is only a single `Structure` left and all fields have concrete values (no ranges or sets).
@ -545,7 +543,8 @@ Now that this is done, we need to implement the `PushSrc::create` virtual meth
```rust
fn create(
&self,
) -> Result<gst::Buffer, gst::FlowReturn> {
_buffer: Option<&mut gst::BufferRef>,
) -> Result<CreateSuccess, gst::FlowError> {
// Keep a local copy of the values of all our properties at this very moment. This
// ensures that the mutex is never locked for long and the application wouldn't
// have to block until this function returns when getting/setting property values
@ -556,7 +555,7 @@ Now that this is done, we need to implement the `PushSrc::create` virtual meth
let info = match state.info {
None => {
gst::element_imp_error!(self, gst::CoreError::Negotiation, ["Have no caps yet"]);
return Err(gst::FlowReturn::NotNegotiated);
return Err(gst::FlowError::NotNegotiated);
}
Some(ref info) => info.clone(),
};
@ -566,7 +565,7 @@ Now that this is done, we need to implement the `PushSrc::create` virtual meth
let n_samples = if let Some(sample_stop) = state.sample_stop {
if sample_stop <= state.sample_offset {
gst::log!(CAT, imp: self, "At EOS");
return Err(gst::FlowReturn::Eos);
return Err(gst::FlowError::Eos);
}
sample_stop - state.sample_offset
@ -626,7 +625,7 @@ Now that this is done, we need to implement the `PushSrc::create` virtual meth
gst::debug!(CAT, imp: self, "Produced buffer {:?}", buffer);
Ok(buffer)
Ok(CreateSuccess::NewBuffer(buffer))
}
```
@ -668,14 +667,14 @@ For working in live mode, we have to add a few different parts in various places
// Waiting happens based on the pipeline clock, which means that a real live source
// with its own clock would require various translations between the two clocks.
// This is out of scope for the tutorial though.
if element.is_live() {
let clock = match element.get_clock() {
None => return Ok(buffer),
if self.obj().is_live() {
let clock = match self.obj().clock() {
None => return Ok(CreateSuccess::NewBuffer(buffer)),
Some(clock) => clock,
};
let segment = element.segment().downcast::<gst::format::Time>().unwrap();
let base_time = element.base_time();
let segment = self.obj().segment().downcast::<gst::format::Time>().unwrap();
let base_time = self.obj().base_time();
let running_time = segment.to_running_time(
buffer
.pts()
@ -686,7 +685,7 @@ For working in live mode, we have to add a few different parts in various places
// running time of the last sample
let wait_until = match running_time.opt_add(base_time) {
Some(wait_until) => wait_until,
None => return Ok(buffer),
None => return Ok(CreateSuccess::NewBuffer(buffer)),
};
let id = clock.new_single_shot_id(wait_until);
@ -696,7 +695,7 @@ For working in live mode, we have to add a few different parts in various places
imp: self,
"Waiting until {}, now {}",
wait_until,
clock.get_time().display(),
clock.time().display(),
);
let (res, jitter) = id.wait();
gst::log!(
@ -710,7 +709,7 @@ For working in live mode, we have to add a few different parts in various places
gst::debug!(CAT, imp: self, "Produced buffer {:?}", buffer);
Ok(buffer)
Ok(CreateSuccess::NewBuffer(buffer))
}
```
@ -727,11 +726,11 @@ impl ElementImpl for SineSrc {
fn change_state(
&self,
transition: gst::StateChange,
) -> gst::StateChangeReturn {
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
// Configure live'ness once here just before starting the source
match transition {
gst::StateChange::ReadyToPaused => {
element.set_live(self.settings.lock().unwrap().is_live);
self.obj().set_live(self.settings.lock().unwrap().is_live);
}
_ => (),
}
@ -780,7 +779,7 @@ Inside the latency query we also signal that we are indeed a live source, and ad
You can test this again with e.g. `gst-launch-1.0` by setting the `is-live`property to true. It should write in the output now that the pipeline is live.
`audiotestsrc` element also does it via `get_times` virtual method. But as this is only really useful for pseudo live sources like this one, we decided to explain how waiting on the clock can be achieved correctly and even more important how that relates to the next section.
`audiotestsrc` element also does it via [`get_times`](https://gstreamer.freedesktop.org/documentation/base/gstbasesink.html?gi-language=c#GstBaseSinkClass::get_times) virtual method. But as this is only really useful for pseudo live sources like this one, we decided to explain how waiting on the clock can be achieved correctly and even more important how that relates to the next section.
### Unlocking
@ -812,7 +811,7 @@ struct SineSrc {
[...]
fn unlock(&self) -> bool {
fn unlock(&self) -> Result<(), gst::ErrorMessage> {
// This should unblock the create() function ASAP, so we
// just unschedule the clock it here, if any.
gst::debug!(CAT, imp: self, "Unlocking");
@ -822,7 +821,7 @@ struct SineSrc {
}
clock_wait.flushing = true;
true
Ok(())
}
```
@ -831,14 +830,14 @@ We store the clock ID in our struct, together with a boolean to signal whether w
Once everything is unlocked, we need to reset things again so that data flow can happen in the future. This is done in the `unlock_stop` virtual method.
```rust
fn unlock_stop(&self) -> bool {
fn unlock_stop(&self) -> Result<(), gst::ErrorMessage> {
// This signals that unlocking is done, so we can reset
// all values again.
gst::debug!(CAT, imp: self, "Unlock stop");
let mut clock_wait = self.clock_wait.lock().unwrap();
clock_wait.flushing = false;
true
Ok(())
}
```
@ -853,7 +852,7 @@ Now as a last step, we need to actually make use of the new struct we added arou
let mut clock_wait = self.clock_wait.lock().unwrap();
if clock_wait.flushing {
gst::debug!(CAT, imp: self, "Flushing");
return Err(gst::FlowReturn::Flushing);
return Err(gst::FlowError::Flushing);
}
let id = clock.new_single_shot_id(wait_until);
@ -865,7 +864,7 @@ Now as a last step, we need to actually make use of the new struct we added arou
imp: self,
"Waiting until {}, now {}",
wait_until,
clock.get_time().display(),
clock.time().display(),
);
let (res, jitter) = id.wait();
gst::log!(
@ -879,19 +878,19 @@ Now as a last step, we need to actually make use of the new struct we added arou
// If the clock ID was unscheduled, unlock() was called
// and we should return Flushing immediately.
if res == gst::ClockReturn::Unscheduled {
if res == Err(gst::ClockError::Unscheduled) {
gst::debug!(CAT, imp: self, "Flushing");
return Err(gst::FlowReturn::Flushing);
return Err(gst::FlowError::Flushing);
}
```
The important part in this code is that we first have to check if we are already supposed to unlock, before even starting to wait. Otherwise we would start waiting without anybody ever being able to unlock. Then we need to store the clock id in the struct and make sure to drop the mutex guard so that the `unlock` function can take it again for unscheduling the clock ID. And once waiting is done, we need to remove the clock id from the struct again and in case of `ClockReturn::Unscheduled` we directly return `FlowReturn::Flushing` instead of the error.
The important part in this code is that we first have to check if we are already supposed to unlock, before even starting to wait. Otherwise we would start waiting without anybody ever being able to unlock. Then we need to store the clock id in the struct and make sure to drop the mutex guard so that the `unlock` function can take it again for unscheduling the clock ID. And once waiting is done, we need to remove the clock id from the struct again and in case of `ClockError::Unscheduled` we directly return `Err(gst::FlowError::Flushing)` instead.
Similarly when using other blocking APIs it is important that they are woken up in a similar way when `unlock` is called. Otherwise the application developer's and thus user experience will be far from ideal.
### Seeking
As a last feature we implement seeking on our source element. In our case that only means that we have to update the `sample_offset` and `sample_stop` fields accordingly, other sources might have to do more work than that.
As the last feature we implement seeking on our source element. In our case that only means that we have to update the `sample_offset` and `sample_stop` fields accordingly, other sources might have to do more work than that.
Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signalling whether we can actually seek in the `is_seekable` virtual method.
@ -908,7 +907,7 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
// reverse playback is requested. These values will all be used during buffer creation
// and for calculating the timestamps, etc.
if segment.get_rate() < 0.0 {
if segment.rate() < 0.0 {
gst::error!(CAT, imp: self, "Reverse playback not supported");
return false;
}
@ -928,14 +927,15 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
use std::f64::consts::PI;
let sample_offset = segment
.get_start()
.start()
.unwrap()
.nseconds()
.mul_div_floor(rate, *gst::ClockTime::SECOND)
.unwrap();
let sample_stop = segment
.get_stop()
.map(|v| v.mul_div_floor(rate, *gst::ClockTime::SECOND).unwrap());
.stop()
.map(|v| v.nseconds().mul_div_floor(rate, *gst::ClockTime::SECOND).unwrap());
let accumulator =
(sample_offset as f64).rem(2.0 * PI * (settings.freq as f64) / (rate as f64));
@ -952,9 +952,9 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
*state = State {
info: state.info.clone(),
sample_offset: sample_offset,
sample_stop: sample_stop,
accumulator: accumulator,
sample_offset,
sample_stop,
accumulator,
};
true
@ -988,9 +988,9 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
*state = State {
info: state.info.clone(),
sample_offset: sample_offset,
sample_stop: sample_stop,
accumulator: accumulator,
sample_offset,
sample_stop,
accumulator,
};
true
@ -999,7 +999,7 @@ Seeking is implemented in the `BaseSrc::do_seek` virtual method, and signallin
CAT,
imp: self,
"Can't seek in format {:?}",
segment.get_format()
segment.format()
);
false

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-fallbackswitch"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>", "Jan Schmidt <jan@centricular.com>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,18 +9,18 @@ rust-version = "1.70"
description = "GStreamer Fallback Switcher and Source Plugin"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-plugin-gtk4 = { path = "../../video/gtk4", optional = true }
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", optional = true }
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-plugin-gtk4 = { path = "../../video/gtk4", optional = true, version = "0.11" }
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", branch = "0.7", version = "0.7", optional = true }
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18", optional = true }
parking_lot = "0.12"
[dev-dependencies]
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gstfallbackswitch"
@ -34,7 +34,7 @@ required-features = ["gtk", "gio", "gst-plugin-gtk4"]
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
default = ["v1_20"]

View file

@ -105,9 +105,8 @@ fn create_ui(app: &gtk::Application) {
let video_sink_weak = video_sink.downgrade();
let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
let video_sink = match video_sink_weak.upgrade() {
Some(video_sink) => video_sink,
None => return glib::ControlFlow::Continue,
let Some(video_sink) = video_sink_weak.upgrade() else {
return glib::ControlFlow::Break;
};
let position = video_sink
@ -121,9 +120,8 @@ fn create_ui(app: &gtk::Application) {
let video_src_pad_weak = video_src_pad.downgrade();
let drop_id = RefCell::new(None);
drop_button.connect_toggled(move |drop_button| {
let video_src_pad = match video_src_pad_weak.upgrade() {
Some(video_src_pad) => video_src_pad,
None => return,
let Some(video_src_pad) = video_src_pad_weak.upgrade() else {
return;
};
let drop = drop_button.is_active();
@ -140,9 +138,8 @@ fn create_ui(app: &gtk::Application) {
let app_weak = app.downgrade();
window.connect_close_request(move |_| {
let app = match app_weak.upgrade() {
Some(app) => app,
None => return glib::Propagation::Stop,
let Some(app) = app_weak.upgrade() else {
return glib::Propagation::Stop;
};
app.quit();
@ -155,9 +152,8 @@ fn create_ui(app: &gtk::Application) {
.add_watch_local(move |_, msg| {
use gst::MessageView;
let app = match app_weak.upgrade() {
Some(app) => app,
None => return glib::ControlFlow::Break,
let Some(app) = app_weak.upgrade() else {
return glib::ControlFlow::Break;
};
match msg.view() {

View file

@ -1911,77 +1911,79 @@ impl FallbackSrc {
let imp = element.imp();
match info.data {
Some(gst::PadProbeData::Event(ref ev)) if ev.type_() == gst::EventType::Eos => {
gst::debug!(
CAT,
obj: element,
"Received EOS from {}source on pad {}",
if fallback_source { "fallback " } else { "" },
pad.name()
);
let Some(ev) = info.event() else {
return gst::PadProbeReturn::Ok;
};
let mut state_guard = imp.state.lock();
let state = match &mut *state_guard {
None => {
return gst::PadProbeReturn::Ok;
}
Some(state) => state,
};
if ev.type_() != gst::EventType::Eos {
return gst::PadProbeReturn::Ok;
}
if is_image {
gst::PadProbeReturn::Ok
} else if state.settings.restart_on_eos || fallback_source {
imp.handle_source_error(state, RetryReason::Eos, fallback_source);
drop(state_guard);
element.notify("statistics");
gst::debug!(
CAT,
obj: element,
"Received EOS from {}source on pad {}",
if fallback_source { "fallback " } else { "" },
pad.name()
);
gst::PadProbeReturn::Drop
let mut state_guard = imp.state.lock();
let state = match &mut *state_guard {
None => {
return gst::PadProbeReturn::Ok;
}
Some(state) => state,
};
if is_image {
gst::PadProbeReturn::Ok
} else if state.settings.restart_on_eos || fallback_source {
imp.handle_source_error(state, RetryReason::Eos, fallback_source);
drop(state_guard);
element.notify("statistics");
gst::PadProbeReturn::Drop
} else {
// Send EOS to all sinkpads of the fallbackswitch and also to the other
// stream's fallbackswitch if it doesn't have a main branch.
let mut sinkpads = vec![];
if let Some(stream) = {
if is_video {
state.video_stream.as_ref()
} else {
// Send EOS to all sinkpads of the fallbackswitch and also to the other
// stream's fallbackswitch if it doesn't have a main branch.
let mut sinkpads = vec![];
state.audio_stream.as_ref()
}
} {
sinkpads.extend(stream.switch.sink_pads().into_iter().filter(|p| p != pad));
}
if let Some(stream) = {
if is_video {
state.video_stream.as_ref()
} else {
state.audio_stream.as_ref()
}
} {
sinkpads
.extend(stream.switch.sink_pads().into_iter().filter(|p| p != pad));
}
if let Some(other_stream) = {
if is_video {
state.audio_stream.as_ref()
} else {
state.video_stream.as_ref()
}
} {
if other_stream.main_branch.is_none() {
sinkpads.extend(
other_stream
.switch
.sink_pads()
.into_iter()
.filter(|p| p != pad),
);
}
}
let event = ev.clone();
element.call_async(move |_| {
for sinkpad in sinkpads {
sinkpad.send_event(event.clone());
}
});
gst::PadProbeReturn::Ok
if let Some(other_stream) = {
if is_video {
state.audio_stream.as_ref()
} else {
state.video_stream.as_ref()
}
} {
if other_stream.main_branch.is_none() {
sinkpads.extend(
other_stream
.switch
.sink_pads()
.into_iter()
.filter(|p| p != pad),
);
}
}
_ => gst::PadProbeReturn::Ok,
let event = ev.clone();
element.call_async(move |_| {
for sinkpad in sinkpads {
sinkpad.send_event(event.clone());
}
});
gst::PadProbeReturn::Ok
}
});
@ -2056,13 +2058,15 @@ impl FallbackSrc {
let qos_probe_id = block_pad
.add_probe(gst::PadProbeType::EVENT_UPSTREAM, |_pad, info| {
if let Some(gst::PadProbeData::Event(ref ev)) = info.data {
if let gst::EventView::Qos(_) = ev.view() {
return gst::PadProbeReturn::Drop;
}
let Some(ev) = info.event() else {
return gst::PadProbeReturn::Ok;
};
if ev.type_() != gst::EventType::Qos {
return gst::PadProbeReturn::Ok;
}
gst::PadProbeReturn::Ok
gst::PadProbeReturn::Drop
})
.unwrap();
@ -2871,19 +2875,17 @@ impl FallbackSrc {
// error. We don't need to remove these pad probes because restarting the source will also
// remove/add the pads again.
for pad in source.source.src_pads() {
pad.add_probe(
gst::PadProbeType::EVENT_DOWNSTREAM,
|_pad, info| match info.data {
Some(gst::PadProbeData::Event(ref event)) => {
if event.type_() == gst::EventType::Eos {
gst::PadProbeReturn::Drop
} else {
gst::PadProbeReturn::Ok
}
}
_ => unreachable!(),
},
)
pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, |_pad, info| {
let Some(ev) = info.event() else {
return gst::PadProbeReturn::Pass;
};
if ev.type_() != gst::EventType::Eos {
return gst::PadProbeReturn::Pass;
}
gst::PadProbeReturn::Drop
})
.unwrap();
}
@ -2891,9 +2893,8 @@ impl FallbackSrc {
self.obj().call_async(move |element| {
let imp = element.imp();
let source = match source_weak.upgrade() {
None => return,
Some(source) => source,
let Some(source) = source_weak.upgrade() else {
return;
};
// Remove blocking pad probes if they are still there as otherwise shutting down the
@ -3079,9 +3080,8 @@ impl FallbackSrc {
let element_weak = element.downgrade();
timeout
.wait_async(move |_clock, _time, _id| {
let element = match element_weak.upgrade() {
None => return,
Some(element) => element,
let Some(element) = element_weak.upgrade() else {
return;
};
gst::debug!(CAT, obj: element, "Woke up, retrying");
@ -3206,20 +3206,23 @@ impl FallbackSrc {
} else {
let mut state_guard = imp.state.lock();
let state = state_guard.as_mut().expect("no state");
if fallback_source {
assert!(state
.fallback_source
.as_ref()
.map(|s| s.restart_timeout.is_none())
.unwrap_or(true));
let source = if fallback_source {
if let Some(source) = &state.fallback_source {
source
} else {
return;
}
} else {
assert!(state.source.restart_timeout.is_none());
&state.source
};
if source.restart_timeout.is_none() {
imp.schedule_source_restart_timeout(
state,
gst::ClockTime::ZERO,
fallback_source,
);
}
imp.schedule_source_restart_timeout(
state,
gst::ClockTime::ZERO,
fallback_source,
);
}
});
})
@ -3303,9 +3306,8 @@ impl FallbackSrc {
let element_weak = self.obj().downgrade();
timeout
.wait_async(move |_clock, _time, _id| {
let element = match element_weak.upgrade() {
None => return,
Some(element) => element,
let Some(element) = element_weak.upgrade() else {
return;
};
element.call_async(move |element| {

View file

@ -14,7 +14,7 @@ use gst::{debug, log, trace};
use gst::glib::once_cell::sync::Lazy;
use parking_lot::{Mutex, MutexGuard};
use parking_lot::{Condvar, Mutex, MutexGuard};
use std::sync::atomic::{AtomicU32, Ordering};
const PROP_PRIORITY: &str = "priority";
@ -36,6 +36,14 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
)
});
/* Mutex locking ordering:
- self.settings
- self.state
- self.active_sinkpad
- pad.settings
- pad.state
*/
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum CapsInfo {
@ -79,6 +87,10 @@ struct State {
timeout_running_time: Option<gst::ClockTime>,
timeout_clock_id: Option<gst::ClockId>,
/// If the src pad is currently busy. Should be checked and waited on using `src_busy_cond`
/// before calling anything requiring the stream lock.
src_busy: bool,
}
impl Default for State {
@ -94,6 +106,8 @@ impl Default for State {
timeout_running_time: None,
timeout_clock_id: None,
src_busy: false,
}
}
}
@ -412,6 +426,7 @@ impl SinkState {
#[derive(Debug)]
pub struct FallbackSwitch {
state: Mutex<State>,
src_busy_cond: Condvar,
settings: Mutex<Settings>,
// Separated from the rest of the `state` because it can be
@ -473,8 +488,8 @@ impl FallbackSwitch {
if active_sinkpad.as_ref() == Some(pad) {
continue;
}
let pad_state = pad_imp.state.lock();
let pad_settings = pad_imp.settings.lock().clone();
let pad_state = pad_imp.state.lock();
#[allow(clippy::collapsible_if)]
/* If this pad has data that arrived within the 'timeout' window
* before the timeout fired, we can switch to it */
@ -568,9 +583,8 @@ impl FallbackSwitch {
let imp_weak = self.downgrade();
timeout_id
.wait_async(move |_clock, _time, clock_id| {
let imp = match imp_weak.upgrade() {
None => return,
Some(imp) => imp,
let Some(imp) = imp_weak.upgrade() else {
return;
};
imp.on_timeout(clock_id);
})
@ -638,8 +652,8 @@ impl FallbackSwitch {
buffer: gst::Buffer,
from_gap: Option<&gst::event::Gap>,
) -> Result<gst::FlowSuccess, gst::FlowError> {
let mut state = self.state.lock();
let settings = self.settings.lock().clone();
let mut state = self.state.lock();
let pad = pad.downcast_ref::<super::FallbackSwitchSinkPad>().unwrap();
let pad_imp = pad.imp();
@ -821,11 +835,14 @@ impl FallbackSwitch {
is_active = self.active_sinkpad.lock().as_ref() == Some(pad);
}
let mut pad_state = pad_imp.state.lock();
let pad_state = pad_imp.state.lock();
if pad_state.flushing {
debug!(CAT, imp: self, "Flushing");
return Err(gst::FlowError::Flushing);
}
// calling schedule_timeout() may result in handle_timeout() being called right away,
// which will need pad state locks, so drop it now to prevent deadlocks.
drop(pad_state);
if is_active {
if start_running_time
@ -871,6 +888,8 @@ impl FallbackSwitch {
}
}
let mut pad_state = pad_imp.state.lock();
if let Some(running_time) = end_running_time {
pad_state.current_running_time = Some(running_time);
}
@ -939,10 +958,12 @@ impl FallbackSwitch {
}
if switched_pad {
let _ = pad.push_event(gst::event::Reconfigure::new());
pad.sticky_events_foreach(|event| {
self.src_pad.push_event(event.clone());
std::ops::ControlFlow::Continue(gst::EventForeachAction::Keep)
self.with_src_busy(|| {
let _ = pad.push_event(gst::event::Reconfigure::new());
pad.sticky_events_foreach(|event| {
self.src_pad.push_event(event.clone());
std::ops::ControlFlow::Continue(gst::EventForeachAction::Keep)
});
});
self.obj().notify(PROP_ACTIVE_PAD);
@ -974,11 +995,13 @@ impl FallbackSwitch {
let out_gap_event = builder.build();
self.src_pad.push_event(out_gap_event);
self.with_src_busy(|| {
self.src_pad.push_event(out_gap_event);
});
Ok(gst::FlowSuccess::Ok)
} else {
self.src_pad.push(buffer)
self.with_src_busy(|| self.src_pad.push(buffer))
}
}
@ -1101,15 +1124,18 @@ impl FallbackSwitch {
drop(state);
if fwd_sticky {
let _ = pad.push_event(gst::event::Reconfigure::new());
pad.sticky_events_foreach(|event| {
self.src_pad.push_event(event.clone());
std::ops::ControlFlow::Continue(gst::EventForeachAction::Keep)
self.with_src_busy(|| {
let _ = pad.push_event(gst::event::Reconfigure::new());
pad.sticky_events_foreach(|event| {
self.src_pad.push_event(event.clone());
std::ops::ControlFlow::Continue(gst::EventForeachAction::Keep)
});
});
self.obj().notify(PROP_ACTIVE_PAD);
}
self.src_pad.push_event(event)
self.with_src_busy(|| self.src_pad.push_event(event))
}
fn sink_query(&self, pad: &super::FallbackSwitchSinkPad, query: &mut gst::QueryRef) -> bool {
@ -1176,8 +1202,8 @@ impl FallbackSwitch {
}
}
let mut state = self.state.lock();
let settings = self.settings.lock().clone();
let mut state = self.state.lock();
min_latency = min_latency.max(settings.min_upstream_latency);
state.upstream_latency = min_latency;
log!(CAT, obj: pad, "Upstream latency {}", min_latency);
@ -1224,6 +1250,34 @@ impl FallbackSwitch {
false
}
/// Wait until src_busy is not set and set it, execute
/// the closure, then unset it again and notify its Cond.
///
/// The State lock is taken while modifying src_busy,
/// but not while executing the closure.
fn with_src_busy<F, R>(&self, func: F) -> R
where
F: FnOnce() -> R,
{
{
let mut state = self.state.lock();
while state.src_busy {
self.src_busy_cond.wait(&mut state);
}
state.src_busy = true;
}
let ret = func();
{
let mut state = self.state.lock();
state.src_busy = false;
self.src_busy_cond.notify_one();
}
ret
}
}
#[glib::object_subclass]
@ -1247,6 +1301,7 @@ impl ObjectSubclass for FallbackSwitch {
Self {
state: Mutex::new(State::default()),
src_busy_cond: Condvar::default(),
settings: Mutex::new(Settings::default()),
active_sinkpad: Mutex::new(None),
src_pad: srcpad,

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-livesync"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Jan Alexander Steffens (heftig) <jan.steffens@ltnglobal.com>"]
license = "MPL-2.0"
description = "Livesync Plugin"
@ -9,17 +9,17 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-plugin-gtk4 = { path = "../../video/gtk4", optional = true }
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", optional = true }
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18", optional = true }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-plugin-gtk4 = { path = "../../video/gtk4", optional = true, version = "0.11" }
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", branch = "0.7", version = "0.7", optional = true }
muldiv = "1.0"
num-rational = { version = "0.4", default-features = false, features = [] }
parking_lot = "0.12"
[dev-dependencies]
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gstlivesync"
@ -36,7 +36,7 @@ name = "livesync"
path = "tests/livesync.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -35,6 +35,7 @@ fn create_pipeline() -> gst::Pipeline {
gst::parse_launch(
r#"videotestsrc name=vsrc is-live=1
! video/x-raw,framerate=60/1,width=800,height=600
! identity single-segment=1
! timeoverlay text="Pre:"
! queue
! livesync latency=50000000
@ -44,6 +45,7 @@ fn create_pipeline() -> gst::Pipeline {
! gtk4paintablesink name=vsink
audiotestsrc name=asrc is-live=1
! audio/x-raw,channels=2
! identity single-segment=1
! queue
! livesync latency=50000000
! audiorate

View file

@ -36,6 +36,16 @@ fn audio_info_from_caps(
.transpose()
}
fn duration_from_caps(caps: &gst::CapsRef) -> Option<gst::ClockTime> {
caps.structure(0)
.filter(|s| s.name().starts_with("video/"))
.and_then(|s| s.get::<gst::Fraction>("framerate").ok())
.filter(|framerate| framerate.denom() > 0 && framerate.numer() > 0)
.and_then(|framerate| {
gst::ClockTime::SECOND.mul_div_round(framerate.denom() as u64, framerate.numer() as u64)
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BufferLateness {
OnTime,
@ -82,9 +92,6 @@ struct State {
/// Latency reported by upstream
upstream_latency: Option<gst::ClockTime>,
/// Duration we assume for buffers without one
fallback_duration: gst::ClockTime,
/// Whether we're in PLAYING state
playing: bool,
@ -118,6 +125,12 @@ struct State {
/// Audio format of our srcpad
out_audio_info: Option<gst_audio::AudioInfo>,
/// Duration from caps on our sinkpad
in_duration: Option<gst::ClockTime>,
/// Duration from caps on our srcpad
out_duration: Option<gst::ClockTime>,
/// Queue between sinkpad and srcpad
queue: VecDeque<Item>,
@ -127,6 +140,9 @@ struct State {
/// Current buffer of our srcpad
out_buffer: Option<gst::Buffer>,
/// Whether our last output buffer was a duplicate
out_buffer_duplicate: bool,
/// Running timestamp of our sinkpad
in_timestamp: Option<Timestamps>,
@ -156,7 +172,9 @@ const PROP_OUT: &str = "out";
const PROP_DUPLICATE: &str = "duplicate";
const DEFAULT_LATENCY: gst::ClockTime = gst::ClockTime::ZERO;
const MINIMUM_DURATION: gst::ClockTime = gst::ClockTime::from_mseconds(8);
const DEFAULT_DURATION: gst::ClockTime = gst::ClockTime::from_mseconds(100);
const MAXIMUM_DURATION: gst::ClockTime = gst::ClockTime::from_seconds(10);
const MINIMUM_LATE_THRESHOLD: gst::ClockTime = gst::ClockTime::ZERO;
const DEFAULT_LATE_THRESHOLD: Option<gst::ClockTime> = Some(gst::ClockTime::from_seconds(2));
@ -167,7 +185,6 @@ impl Default for State {
late_threshold: DEFAULT_LATE_THRESHOLD,
single_segment: false,
upstream_latency: None,
fallback_duration: DEFAULT_DURATION,
playing: false,
eos: false,
srcresult: Err(gst::FlowError::Flushing),
@ -177,11 +194,14 @@ impl Default for State {
out_segment: None,
in_caps: None,
pending_caps: None,
in_duration: None,
out_duration: None,
in_audio_info: None,
out_audio_info: None,
queue: VecDeque::with_capacity(32),
buffer_queued: false,
out_buffer: None,
out_buffer_duplicate: false,
in_timestamp: None,
out_timestamp: None,
num_in: 0,
@ -203,8 +223,8 @@ impl ObjectSubclass for LiveSync {
.activatemode_function(|pad, parent, mode, active| {
Self::catch_panic_pad_function(
parent,
|| Err(gst::loggable_error!(CAT, "sink_activate_mode panicked")),
|livesync| livesync.sink_activate_mode(pad, mode, active),
|| Err(gst::loggable_error!(CAT, "sink_activatemode panicked")),
|livesync| livesync.sink_activatemode(pad, mode, active),
)
})
.event_function(|pad, parent, event| {
@ -239,8 +259,8 @@ impl ObjectSubclass for LiveSync {
.activatemode_function(|pad, parent, mode, active| {
Self::catch_panic_pad_function(
parent,
|| Err(gst::loggable_error!(CAT, "src_activate_mode panicked")),
|livesync| livesync.src_activate_mode(pad, mode, active),
|| Err(gst::loggable_error!(CAT, "src_activatemode panicked")),
|livesync| livesync.src_activatemode(pad, mode, active),
)
})
.event_function(|pad, parent, event| {
@ -342,7 +362,6 @@ impl ObjectImpl for LiveSync {
match pspec.name() {
PROP_LATENCY => {
state.latency = value.get().unwrap();
state.update_fallback_duration();
let _ = self.obj().post_message(gst::message::Latency::new());
}
@ -428,9 +447,21 @@ impl ElementImpl for LiveSync {
let success = self.parent_change_state(transition)?;
if transition == gst::StateChange::PlayingToPaused {
let mut state = self.state.lock();
state.playing = false;
match transition {
gst::StateChange::PlayingToPaused => {
let mut state = self.state.lock();
state.playing = false;
}
gst::StateChange::PausedToReady => {
let mut state = self.state.lock();
state.num_in = 0;
state.num_drop = 0;
state.num_out = 0;
state.num_duplicate = 0;
}
_ => {}
}
match (transition, success) {
@ -472,31 +503,13 @@ impl State {
})
}
fn update_fallback_duration(&mut self) {
self.fallback_duration = self
// First, try 1/framerate from the caps
.in_caps
.as_ref()
.and_then(|c| c.structure(0))
.filter(|s| s.name().starts_with("video/"))
.and_then(|s| s.get::<gst::Fraction>("framerate").ok())
.filter(|framerate| framerate.denom() > 0 && framerate.numer() > 0)
.and_then(|framerate| {
gst::ClockTime::SECOND
.mul_div_round(framerate.denom() as u64, framerate.numer() as u64)
})
.filter(|&dur| dur > 8.mseconds() && dur < 10.seconds())
// Otherwise, half the configured latency
.or_else(|| Some(self.latency / 2))
// In any case, don't allow a zero duration
.filter(|&dur| dur > gst::ClockTime::ZERO)
// Safe default
.unwrap_or(DEFAULT_DURATION);
fn pending_events(&self) -> bool {
self.pending_caps.is_some() || self.pending_segment.is_some()
}
}
impl LiveSync {
fn sink_activate_mode(
fn sink_activatemode(
&self,
pad: &gst::Pad,
mode: gst::PadMode,
@ -506,43 +519,18 @@ impl LiveSync {
return Err(gst::loggable_error!(CAT, "Wrong scheduling mode"));
}
if active {
let mut state = self.state.lock();
state.srcresult = Ok(gst::FlowSuccess::Ok);
state.eos = false;
state.in_timestamp = None;
state.num_in = 0;
state.num_drop = 0;
state.in_segment = None;
} else {
{
let mut state = self.state.lock();
state.srcresult = Err(gst::FlowError::Flushing);
if let Some(clock_id) = state.clock_id.take() {
clock_id.unschedule();
}
state.pending_caps = None;
state.out_audio_info = None;
state.out_buffer = None;
self.cond.notify_all();
}
if !active {
self.set_flushing(&mut self.state.lock());
let lock = pad.stream_lock();
{
let mut state = self.state.lock();
state.in_caps = None;
state.in_audio_info = None;
state.queue.clear();
state.buffer_queued = false;
state.update_fallback_duration();
}
self.sink_reset(&mut self.state.lock());
drop(lock);
}
Ok(())
}
fn src_activate_mode(
fn src_activatemode(
&self,
pad: &gst::Pad,
mode: gst::PadMode,
@ -553,37 +541,51 @@ impl LiveSync {
}
if active {
let ret;
{
let mut state = self.state.lock();
state.srcresult = Ok(gst::FlowSuccess::Ok);
state.pending_segment = None;
state.out_segment = None;
state.out_timestamp = None;
state.num_out = 0;
state.num_duplicate = 0;
ret = self.start_src_task().map_err(Into::into);
}
ret
self.start_src_task(&mut self.state.lock())
.map_err(|e| gst::LoggableError::new(*CAT, e))?;
} else {
{
let mut state = self.state.lock();
state.srcresult = Err(gst::FlowError::Flushing);
if let Some(clock_id) = state.clock_id.take() {
clock_id.unschedule();
}
state.pending_caps = None;
state.out_audio_info = None;
state.out_buffer = None;
self.cond.notify_all();
}
let mut state = self.state.lock();
self.set_flushing(&mut state);
self.src_reset(&mut state);
drop(state);
pad.stop_task().map_err(Into::into)
pad.stop_task()?;
}
Ok(())
}
fn set_flushing(&self, state: &mut State) {
state.srcresult = Err(gst::FlowError::Flushing);
if let Some(clock_id) = state.clock_id.take() {
clock_id.unschedule();
}
// Ensure we drop any query response sender to unblock the sinkpad
state.queue.clear();
state.buffer_queued = false;
self.cond.notify_all();
}
fn sink_reset(&self, state: &mut State) {
state.eos = false;
state.in_segment = None;
state.in_caps = None;
state.in_audio_info = None;
state.in_duration = None;
state.in_timestamp = None;
}
fn src_reset(&self, state: &mut State) {
state.pending_segment = None;
state.out_segment = None;
state.pending_caps = None;
state.out_audio_info = None;
state.out_duration = None;
state.out_buffer = None;
state.out_buffer_duplicate = false;
state.out_timestamp = None;
}
fn sink_event(&self, pad: &gst::Pad, mut event: gst::Event) -> bool {
@ -603,16 +605,13 @@ impl LiveSync {
gst::EventView::FlushStart(_) => {
let ret = self.srcpad.push_event(event);
{
let mut state = self.state.lock();
state.srcresult = Err(gst::FlowError::Flushing);
if let Some(clock_id) = state.clock_id.take() {
clock_id.unschedule();
}
self.cond.notify_all();
self.set_flushing(&mut self.state.lock());
if let Err(e) = self.srcpad.pause_task() {
gst::error!(CAT, imp: self, "Failed to pause task: {e}");
return false;
}
let _ = self.srcpad.pause_task();
return ret;
}
@ -620,21 +619,14 @@ impl LiveSync {
let ret = self.srcpad.push_event(event);
let mut state = self.state.lock();
state.srcresult = Ok(gst::FlowSuccess::Ok);
state.eos = false;
state.in_segment = None;
state.pending_segment = None;
state.out_segment = None;
state.in_caps = None;
state.pending_caps = None;
state.in_audio_info = None;
state.out_audio_info = None;
state.queue.clear();
state.buffer_queued = false;
state.out_buffer = None;
state.update_fallback_duration();
self.sink_reset(&mut state);
self.src_reset(&mut state);
if let Err(e) = self.start_src_task(&mut state) {
gst::error!(CAT, imp: self, "Failed to start task: {e}");
return false;
}
let _ = self.start_src_task();
return ret;
}
@ -668,10 +660,12 @@ impl LiveSync {
}
};
let duration = duration_from_caps(&caps);
let mut state = self.state.lock();
state.in_caps = Some(caps);
state.in_audio_info = audio_info;
state.update_fallback_duration();
state.in_duration = duration;
}
gst::EventView::Gap(_) => {
@ -689,12 +683,14 @@ impl LiveSync {
let mut state = self.state.lock();
if is_restart {
if state.srcresult == Err(gst::FlowError::Eos) {
state.srcresult = Ok(gst::FlowSuccess::Ok);
}
state.eos = false;
let _ = self.start_src_task();
if state.srcresult == Err(gst::FlowError::Eos) {
if let Err(e) = self.start_src_task(&mut state) {
gst::error!(CAT, imp: self, "Failed to start task: {e}");
return false;
}
}
}
if state.eos {
@ -739,10 +735,12 @@ impl LiveSync {
{
let mut state = self.state.lock();
if state.srcresult == Err(gst::FlowError::NotLinked) {
state.srcresult = Ok(gst::FlowSuccess::Ok);
let _ = self.start_src_task();
if let Err(e) = self.start_src_task(&mut state) {
gst::error!(CAT, imp: self, "Failed to start task: {e}");
}
}
}
self.sinkpad.push_event(event)
}
@ -766,6 +764,7 @@ impl LiveSync {
self.cond.notify_all();
drop(state);
// If the sender gets dropped, we will also unblock
receiver.recv().unwrap_or(false)
} else {
gst::Pad::query_default(pad, Some(&*self.obj()), query)
@ -850,20 +849,31 @@ impl LiveSync {
}
if let Some(audio_info) = &state.in_audio_info {
let buf_duration = buf_mut.duration().unwrap_or_default();
if let Some(calc_duration) = audio_info
.convert::<Option<gst::ClockTime>>(Some(gst::format::Bytes::from_usize(
buf_mut.size(),
)))
let Some(calc_duration) = audio_info
.convert::<Option<gst::ClockTime>>(gst::format::Bytes::from_usize(buf_mut.size()))
.flatten()
{
else {
gst::error!(
CAT,
imp: self,
"Failed to calculate duration of {:?}",
buf_mut,
);
return Err(gst::FlowError::Error);
};
if let Some(buf_duration) = buf_mut.duration() {
let diff = if buf_duration < calc_duration {
calc_duration - buf_duration
} else {
buf_duration - calc_duration
};
if diff.nseconds() > 1 {
let sample_duration = gst::ClockTime::SECOND
.mul_div_round(1, audio_info.rate().into())
.unwrap();
if diff > sample_duration {
gst::warning!(
CAT,
imp: self,
@ -871,20 +881,26 @@ impl LiveSync {
buf_duration,
calc_duration,
);
buf_mut.set_duration(calc_duration);
}
} else {
gst::debug!(
CAT,
imp: self,
"Failed to calculate duration of {:?}",
buf_mut,
);
gst::debug!(CAT, imp: self, "Patching incoming buffer with duration {calc_duration}");
}
buf_mut.set_duration(calc_duration);
} else if buf_mut.duration().is_none() {
let duration = state.in_duration.map_or(DEFAULT_DURATION, |dur| {
dur.clamp(MINIMUM_DURATION, MAXIMUM_DURATION)
});
gst::debug!(CAT, imp: self, "Patching incoming buffer with duration {duration}");
buf_mut.set_duration(duration);
}
// At this stage we should really really have a segment
let segment = state.in_segment.as_ref().ok_or(gst::FlowError::Error)?;
let segment = state.in_segment.as_ref().ok_or_else(|| {
gst::error!(CAT, imp: self, "Missing segment");
gst::FlowError::Error
})?;
if state.single_segment {
let dts = segment
@ -905,94 +921,43 @@ impl LiveSync {
buf_mut.set_pts(pts.map(|t| t + state.latency));
}
if buf_mut.duration().is_none() {
gst::debug!(CAT, imp: self, "Incoming buffer without duration");
buf_mut.set_duration(Some(state.fallback_duration));
}
if state
.out_buffer
.as_ref()
.map_or(false, |b| b.flags().contains(gst::BufferFlags::GAP))
{
// We are done bridging a gap, so mark it as DISCONT instead
buf_mut.unset_flags(gst::BufferFlags::GAP);
buf_mut.set_flags(gst::BufferFlags::DISCONT);
}
let mut timestamp = state.ts_range(buf_mut, segment);
let timestamp = state.ts_range(buf_mut, segment);
let lateness = self.buffer_is_backwards(&state, timestamp);
match lateness {
BufferLateness::OnTime => {}
BufferLateness::LateUnderThreshold => {
gst::debug!(CAT, imp: self, "Discarding late {:?}", buf_mut);
state.num_drop += 1;
return Ok(gst::FlowSuccess::Ok);
}
BufferLateness::LateOverThreshold => {
gst::debug!(CAT, imp: self, "Accepting late {:?}", buf_mut);
let prev = state.out_buffer.as_ref().unwrap();
let prev_duration = prev.duration().unwrap();
if let Some(audio_info) = &state.in_audio_info {
let mut map_info = buf_mut.map_writable().map_err(|e| {
gst::error!(CAT, imp: self, "Failed to map buffer: {}", e);
gst::FlowError::Error
})?;
audio_info
.format_info()
.fill_silence(map_info.as_mut_slice());
} else {
buf_mut.set_duration(Some(state.fallback_duration));
}
buf_mut.set_dts(prev.dts().map(|t| t + prev_duration));
buf_mut.set_pts(prev.pts().map(|t| t + prev_duration));
buf_mut.set_flags(gst::BufferFlags::GAP);
timestamp = state.ts_range(buf_mut, state.out_segment.as_ref().unwrap());
}
if lateness == BufferLateness::LateUnderThreshold {
gst::debug!(CAT, imp: self, "Discarding late {:?}", buf_mut);
state.num_drop += 1;
return Ok(gst::FlowSuccess::Ok);
}
gst::trace!(CAT, imp: self, "Queueing {:?} ({:?})", buffer, lateness);
state.queue.push_back(Item::Buffer(buffer, lateness));
state.buffer_queued = true;
state.in_timestamp = timestamp;
state.num_in += 1;
self.cond.notify_all();
Ok(gst::FlowSuccess::Ok)
}
fn start_src_task(&self) -> Result<(), glib::BoolError> {
self.srcpad.start_task({
let pad = self.srcpad.downgrade();
move || {
let pad = pad.upgrade().unwrap();
let parent = pad.parent_element().unwrap();
let livesync = parent.downcast_ref::<super::LiveSync>().unwrap();
let ret = livesync.imp().src_loop(&pad);
fn start_src_task(&self, state: &mut State) -> Result<(), glib::BoolError> {
state.srcresult = Ok(gst::FlowSuccess::Ok);
if !ret {
gst::log!(CAT, obj: &parent, "Loop stopping");
let _ = pad.pause_task();
}
}
})
let imp = self.ref_counted();
let ret = self.srcpad.start_task(move || imp.src_loop());
if ret.is_err() {
state.srcresult = Err(gst::FlowError::Error);
}
ret
}
fn src_loop(&self, pad: &gst::Pad) -> bool {
let mut err = match self.src_loop_inner() {
Ok(_) => return true,
Err(e) => e,
fn src_loop(&self) {
let Err(mut err) = self.src_loop_inner() else {
return;
};
let eos;
{
let eos = {
let mut state = self.state.lock();
match state.srcresult {
@ -1002,21 +967,22 @@ impl LiveSync {
// Communicate our flow return
Ok(_) => state.srcresult = Err(err),
}
eos = state.eos;
state.clock_id = None;
self.cond.notify_all();
}
state.eos
};
// Following GstQueue's behavior:
// > let app know about us giving up if upstream is not expected to do so
// > EOS is already taken care of elsewhere
if eos && !matches!(err, gst::FlowError::Flushing | gst::FlowError::Eos) {
self.flow_error(err);
pad.push_event(gst::event::Eos::new());
self.srcpad.push_event(gst::event::Eos::new());
}
false
gst::log!(CAT, imp: self, "Loop stopping");
let _ = self.srcpad.pause_task();
}
fn src_loop_inner(&self) -> Result<gst::FlowSuccess, gst::FlowError> {
@ -1053,8 +1019,8 @@ impl LiveSync {
clock_id.time(),
);
let (res, _) = MutexGuard::unlocked(&mut state, || clock_id.wait());
gst::trace!(CAT, imp: self, "Clock returned {res:?}",);
let (res, jitter) = MutexGuard::unlocked(&mut state, || clock_id.wait());
gst::trace!(CAT, imp: self, "Clock returned {res:?} {jitter}",);
if res == Err(gst::ClockError::Unscheduled) {
return Err(gst::FlowError::Flushing);
@ -1077,6 +1043,7 @@ impl LiveSync {
None
} else {
state.buffer_queued = false;
self.cond.notify_all();
Some((buffer, lateness))
}
}
@ -1087,19 +1054,20 @@ impl LiveSync {
match event.view() {
gst::EventView::Segment(e) => {
let segment = e.segment().downcast_ref().unwrap();
gst::debug!(CAT, imp: self, "pending {segment:?}");
state.pending_segment = Some(segment.clone());
push = false;
}
gst::EventView::Eos(_) => {
state.out_buffer = None;
state.out_buffer_duplicate = false;
state.out_timestamp = None;
state.srcresult = Err(gst::FlowError::Eos);
}
gst::EventView::Caps(e) => {
state.pending_caps = Some(e.caps_owned());
state.update_fallback_duration();
push = false;
}
@ -1128,57 +1096,50 @@ impl LiveSync {
}
};
let duplicate;
let mut caps = None;
let mut segment = None;
if let Some((buffer, lateness)) = in_buffer {
state.out_buffer = Some(buffer);
state.out_timestamp = state.in_timestamp;
caps = state.pending_caps.take();
segment = state.pending_segment.take();
match in_buffer {
Some((mut buffer, BufferLateness::OnTime)) => {
state.num_in += 1;
duplicate = lateness != BufferLateness::OnTime;
self.cond.notify_all();
} else {
// Work around borrow checker
let State {
fallback_duration,
out_buffer: ref mut buffer,
out_audio_info: ref audio_info,
..
} = *state;
gst::debug!(CAT, imp: self, "Repeating {:?}", buffer);
let buffer = buffer.as_mut().unwrap().make_mut();
let prev_duration = buffer.duration().unwrap();
if let Some(audio_info) = audio_info {
if !buffer.flags().contains(gst::BufferFlags::GAP) {
let mut map_info = buffer.map_writable().map_err(|e| {
gst::error!(CAT, imp: self, "Failed to map buffer: {}", e);
gst::FlowError::Error
})?;
audio_info
.format_info()
.fill_silence(map_info.as_mut_slice());
if state.out_buffer.is_none() || state.out_buffer_duplicate {
// We are just starting or done bridging a gap
buffer.make_mut().set_flags(gst::BufferFlags::DISCONT);
}
} else {
buffer.set_duration(Some(fallback_duration));
state.out_buffer = Some(buffer);
state.out_buffer_duplicate = false;
state.out_timestamp = state.in_timestamp;
caps = state.pending_caps.take();
segment = state.pending_segment.take();
}
buffer.set_dts(buffer.dts().map(|t| t + prev_duration));
buffer.set_pts(buffer.pts().map(|t| t + prev_duration));
buffer.set_flags(gst::BufferFlags::GAP);
buffer.unset_flags(gst::BufferFlags::DISCONT);
Some((buffer, BufferLateness::LateOverThreshold)) if !state.pending_events() => {
gst::debug!(CAT, imp: self, "Accepting late {:?}", buffer);
state.num_in += 1;
state.out_timestamp = state.ts_range(
state.out_buffer.as_ref().unwrap(),
state.out_segment.as_ref().unwrap(),
);
duplicate = true;
};
self.patch_output_buffer(&mut state, Some(buffer))?;
}
Some((buffer, BufferLateness::LateOverThreshold)) => {
// Cannot accept late-over-threshold buffers while we have pending events
gst::debug!(CAT, imp: self, "Discarding late {:?}", buffer);
state.num_drop += 1;
self.patch_output_buffer(&mut state, None)?;
}
None => {
self.patch_output_buffer(&mut state, None)?;
}
Some((_, BufferLateness::LateUnderThreshold)) => {
// Is discarded before queueing
unreachable!();
}
}
let buffer = state.out_buffer.clone().unwrap();
let sync_ts = state
@ -1193,10 +1154,16 @@ impl LiveSync {
state.srcresult?;
state.out_audio_info = audio_info_from_caps(&caps).unwrap();
state.out_duration = duration_from_caps(&caps);
}
if let Some(segment) = segment {
if let Some(mut segment) = segment {
if !state.single_segment {
if let Some(stop) = segment.stop() {
gst::debug!(CAT, imp: self, "Removing stop {} from outgoing segment", stop);
segment.set_stop(gst::ClockTime::NONE);
}
gst::debug!(CAT, imp: self, "Forwarding segment: {:?}", segment);
let event = gst::event::Segment::new(&segment);
@ -1221,9 +1188,6 @@ impl LiveSync {
}
state.num_out += 1;
if duplicate {
state.num_duplicate += 1;
}
drop(state);
@ -1283,15 +1247,16 @@ impl LiveSync {
None => return false,
};
let slack = state
.out_buffer
.as_deref()
.map_or(gst::ClockTime::ZERO, |b| b.duration().unwrap());
// When out_timestamp is set, we also have an out_buffer
let slack = state.out_buffer.as_deref().unwrap().duration().unwrap();
if timestamp.start < out_timestamp.end + slack {
return false;
}
// This buffer would start beyond another buffer duration after our
// last emitted buffer ended
gst::debug!(
CAT,
imp: self,
@ -1316,4 +1281,82 @@ impl LiveSync {
details: details
);
}
/// Patches the output buffer for repeating, setting out_buffer, out_buffer_duplicate and
/// out_timestamp
fn patch_output_buffer(
&self,
state: &mut State,
source: Option<gst::Buffer>,
) -> Result<(), gst::FlowError> {
let out_buffer = state.out_buffer.as_mut().unwrap();
let mut duplicate = state.out_buffer_duplicate;
let duration = out_buffer.duration().unwrap();
let dts = out_buffer.dts().map(|t| t + duration);
let pts = out_buffer.pts().map(|t| t + duration);
if let Some(source) = source {
gst::debug!(CAT, imp: self, "Repeating {:?} using {:?}", out_buffer, source);
*out_buffer = source;
duplicate = false;
} else {
gst::debug!(CAT, imp: self, "Repeating {:?}", out_buffer);
}
let buffer = out_buffer.make_mut();
if !duplicate {
let duration_is_valid =
(MINIMUM_DURATION..=MAXIMUM_DURATION).contains(&buffer.duration().unwrap());
if state.out_duration.is_some() || !duration_is_valid {
// Resize the buffer if caps gave us a duration
// or the current duration is unreasonable
let duration = state.out_duration.map_or(DEFAULT_DURATION, |dur| {
dur.clamp(MINIMUM_DURATION, MAXIMUM_DURATION)
});
if let Some(audio_info) = &state.out_audio_info {
let Some(size) = audio_info
.convert::<Option<gst::format::Bytes>>(duration)
.flatten()
.and_then(|bytes| usize::try_from(bytes).ok())
else {
gst::error!(CAT, imp: self, "Failed to calculate size of repeat buffer");
return Err(gst::FlowError::Error);
};
buffer.replace_all_memory(gst::Memory::with_size(size));
}
buffer.set_duration(duration);
gst::debug!(CAT, imp: self, "Patched output buffer duration to {duration}");
}
if let Some(audio_info) = &state.out_audio_info {
let mut map_info = buffer.map_writable().map_err(|e| {
gst::error!(CAT, imp: self, "Failed to map buffer: {}", e);
gst::FlowError::Error
})?;
audio_info
.format_info()
.fill_silence(map_info.as_mut_slice());
}
}
buffer.set_dts(dts);
buffer.set_pts(pts);
buffer.set_flags(gst::BufferFlags::GAP);
buffer.unset_flags(gst::BufferFlags::DISCONT);
state.out_buffer_duplicate = true;
state.out_timestamp = state.ts_range(
state.out_buffer.as_ref().unwrap(),
state.out_segment.as_ref().unwrap(),
);
state.num_duplicate += 1;
Ok(())
}
}

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-togglerecord"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
license = "MPL-2.0"
description = "GStreamer Toggle Record Plugin"
@ -9,17 +9,17 @@ edition = "2021"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-plugin-gtk4 = { path = "../../video/gtk4", optional = true }
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", optional = true }
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", optional = true }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
gst-plugin-gtk4 = { path = "../../video/gtk4", optional = true, version = "0.11" }
gtk = { package = "gtk4", git = "https://github.com/gtk-rs/gtk4-rs", branch = "0.7", version = "0.7", optional = true }
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.18", version = "0.18", optional = true }
parking_lot = "0.12"
[dev-dependencies]
either = "1.0"
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
[lib]
name = "gsttogglerecord"
@ -32,7 +32,7 @@ path = "examples/gtk_recording.rs"
required-features = ["gtk", "gio", "gst-plugin-gtk4"]
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -217,14 +217,12 @@ fn create_ui(app: &gtk::Application) {
let video_sink_weak = video_sink.downgrade();
let togglerecord_weak = togglerecord.downgrade();
let timeout_id = glib::timeout_add_local(std::time::Duration::from_millis(100), move || {
let video_sink = match video_sink_weak.upgrade() {
Some(video_sink) => video_sink,
None => return glib::ControlFlow::Continue,
let Some(video_sink) = video_sink_weak.upgrade() else {
return glib::ControlFlow::Break;
};
let togglerecord = match togglerecord_weak.upgrade() {
Some(togglerecord) => togglerecord,
None => return glib::ControlFlow::Continue,
let Some(togglerecord) = togglerecord_weak.upgrade() else {
return glib::ControlFlow::Break;
};
let position = video_sink
@ -244,9 +242,8 @@ fn create_ui(app: &gtk::Application) {
let togglerecord_weak = togglerecord.downgrade();
record_button.connect_clicked(move |button| {
let togglerecord = match togglerecord_weak.upgrade() {
Some(togglerecord) => togglerecord,
None => return,
let Some(togglerecord) = togglerecord_weak.upgrade() else {
return;
};
let recording = !togglerecord.property::<bool>("record");
@ -257,9 +254,8 @@ fn create_ui(app: &gtk::Application) {
let record_button_weak = record_button.downgrade();
finish_button.connect_clicked(move |button| {
let record_button = match record_button_weak.upgrade() {
Some(record_button) => record_button,
None => return,
let Some(record_button) = record_button_weak.upgrade() else {
return;
};
record_button.set_sensitive(false);
@ -271,9 +267,8 @@ fn create_ui(app: &gtk::Application) {
let app_weak = app.downgrade();
window.connect_close_request(move |_| {
let app = match app_weak.upgrade() {
Some(app) => app,
None => return glib::Propagation::Stop,
let Some(app) = app_weak.upgrade() else {
return glib::Propagation::Stop;
};
app.quit();
@ -286,9 +281,8 @@ fn create_ui(app: &gtk::Application) {
.add_watch_local(move |_, msg| {
use gst::MessageView;
let app = match app_weak.upgrade() {
Some(app) => app,
None => return glib::ControlFlow::Break,
let Some(app) = app_weak.upgrade() else {
return glib::ControlFlow::Break;
};
match msg.view() {

View file

@ -66,14 +66,12 @@ fn setup_sender_receiver(
sinkpad.add_probe(
gst::PadProbeType::QUERY_UPSTREAM,
move |_pad, probe_info| {
let query = match &mut probe_info.data {
Some(gst::PadProbeData::Query(q)) => q,
_ => unreachable!(),
let Some(query) = probe_info.query_mut() else {
unreachable!();
};
use gst::QueryViewMut::*;
match query.view_mut() {
Latency(q) => {
gst::QueryViewMut::Latency(q) => {
q.set(live, gst::ClockTime::ZERO, None);
gst::PadProbeReturn::Handled
}

View file

@ -1,6 +1,6 @@
[package]
name = "gst-plugin-tracers"
version = "0.11.0-alpha.1"
version = "0.11.3"
authors = ["Guillaume Desmottes <guillaume.desmottes@onestream.live>"]
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
license = "MPL-2.0"
@ -9,7 +9,7 @@ description = "GStreamer Rust tracers plugin"
rust-version = "1.70"
[dependencies]
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.21", version = "0.21" }
anyhow = "1"
regex = "1"
@ -22,7 +22,7 @@ crate-type = ["cdylib", "rlib"]
path = "src/lib.rs"
[build-dependencies]
gst-plugin-version-helper = { path="../../version-helper" }
gst-plugin-version-helper = { path="../../version-helper", version = "0.8" }
[features]
static = []

View file

@ -192,9 +192,8 @@ impl PipelineSnapshot {
for signal in &mut signals {
match signal {
SIGUSR1 => {
let tracer = match tracer_weak.upgrade() {
Some(tracer) => tracer,
None => break,
let Some(tracer) = tracer_weak.upgrade() else {
break;
};
let pipelines = {

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