mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-09-28 23:02:13 +00:00
Compare commits
201 commits
main
...
gstreamer-
Author | SHA1 | Date | |
---|---|---|---|
|
59f575888e | ||
|
74a40060ce | ||
|
5c2582d105 | ||
|
4b9392938f | ||
|
407a367529 | ||
|
88a437ac32 | ||
|
853acfc4fe | ||
|
3cd6074a8e | ||
|
ab5ee0511b | ||
|
4ba452dcc3 | ||
|
711313c4c5 | ||
|
c83f48f0a1 | ||
|
101bcbc1a0 | ||
|
2a68be2000 | ||
|
3ea77d7a74 | ||
|
c818a575b4 | ||
|
43e5bd7b3a | ||
|
c6158b7a4e | ||
|
27f5b5cc33 | ||
|
d02508a7d0 | ||
|
df3b90881f | ||
|
53ae335d22 | ||
|
c8e8af3e81 | ||
|
8e0fc8b063 | ||
|
cc8da54adb | ||
|
e213ba9618 | ||
|
e8df0a0cb7 | ||
|
408d439631 | ||
|
2f623e15c2 | ||
|
2a8a90f76f | ||
|
cd3e333a0c | ||
|
1bfe6f9142 | ||
|
db9ef0b2af | ||
|
85a03f5ff0 | ||
|
4b936950c2 | ||
|
698ab100b3 | ||
|
517dc286d0 | ||
|
514a8e48ef | ||
|
ff1c99df98 | ||
|
696944c08e | ||
|
37dedfd4d0 | ||
|
bb2f632c9c | ||
|
f6b092d2af | ||
|
cd5a93dc09 | ||
|
a5a3c44951 | ||
|
a0dbb94e01 | ||
|
7013416a39 | ||
|
9f8fa99089 | ||
|
34434bd877 | ||
|
d67baa7668 | ||
|
8cdb30bd39 | ||
|
efc07cecf7 | ||
|
7db53aba22 | ||
|
2045847bd6 | ||
|
f4cb4b9da6 | ||
|
b0bd55c4d2 | ||
|
b9e6c817b7 | ||
|
e95a2c1016 | ||
|
31760b8f9a | ||
|
8d7ce380c4 | ||
|
e8701652e2 | ||
|
993619d654 | ||
|
d9d5571641 | ||
|
ba889c143c | ||
|
4b95bde38f | ||
|
041f51c4bb | ||
|
161c6db641 | ||
|
deeff67f94 | ||
|
568c2be582 | ||
|
548fe54ba9 | ||
|
a1afef2207 | ||
|
778c4da27e | ||
|
cbc99fb198 | ||
|
b701003352 | ||
|
5f9645bb74 | ||
|
bae5294e8f | ||
|
19957d1d23 | ||
|
bc9408840f | ||
|
08668a4bbb | ||
|
fb745f077b | ||
|
1eea2219c6 | ||
|
9b2d9ba4f9 | ||
|
00615ab478 | ||
|
6ceccac1be | ||
|
6596b6cdd1 | ||
|
b5641d838e | ||
|
fffd7dc542 | ||
|
b4185134d1 | ||
|
dc7e3c9f28 | ||
|
5f6afce842 | ||
|
7b1ee9f948 | ||
|
b6c9c14ccf | ||
|
3936211b55 | ||
|
2a981132b4 | ||
|
a1fd847f70 | ||
|
71558bd086 | ||
|
b689a0825e | ||
|
b3e33e329b | ||
|
8c27aefe76 | ||
|
44ec9eba7f | ||
|
fd5b31fb43 | ||
|
d79edce517 | ||
|
412c191fc2 | ||
|
e46d2dfa54 | ||
|
e4788662b9 | ||
|
cab5410782 | ||
|
8ac5632561 | ||
|
4f67623c22 | ||
|
1d4d9b3bdb | ||
|
3202c4dc39 | ||
|
3bc9df7e71 | ||
|
929c48e19a | ||
|
4e9ec324e1 | ||
|
3fc0326084 | ||
|
420716fb63 | ||
|
baf3da86cc | ||
|
2e529fa152 | ||
|
60772d2c06 | ||
|
184f879bf7 | ||
|
b2ad89cf06 | ||
|
506c96e8aa | ||
|
2227b41342 | ||
|
e6789fc338 | ||
|
b7534643be | ||
|
0b2aa2646f | ||
|
507377c052 | ||
|
f590b7e62f | ||
|
9fa3d88a63 | ||
|
b8d2d98027 | ||
|
bfe62488f4 | ||
|
922f14ea19 | ||
|
204e9af663 | ||
|
a5f48507c4 | ||
|
7df114e0e9 | ||
|
a7c75f8066 | ||
|
08799d242c | ||
|
a59a0340cf | ||
|
cadf36ff01 | ||
|
6a05b7f56a | ||
|
1f4a035dc0 | ||
|
b41d1e3f34 | ||
|
649434bd04 | ||
|
10813ed621 | ||
|
ea0d5751a2 | ||
|
c771c86631 | ||
|
516b561191 | ||
|
23e8fea170 | ||
|
81a46ee33d | ||
|
e7f5e73e3f | ||
|
969be7ab52 | ||
|
931917e559 | ||
|
1fb0062059 | ||
|
93ba677b18 | ||
|
9491c77540 | ||
|
6c15bba592 | ||
|
2b287bcd61 | ||
|
582cc34895 | ||
|
8bd9de8d48 | ||
|
b015688447 | ||
|
274e57a536 | ||
|
e434fd19ca | ||
|
28065de413 | ||
|
a6f64b5b20 | ||
|
5295fe9e67 | ||
|
331d053516 | ||
|
5c9bc03eab | ||
|
8c454c5c37 | ||
|
a9f3ff2925 | ||
|
cdf07dd860 | ||
|
2e52fece61 | ||
|
01816e2a8a | ||
|
ea82881e1c | ||
|
429e545e5c | ||
|
2e3373647a | ||
|
43ac186e69 | ||
|
1ef9a46508 | ||
|
b7891e77e5 | ||
|
bdb423e2b9 | ||
|
4556657602 | ||
|
cb5a956ee7 | ||
|
d0228ed544 | ||
|
6b3f0f764e | ||
|
07f3b0f504 | ||
|
8dc22d3bf1 | ||
|
f2f0eb30e0 | ||
|
b596b407f6 | ||
|
2b6d87cf66 | ||
|
18fa678a5c | ||
|
8e2a6500aa | ||
|
e268577994 | ||
|
f2a6a8d3de | ||
|
51ff099221 | ||
|
eefa8540ba | ||
|
790453364d | ||
|
32d2372e90 | ||
|
15955758b6 | ||
|
54fe3f1c02 | ||
|
a54318fbb4 | ||
|
46152533ba | ||
|
ba5270d30a | ||
|
2ff40142db |
184 changed files with 22152 additions and 4848 deletions
140
.gitlab-ci.yml
140
.gitlab-ci.yml
|
@ -38,10 +38,33 @@ default:
|
|||
interruptible: true
|
||||
|
||||
stages:
|
||||
- "trigger"
|
||||
- "prep"
|
||||
- "lint"
|
||||
- "test"
|
||||
- "extras"
|
||||
- "integration"
|
||||
|
||||
# This is an empty job that is used to trigger the pipeline.
|
||||
trigger:
|
||||
image: alpine:latest
|
||||
stage: 'trigger'
|
||||
variables:
|
||||
GIT_STRATEGY: none
|
||||
script:
|
||||
- echo "Trigger job done, now running the pipeline."
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
# If the MR is assigned to the Merge bot, trigger the pipeline automatically
|
||||
- if: '$CI_MERGE_REQUEST_ASSIGNEES == "gstreamer-merge-bot"'
|
||||
# Require explicit action to trigger tests post merge
|
||||
- if: '$CI_PROJECT_NAMESPACE == "gstreamer" && $CI_COMMIT_BRANCH == "main"'
|
||||
when: 'manual'
|
||||
# When the assignee isn't the merge bot, require an explicit action to trigger the pipeline
|
||||
# to avoid wasting CI resources
|
||||
- if: '$CI_MERGE_REQUEST_ASSIGNEES != "gstreamer-merge-bot"'
|
||||
when: 'manual'
|
||||
allow_failure: false
|
||||
|
||||
.debian:11:
|
||||
variables:
|
||||
|
@ -49,30 +72,23 @@ stages:
|
|||
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:11-stable:
|
||||
extends: .debian:11
|
||||
variables:
|
||||
FDO_DISTRIBUTION_TAG: '$GST_RS_STABLE-${GST_RS_IMG_TAG}_2022-09-07.0'
|
||||
FDO_DISTRIBUTION_TAG: '$GST_RS_STABLE-${GST_RS_IMG_TAG}_2022-11-05.0'
|
||||
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:$GST_RS_STABLE-$GST_RS_IMG_TAG"
|
||||
|
||||
.debian:11-msrv:
|
||||
extends: .debian:11
|
||||
variables:
|
||||
FDO_DISTRIBUTION_TAG: '$GST_RS_MSRV-${GST_RS_IMG_TAG}_2022-09-07.0'
|
||||
FDO_DISTRIBUTION_TAG: '$GST_RS_MSRV-${GST_RS_IMG_TAG}_2022-11-05.0'
|
||||
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:$GST_RS_MSRV-$GST_RS_IMG_TAG"
|
||||
|
||||
.debian:11-nightly:
|
||||
extends: .debian:11
|
||||
variables:
|
||||
FDO_DISTRIBUTION_TAG: 'nightly-${GST_RS_IMG_TAG}_2022-09-07.0'
|
||||
FDO_DISTRIBUTION_TAG: 'nightly-${GST_RS_IMG_TAG}_2022-11-05.0'
|
||||
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:nightly-$GST_RS_IMG_TAG"
|
||||
|
||||
.build-debian-container:
|
||||
|
@ -86,6 +102,8 @@ stages:
|
|||
apt clean &&
|
||||
bash ./ci/install-rust-ext.sh &&
|
||||
pip install tomli
|
||||
needs:
|
||||
- "trigger"
|
||||
rules:
|
||||
- if: '$UPDATE_IMG == null'
|
||||
|
||||
|
@ -230,7 +248,7 @@ meson shared:
|
|||
meson static:
|
||||
extends: .meson
|
||||
script:
|
||||
- meson build --default-library=static --prefix=$(pwd)/install -Dsodium=built-in
|
||||
- meson build --default-library=static --prefix=$(pwd)/install -Dsodium-source=built-in
|
||||
- ninja -C build install
|
||||
- ./ci/generate-static-test.py test-static-link-all
|
||||
- cd test-static-link-all
|
||||
|
@ -246,6 +264,7 @@ meson static:
|
|||
# Check that the gstreamer documentation keeps working
|
||||
documentation:
|
||||
image: $GSTREAMER_DOC_IMAGE
|
||||
stage: 'integration'
|
||||
variables:
|
||||
MESON_ARGS: >
|
||||
-Ddoc=enabled
|
||||
|
@ -258,7 +277,7 @@ documentation:
|
|||
-Dsharp=disabled
|
||||
-Dgst-examples=disabled
|
||||
-Drs=enabled
|
||||
-Dgst-plugins-rs:sodium=system
|
||||
-Dgst-plugins-rs:sodium-source=system
|
||||
-Dgst-docs:fatal_warnings=true
|
||||
-Dorc=disabled
|
||||
script:
|
||||
|
@ -279,54 +298,65 @@ documentation:
|
|||
paths:
|
||||
- documentation/
|
||||
needs: []
|
||||
rules:
|
||||
# Run job if the MR is assigned to the Merge bot or it a post-merge pipeline on main branch
|
||||
- if: '$CI_MERGE_REQUEST_ASSIGNEES == "gstreamer-merge-bot"'
|
||||
when: 'always'
|
||||
- if: '$CI_PROJECT_NAMESPACE == "gstreamer" && $CI_COMMIT_BRANCH == "main"'
|
||||
when: 'always'
|
||||
# Require explicit action to trigger otherwise
|
||||
- if: '$CI_PROJECT_NAMESPACE != "gstreamer" || $CI_COMMIT_BRANCH != "main"'
|
||||
when: 'manual'
|
||||
|
||||
# build gst-plugins-rs as a gst-build subproject
|
||||
gst-build:
|
||||
extends: .meson
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
when: 'manual'
|
||||
allow_failure: true
|
||||
variables:
|
||||
MESON_ARGS: >
|
||||
-Domx=disabled
|
||||
-Dpython=disabled
|
||||
-Dlibav=disabled
|
||||
-Dlibnice=disabled
|
||||
-Dugly=disabled
|
||||
-Dbad=disabled
|
||||
-Ddevtools=disabled
|
||||
-Dges=disabled
|
||||
-Drtsp_server=disabled
|
||||
-Dvaapi=disabled
|
||||
-Dsharp=disabled
|
||||
-Dgst-examples=disabled
|
||||
-Drs=enabled
|
||||
-Dgst-plugins-rs:sodium=system
|
||||
script:
|
||||
- P=$(pwd)
|
||||
- cd ..
|
||||
- rm -rf gstreamer
|
||||
- git clone --depth 1 https://gitlab.freedesktop.org/gstreamer/gstreamer.git --branch main
|
||||
- cd gstreamer
|
||||
- ln -s $P subprojects/gst-plugins-rs
|
||||
- meson build $MESON_ARGS
|
||||
- ninja -C build
|
||||
# Check static Rust plugins can be linked into gst-full
|
||||
- meson build-gst-full --default-library=static $MESON_ARGS
|
||||
- ninja -C build-gst-full
|
||||
- meson devenv -C build-gst-full ./gst-inspect-1.0 rsaudiofx
|
||||
artifacts:
|
||||
expire_in: '7 days'
|
||||
when: always
|
||||
paths:
|
||||
- 'build/meson-logs/'
|
||||
- 'build-gst-full/meson-logs/'
|
||||
# Disabled because of https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/262
|
||||
#gst-build:
|
||||
# extends: .meson
|
||||
# rules:
|
||||
# - if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
# - if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
# when: 'manual'
|
||||
# allow_failure: true
|
||||
# variables:
|
||||
# MESON_ARGS: >
|
||||
# -Domx=disabled
|
||||
# -Dpython=disabled
|
||||
# -Dlibav=disabled
|
||||
# -Dlibnice=disabled
|
||||
# -Dugly=disabled
|
||||
# -Dbad=disabled
|
||||
# -Ddevtools=disabled
|
||||
# -Dges=disabled
|
||||
# -Drtsp_server=disabled
|
||||
# -Dvaapi=disabled
|
||||
# -Dsharp=disabled
|
||||
# -Dgst-examples=disabled
|
||||
# -Drs=enabled
|
||||
# -Dgst-plugins-rs:sodium-source=system
|
||||
# script:
|
||||
# - P=$(pwd)
|
||||
# - cd ..
|
||||
# - rm -rf gstreamer
|
||||
# - git clone --depth 1 https://gitlab.freedesktop.org/gstreamer/gstreamer.git --branch main
|
||||
# - cd gstreamer
|
||||
# - ln -s $P subprojects/gst-plugins-rs
|
||||
# - meson build $MESON_ARGS
|
||||
# - ninja -C build
|
||||
# # Check static Rust plugins can be linked into gst-full
|
||||
# - meson build-gst-full --default-library=static $MESON_ARGS
|
||||
# - ninja -C build-gst-full
|
||||
# - meson devenv -C build-gst-full ./gst-inspect-1.0 rsaudiofx
|
||||
# artifacts:
|
||||
# expire_in: '7 days'
|
||||
# when: always
|
||||
# paths:
|
||||
# - 'build/meson-logs/'
|
||||
# - 'build-gst-full/meson-logs/'
|
||||
|
||||
.msvc2019 build:
|
||||
stage: 'test'
|
||||
needs: []
|
||||
needs:
|
||||
- 'trigger'
|
||||
tags:
|
||||
- 'docker'
|
||||
- 'windows'
|
||||
|
|
6305
Cargo.lock
generated
Normal file
6305
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -16,6 +16,7 @@ members = [
|
|||
|
||||
"mux/flavors",
|
||||
"mux/fmp4",
|
||||
"mux/mp4",
|
||||
|
||||
"net/aws",
|
||||
"net/hlssink3",
|
||||
|
@ -35,6 +36,7 @@ members = [
|
|||
"text/wrap",
|
||||
|
||||
"utils/fallbackswitch",
|
||||
"utils/livesync",
|
||||
"utils/togglerecord",
|
||||
"utils/tracers",
|
||||
"utils/uriplaylistbin",
|
||||
|
@ -63,6 +65,7 @@ default-members = [
|
|||
"generic/threadshare",
|
||||
|
||||
"mux/fmp4",
|
||||
"mux/mp4",
|
||||
|
||||
"net/aws",
|
||||
"net/hlssink3",
|
||||
|
@ -82,6 +85,7 @@ default-members = [
|
|||
"text/wrap",
|
||||
|
||||
"utils/fallbackswitch",
|
||||
"utils/livesync",
|
||||
"utils/togglerecord",
|
||||
"utils/tracers",
|
||||
"utils/uriplaylistbin",
|
||||
|
|
|
@ -127,6 +127,9 @@ You will find the following plugins in this repository:
|
|||
configuring a fallback audio/video if there are problems with the main
|
||||
source.
|
||||
|
||||
- `livesync`: Element to maintain a continuous live stream from a
|
||||
potentially unstable source.
|
||||
|
||||
- `togglerecord`: Element to enable starting and stopping multiple streams together.
|
||||
|
||||
- `tracers`: Plugin with multiple tracers:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-audiofx"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
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.63"
|
||||
|
||||
[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.19", version = "0.19.1", features = ["v1_16"] }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_16"] }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_16"] }
|
||||
anyhow = "1"
|
||||
byte-slice-cast = "1.0"
|
||||
num-traits = "0.2"
|
||||
|
@ -29,11 +29,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.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { path="../../version-helper" }
|
||||
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -90,10 +90,10 @@ fn run() -> Result<(), Error> {
|
|||
let new_z = -x * f32::sin(ROTATION) + z * f32::cos(ROTATION);
|
||||
|
||||
let objs = [gst::Structure::builder("application/spatial-object")
|
||||
.field("x", &new_x)
|
||||
.field("y", &y)
|
||||
.field("z", &new_z)
|
||||
.field("distance-gain", &gain)
|
||||
.field("x", new_x)
|
||||
.field("y", y)
|
||||
.field("z", new_z)
|
||||
.field("distance-gain", gain)
|
||||
.build()];
|
||||
|
||||
hrtf.set_property("spatial-objects", gst::Array::new(objs));
|
||||
|
|
|
@ -15,8 +15,8 @@ pub struct RingBuffer {
|
|||
|
||||
impl RingBuffer {
|
||||
pub fn new(size: usize) -> Self {
|
||||
let mut buffer = Vec::with_capacity(size as usize);
|
||||
buffer.extend(iter::repeat(0.0).take(size as usize));
|
||||
let mut buffer = Vec::with_capacity(size);
|
||||
buffer.extend(iter::repeat(0.0).take(size));
|
||||
|
||||
Self {
|
||||
buffer: buffer.into_boxed_slice(),
|
||||
|
|
|
@ -611,9 +611,8 @@ impl State {
|
|||
// the position where we have to start writing the next 100ms in the next
|
||||
// iteration.
|
||||
|
||||
let mut outbuf = gst::Buffer::with_size(
|
||||
self.current_samples_per_frame as usize * self.info.bpf() as usize,
|
||||
)
|
||||
let mut outbuf =
|
||||
gst::Buffer::with_size(self.current_samples_per_frame * self.info.bpf() as usize)
|
||||
.map_err(|_| gst::FlowError::Error)?;
|
||||
{
|
||||
let outbuf = outbuf.get_mut().unwrap();
|
||||
|
@ -819,7 +818,7 @@ impl State {
|
|||
// adjustment. frame_type should only ever be set to Final at the end if we ended up in
|
||||
// Inner state before.
|
||||
if self.frame_type == FrameType::First
|
||||
&& (src.len() / self.info.channels() as usize) < self.current_samples_per_frame as usize
|
||||
&& (src.len() / self.info.channels() as usize) < self.current_samples_per_frame
|
||||
{
|
||||
self.process_first_frame_is_last(imp)?;
|
||||
}
|
||||
|
@ -1560,7 +1559,7 @@ impl AudioLoudNorm {
|
|||
}
|
||||
|
||||
// Need to reset the state now
|
||||
*state = State::new(&*self.settings.lock().unwrap(), state.info.clone());
|
||||
*state = State::new(&self.settings.lock().unwrap(), state.info.clone());
|
||||
}
|
||||
|
||||
state.adapter.push(buffer);
|
||||
|
@ -1602,7 +1601,7 @@ impl AudioLoudNorm {
|
|||
Err(_) => return false,
|
||||
};
|
||||
}
|
||||
*state = Some(State::new(&*self.settings.lock().unwrap(), info));
|
||||
*state = Some(State::new(&self.settings.lock().unwrap(), info));
|
||||
drop(state);
|
||||
|
||||
if let Some(outbuf) = outbuf {
|
||||
|
@ -1623,7 +1622,7 @@ impl AudioLoudNorm {
|
|||
Err(gst::FlowError::Eos) => None,
|
||||
Err(_) => return false,
|
||||
};
|
||||
*state = State::new(&*self.settings.lock().unwrap(), state.info.clone());
|
||||
*state = State::new(&self.settings.lock().unwrap(), state.info.clone());
|
||||
}
|
||||
drop(state);
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
|
@ -31,8 +33,22 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|||
)
|
||||
});
|
||||
|
||||
const DEFAULT_VOICE_ACTIVITY_THRESHOLD: f32 = 0.0;
|
||||
const FRAME_SIZE: usize = DenoiseState::FRAME_SIZE;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Settings {
|
||||
vad_threshold: f32,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings {
|
||||
vad_threshold: DEFAULT_VOICE_ACTIVITY_THRESHOLD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChannelDenoiser {
|
||||
denoiser: Box<DenoiseState<'static>>,
|
||||
frame_chunk: Box<[f32; FRAME_SIZE]>,
|
||||
|
@ -47,6 +63,7 @@ struct State {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct AudioRNNoise {
|
||||
settings: Mutex<Settings>,
|
||||
state: AtomicRefCell<Option<State>>,
|
||||
}
|
||||
|
||||
|
@ -82,43 +99,6 @@ impl State {
|
|||
fn needs_more_data(&self) -> bool {
|
||||
self.adapter.available() < (FRAME_SIZE * self.in_info.bpf() as usize)
|
||||
}
|
||||
|
||||
fn process(&mut self, input_plane: &[f32], output_plane: &mut [f32]) {
|
||||
let channels = self.in_info.channels() as usize;
|
||||
let size = FRAME_SIZE * channels;
|
||||
|
||||
for (out_frame, in_frame) in output_plane.chunks_mut(size).zip(input_plane.chunks(size)) {
|
||||
for (index, item) in in_frame.iter().enumerate() {
|
||||
let channel_index = index % channels;
|
||||
let channel_denoiser = &mut self.denoisers[channel_index];
|
||||
let pos = index / channels;
|
||||
channel_denoiser.frame_chunk[pos] = *item;
|
||||
}
|
||||
|
||||
for i in (in_frame.len() / channels)..(size / channels) {
|
||||
for c in 0..channels {
|
||||
let channel_denoiser = &mut self.denoisers[c];
|
||||
channel_denoiser.frame_chunk[i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: The first chunks coming out of the denoisers contains some
|
||||
// fade-in artifacts. We might want to discard those.
|
||||
for channel_denoiser in &mut self.denoisers {
|
||||
channel_denoiser.denoiser.process_frame(
|
||||
&mut channel_denoiser.out_chunk[..],
|
||||
&channel_denoiser.frame_chunk[..],
|
||||
);
|
||||
}
|
||||
|
||||
for (index, item) in out_frame.iter_mut().enumerate() {
|
||||
let channel_index = index % channels;
|
||||
let channel_denoiser = &self.denoisers[channel_index];
|
||||
let pos = index / channels;
|
||||
*item = channel_denoiser.out_chunk[pos];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AudioRNNoise {
|
||||
|
@ -131,6 +111,7 @@ impl AudioRNNoise {
|
|||
return Ok(gst::FlowSuccess::Ok);
|
||||
}
|
||||
|
||||
let settings = *self.settings.lock().unwrap();
|
||||
let mut buffer = gst::Buffer::with_size(available).map_err(|e| {
|
||||
gst::error!(CAT, imp: self, "Failed to allocate buffer at EOS {:?}", e);
|
||||
gst::FlowError::Flushing
|
||||
|
@ -151,7 +132,7 @@ impl AudioRNNoise {
|
|||
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
||||
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
|
||||
|
||||
state.process(in_data, out_data);
|
||||
self.process(state, &settings, in_data, out_data);
|
||||
}
|
||||
|
||||
self.obj().src_pad().push(buffer)
|
||||
|
@ -164,6 +145,7 @@ impl AudioRNNoise {
|
|||
let duration = state.buffer_duration(output_size as _);
|
||||
let pts = state.current_pts();
|
||||
|
||||
let settings = *self.settings.lock().unwrap();
|
||||
let mut buffer = gst::Buffer::with_size(output_size).map_err(|_| gst::FlowError::Error)?;
|
||||
|
||||
{
|
||||
|
@ -181,11 +163,64 @@ impl AudioRNNoise {
|
|||
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
||||
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
|
||||
|
||||
state.process(in_data, out_data);
|
||||
self.process(state, &settings, in_data, out_data);
|
||||
}
|
||||
|
||||
Ok(GenerateOutputSuccess::Buffer(buffer))
|
||||
}
|
||||
|
||||
fn process(
|
||||
&self,
|
||||
state: &mut State,
|
||||
settings: &Settings,
|
||||
input_plane: &[f32],
|
||||
output_plane: &mut [f32],
|
||||
) {
|
||||
let channels = state.in_info.channels() as usize;
|
||||
let size = FRAME_SIZE * channels;
|
||||
|
||||
for (out_frame, in_frame) in output_plane.chunks_mut(size).zip(input_plane.chunks(size)) {
|
||||
for (index, item) in in_frame.iter().enumerate() {
|
||||
let channel_index = index % channels;
|
||||
let channel_denoiser = &mut state.denoisers[channel_index];
|
||||
let pos = index / channels;
|
||||
channel_denoiser.frame_chunk[pos] = *item * 32767.0;
|
||||
}
|
||||
|
||||
for i in (in_frame.len() / channels)..(size / channels) {
|
||||
for c in 0..channels {
|
||||
let channel_denoiser = &mut state.denoisers[c];
|
||||
channel_denoiser.frame_chunk[i] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: The first chunks coming out of the denoisers contains some
|
||||
// fade-in artifacts. We might want to discard those.
|
||||
let mut vad: f32 = 0.0;
|
||||
for channel_denoiser in &mut state.denoisers {
|
||||
vad = f32::max(
|
||||
vad,
|
||||
channel_denoiser.denoiser.process_frame(
|
||||
&mut channel_denoiser.out_chunk[..],
|
||||
&channel_denoiser.frame_chunk[..],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
gst::debug!(CAT, imp: self, "Voice activity: {}", vad);
|
||||
|
||||
if vad < settings.vad_threshold {
|
||||
out_frame.fill(0.0);
|
||||
} else {
|
||||
for (index, item) in out_frame.iter_mut().enumerate() {
|
||||
let channel_index = index % channels;
|
||||
let channel_denoiser = &state.denoisers[channel_index];
|
||||
let pos = index / channels;
|
||||
*item = channel_denoiser.out_chunk[pos] / 32767.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -195,7 +230,42 @@ impl ObjectSubclass for AudioRNNoise {
|
|||
type ParentType = gst_base::BaseTransform;
|
||||
}
|
||||
|
||||
impl ObjectImpl for AudioRNNoise {}
|
||||
impl ObjectImpl for AudioRNNoise {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![glib::ParamSpecFloat::builder("voice-activity-threshold")
|
||||
.nick("Voice activity threshold")
|
||||
.blurb("Threshold of the voice activity detector below which to mute the output")
|
||||
.minimum(0.0)
|
||||
.maximum(1.0)
|
||||
.default_value(DEFAULT_VOICE_ACTIVITY_THRESHOLD)
|
||||
.mutable_playing()
|
||||
.build()]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"voice-activity-threshold" => {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
settings.vad_threshold = value.get().expect("type checked upstream");
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"voice-activity-threshold" => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
settings.vad_threshold.to_value()
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for AudioRNNoise {}
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ impl ObjectImpl for EbuR128Level {
|
|||
.build()]
|
||||
});
|
||||
|
||||
&*SIGNALS
|
||||
&SIGNALS
|
||||
}
|
||||
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
|
@ -482,7 +482,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
|
||||
if state.ebur128.mode().contains(ebur128::Mode::M) {
|
||||
match state.ebur128.loudness_momentary() {
|
||||
Ok(loudness) => s.set("momentary-loudness", &loudness),
|
||||
Ok(loudness) => s.set("momentary-loudness", loudness),
|
||||
Err(err) => gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
|
@ -494,7 +494,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
|
||||
if state.ebur128.mode().contains(ebur128::Mode::S) {
|
||||
match state.ebur128.loudness_shortterm() {
|
||||
Ok(loudness) => s.set("shortterm-loudness", &loudness),
|
||||
Ok(loudness) => s.set("shortterm-loudness", loudness),
|
||||
Err(err) => gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
|
@ -506,7 +506,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
|
||||
if state.ebur128.mode().contains(ebur128::Mode::I) {
|
||||
match state.ebur128.loudness_global() {
|
||||
Ok(loudness) => s.set("global-loudness", &loudness),
|
||||
Ok(loudness) => s.set("global-loudness", loudness),
|
||||
Err(err) => gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
|
@ -516,7 +516,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
}
|
||||
|
||||
match state.ebur128.relative_threshold() {
|
||||
Ok(threshold) => s.set("relative-threshold", &threshold),
|
||||
Ok(threshold) => s.set("relative-threshold", threshold),
|
||||
Err(err) => gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
|
@ -528,7 +528,7 @@ impl BaseTransformImpl for EbuR128Level {
|
|||
|
||||
if state.ebur128.mode().contains(ebur128::Mode::LRA) {
|
||||
match state.ebur128.loudness_range() {
|
||||
Ok(range) => s.set("loudness-range", &range),
|
||||
Ok(range) => s.set("loudness-range", range),
|
||||
Err(err) => {
|
||||
gst::error!(CAT, imp: self, "Failed to get loudness range: {}", err)
|
||||
}
|
||||
|
|
|
@ -373,7 +373,7 @@ impl HrtfRender {
|
|||
let (prev_offset, _) = state.adapter.prev_offset();
|
||||
let offset = prev_offset.checked_add(distance_samples).unwrap_or(0);
|
||||
|
||||
let duration_samples = outputsz / outbpf as usize;
|
||||
let duration_samples = outputsz / outbpf;
|
||||
let duration = samples_to_time(duration_samples as u64);
|
||||
|
||||
(pts, offset, duration)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-claxon"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
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.63"
|
||||
|
||||
[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.19", version = "0.19.1" }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
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.19", version = "0.19" }
|
||||
|
||||
[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 = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-csound"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
authors = ["Natanael Mojica <neithanmo@gmail.com>"]
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
license = "MPL-2.0"
|
||||
|
@ -9,15 +9,15 @@ rust-version = "1.63"
|
|||
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.19", version = "0.19.1" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
csound = "0.1.8"
|
||||
once_cell = "1.0"
|
||||
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.19", version = "0.19" }
|
||||
|
||||
[lib]
|
||||
name = "gstcsound"
|
||||
|
@ -29,7 +29,7 @@ name = "csound-effect"
|
|||
path = "examples/effect_example.rs"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { path = "../../version-helper" }
|
||||
gst-plugin-version-helper = { version = "0.7", path = "../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -80,7 +80,7 @@ fn create_pipeline() -> Result<gst::Pipeline, Box<dyn Error>> {
|
|||
let audio_sink = gst::parse_bin_from_description(AUDIO_SINK, true)?.upcast();
|
||||
|
||||
let csoundfilter = gst::ElementFactory::make("csoundfilter")
|
||||
.property("csd-text", &CSD)
|
||||
.property("csd-text", CSD)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -517,15 +517,15 @@ impl BaseTransformImpl for CsoundFilter {
|
|||
let ichannels = csound.input_channels() as i32;
|
||||
let ochannels = csound.output_channels() as i32;
|
||||
for s in new_caps.make_mut().iter_mut() {
|
||||
s.set("format", &gst_audio::AUDIO_FORMAT_F64.to_str());
|
||||
s.set("rate", &sr);
|
||||
s.set("format", gst_audio::AUDIO_FORMAT_F64.to_str());
|
||||
s.set("rate", sr);
|
||||
|
||||
// replace the channel property with our values,
|
||||
// if they are not supported, the negotiation will fail.
|
||||
if direction == gst::PadDirection::Src {
|
||||
s.set("channels", &ichannels);
|
||||
s.set("channels", ichannels);
|
||||
} else {
|
||||
s.set("channels", &ochannels);
|
||||
s.set("channels", ochannels);
|
||||
}
|
||||
// Csound does not have a concept of channel-mask
|
||||
s.remove_field("channel-mask");
|
||||
|
|
|
@ -57,7 +57,7 @@ fn init() {
|
|||
|
||||
fn build_harness(src_caps: gst::Caps, sink_caps: gst::Caps, csd: &str) -> gst_check::Harness {
|
||||
let filter = gst::ElementFactory::make("csoundfilter")
|
||||
.property("csd-text", &csd)
|
||||
.property("csd-text", csd)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
@ -260,10 +260,7 @@ fn csound_filter_underflow() {
|
|||
}
|
||||
|
||||
assert_eq!(num_buffers, UNDERFLOW_NUM_BUFFERS / 2);
|
||||
assert_eq!(
|
||||
num_samples as usize,
|
||||
UNDERFLOW_NUM_SAMPLES * UNDERFLOW_NUM_BUFFERS
|
||||
);
|
||||
assert_eq!(num_samples, UNDERFLOW_NUM_SAMPLES * UNDERFLOW_NUM_BUFFERS);
|
||||
}
|
||||
|
||||
// Verifies that the caps negotiation is properly done, by pushing buffers whose caps
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-lewton"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
authors = ["Sebastian Dröge <sebastian@centricular.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.63"
|
||||
|
||||
[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.19", version = "0.19.1" }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
lewton = { version = "0.10", default-features = false }
|
||||
byte-slice-cast = "1.0"
|
||||
atomic_refcell = "0.1"
|
||||
once_cell = "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.19", version = "0.19" }
|
||||
|
||||
[lib]
|
||||
name = "gstlewton"
|
||||
|
@ -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 = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -405,7 +405,7 @@ impl LewtonDec {
|
|||
let outbuf = if let Some(ref reorder_map) = state.reorder_map {
|
||||
let mut outbuf = self
|
||||
.obj()
|
||||
.allocate_output_buffer(sample_count as usize * audio_info.bpf() as usize);
|
||||
.allocate_output_buffer(sample_count * audio_info.bpf() as usize);
|
||||
{
|
||||
// And copy the decoded data into our output buffer while reordering the channels to the
|
||||
// GStreamer channel order
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-spotify"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
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.63"
|
||||
|
||||
[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.19", version = "0.19.1" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
once_cell = "1.0"
|
||||
librespot = { version = "0.4", default-features = false }
|
||||
tokio = "1.0"
|
||||
|
@ -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 = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use std::sync::{mpsc, Arc, Mutex};
|
||||
use std::sync::{mpsc, Arc, Mutex, MutexGuard};
|
||||
|
||||
use anyhow::bail;
|
||||
use futures::future::{AbortHandle, Abortable, Aborted};
|
||||
use once_cell::sync::Lazy;
|
||||
use tokio::{runtime, task::JoinHandle};
|
||||
|
||||
|
@ -74,10 +75,16 @@ struct Settings {
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct SpotifyAudioSrc {
|
||||
setup_thread: Mutex<Option<SetupThread>>,
|
||||
state: Arc<Mutex<Option<State>>>,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
struct SetupThread {
|
||||
thread_handle: std::thread::JoinHandle<Result<anyhow::Result<()>, Aborted>>,
|
||||
abort_handle: AbortHandle,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for SpotifyAudioSrc {
|
||||
const NAME: &'static str = "GstSpotifyAudioSrc";
|
||||
|
@ -237,17 +244,22 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
|||
}
|
||||
}
|
||||
|
||||
if let Err(err) = RUNTIME.block_on(async move { self.setup().await }) {
|
||||
let details = format!("{:?}", err);
|
||||
gst::error!(CAT, imp: self, "failed to start: {}", details);
|
||||
gst::element_imp_error!(self, gst::ResourceError::Settings, [&details]);
|
||||
return Err(gst::error_msg!(gst::ResourceError::Settings, [&details]));
|
||||
{
|
||||
let setup_thread = self.setup_thread.lock().unwrap();
|
||||
if setup_thread.is_some() {
|
||||
// already starting
|
||||
return Ok(());
|
||||
}
|
||||
self.start_setup(setup_thread);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
// stop the setup if it's not completed yet
|
||||
self.cancel_setup();
|
||||
|
||||
if let Some(state) = self.state.lock().unwrap().take() {
|
||||
gst::debug!(CAT, imp: self, "stopping");
|
||||
state.player.stop();
|
||||
|
@ -258,6 +270,12 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unlock(&self) -> Result<(), gst::ErrorMessage> {
|
||||
self.cancel_setup();
|
||||
|
||||
self.parent_unlock()
|
||||
}
|
||||
}
|
||||
|
||||
impl PushSrcImpl for SpotifyAudioSrc {
|
||||
|
@ -265,6 +283,41 @@ impl PushSrcImpl for SpotifyAudioSrc {
|
|||
&self,
|
||||
_buffer: Option<&mut gst::BufferRef>,
|
||||
) -> Result<CreateSuccess, gst::FlowError> {
|
||||
let state_set = {
|
||||
let state = self.state.lock().unwrap();
|
||||
state.is_some()
|
||||
};
|
||||
|
||||
if !state_set {
|
||||
let setup_thread = self.setup_thread.lock().unwrap();
|
||||
if setup_thread.is_none() {
|
||||
// unlock() could potentially cancel the setup, and create() can be called after unlock() without going through start() again.
|
||||
self.start_setup(setup_thread);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// wait for the setup to be completed
|
||||
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||
if let Some(setup) = setup_thread.take() {
|
||||
let res = setup.thread_handle.join().unwrap();
|
||||
|
||||
match res {
|
||||
Err(_aborted) => {
|
||||
gst::debug!(CAT, imp: self, "setup has been cancelled");
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
let details = format!("{:?}", err);
|
||||
gst::error!(CAT, imp: self, "failed to start: {}", details);
|
||||
gst::element_imp_error!(self, gst::ResourceError::Settings, [&details]);
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
Ok(Ok(_)) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let state = self.state.lock().unwrap();
|
||||
let state = state.as_ref().unwrap();
|
||||
|
||||
|
@ -290,112 +343,6 @@ impl PushSrcImpl for SpotifyAudioSrc {
|
|||
}
|
||||
}
|
||||
|
||||
impl SpotifyAudioSrc {
|
||||
async fn setup(&self) -> anyhow::Result<()> {
|
||||
let (credentials, cache, track) = {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
|
||||
let credentials_cache = if settings.cache_credentials.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&settings.cache_credentials)
|
||||
};
|
||||
|
||||
let files_cache = if settings.cache_files.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&settings.cache_files)
|
||||
};
|
||||
|
||||
let max_size = if settings.cache_max_size != 0 {
|
||||
Some(settings.cache_max_size)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cache = Cache::new(credentials_cache, None, files_cache, max_size)?;
|
||||
|
||||
let credentials = match cache.credentials() {
|
||||
Some(cached_cred) => {
|
||||
gst::debug!(CAT, imp: self, "reuse credentials from cache",);
|
||||
cached_cred
|
||||
}
|
||||
None => {
|
||||
gst::debug!(CAT, imp: self, "credentials not in cache",);
|
||||
|
||||
if settings.username.is_empty() {
|
||||
bail!("username is not set and credentials are not in cache");
|
||||
}
|
||||
if settings.password.is_empty() {
|
||||
bail!("password is not set and credentials are not in cache");
|
||||
}
|
||||
|
||||
let cred = Credentials::with_password(&settings.username, &settings.password);
|
||||
cache.save_credentials(&cred);
|
||||
cred
|
||||
}
|
||||
};
|
||||
|
||||
if settings.track.is_empty() {
|
||||
bail!("track is not set")
|
||||
}
|
||||
|
||||
(credentials, cache, settings.track.clone())
|
||||
};
|
||||
|
||||
let state = self.state.clone();
|
||||
|
||||
let (session, _credentials) =
|
||||
Session::connect(SessionConfig::default(), credentials, Some(cache), false).await?;
|
||||
|
||||
let player_config = PlayerConfig {
|
||||
passthrough: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// use a sync channel to prevent buffering the whole track inside the channel
|
||||
let (sender, receiver) = mpsc::sync_channel(2);
|
||||
let sender_clone = sender.clone();
|
||||
|
||||
let (mut player, mut player_event_channel) =
|
||||
Player::new(player_config, session, Box::new(NoOpVolume), || {
|
||||
Box::new(BufferSink { sender })
|
||||
});
|
||||
|
||||
let track = match SpotifyId::from_uri(&track) {
|
||||
Ok(track) => track,
|
||||
Err(_) => bail!("Failed to create Spotify URI from track"),
|
||||
};
|
||||
|
||||
player.load(track, true, 0);
|
||||
|
||||
let player_channel_handle = RUNTIME.spawn(async move {
|
||||
let sender = sender_clone;
|
||||
|
||||
while let Some(event) = player_event_channel.recv().await {
|
||||
match event {
|
||||
PlayerEvent::EndOfTrack { .. } => {
|
||||
let _ = sender.send(Message::Eos);
|
||||
}
|
||||
PlayerEvent::Unavailable { .. } => {
|
||||
let _ = sender.send(Message::Unavailable);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut state = state.lock().unwrap();
|
||||
state.replace(State {
|
||||
player,
|
||||
receiver,
|
||||
player_channel_handle,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct BufferSink {
|
||||
sender: mpsc::SyncSender<Message>,
|
||||
}
|
||||
|
@ -456,3 +403,144 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SpotifyAudioSrc {
|
||||
fn start_setup(&self, mut setup_thread: MutexGuard<Option<SetupThread>>) {
|
||||
let self_ = self.to_owned();
|
||||
|
||||
// run the runtime from another thread to prevent the "start a runtime from within a runtime" panic
|
||||
// when the plugin is statically linked.
|
||||
let (abort_handle, abort_registration) = AbortHandle::new_pair();
|
||||
let thread_handle = std::thread::spawn(move || {
|
||||
RUNTIME.block_on(async move {
|
||||
let future = Abortable::new(self_.setup(), abort_registration);
|
||||
future.await
|
||||
})
|
||||
});
|
||||
|
||||
setup_thread.replace(SetupThread {
|
||||
thread_handle,
|
||||
abort_handle,
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup(&self) -> anyhow::Result<()> {
|
||||
{
|
||||
let state = self.state.lock().unwrap();
|
||||
|
||||
if state.is_some() {
|
||||
// already setup
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let (credentials, cache, track) = {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
|
||||
let credentials_cache = if settings.cache_credentials.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&settings.cache_credentials)
|
||||
};
|
||||
|
||||
let files_cache = if settings.cache_files.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(&settings.cache_files)
|
||||
};
|
||||
|
||||
let max_size = if settings.cache_max_size != 0 {
|
||||
Some(settings.cache_max_size)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cache = Cache::new(credentials_cache, None, files_cache, max_size)?;
|
||||
|
||||
let credentials = match cache.credentials() {
|
||||
Some(cached_cred) => {
|
||||
gst::debug!(CAT, imp: self, "reuse credentials from cache",);
|
||||
cached_cred
|
||||
}
|
||||
None => {
|
||||
gst::debug!(CAT, imp: self, "credentials not in cache",);
|
||||
|
||||
if settings.username.is_empty() {
|
||||
bail!("username is not set and credentials are not in cache");
|
||||
}
|
||||
if settings.password.is_empty() {
|
||||
bail!("password is not set and credentials are not in cache");
|
||||
}
|
||||
|
||||
let cred = Credentials::with_password(&settings.username, &settings.password);
|
||||
cache.save_credentials(&cred);
|
||||
cred
|
||||
}
|
||||
};
|
||||
|
||||
if settings.track.is_empty() {
|
||||
bail!("track is not set")
|
||||
}
|
||||
|
||||
(credentials, cache, settings.track.clone())
|
||||
};
|
||||
|
||||
let (session, _credentials) =
|
||||
Session::connect(SessionConfig::default(), credentials, Some(cache), false).await?;
|
||||
|
||||
let player_config = PlayerConfig {
|
||||
passthrough: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// use a sync channel to prevent buffering the whole track inside the channel
|
||||
let (sender, receiver) = mpsc::sync_channel(2);
|
||||
let sender_clone = sender.clone();
|
||||
|
||||
let (mut player, mut player_event_channel) =
|
||||
Player::new(player_config, session, Box::new(NoOpVolume), || {
|
||||
Box::new(BufferSink { sender })
|
||||
});
|
||||
|
||||
let track = match SpotifyId::from_uri(&track) {
|
||||
Ok(track) => track,
|
||||
Err(_) => bail!("Failed to create Spotify URI from track"),
|
||||
};
|
||||
|
||||
player.load(track, true, 0);
|
||||
|
||||
let player_channel_handle = RUNTIME.spawn(async move {
|
||||
let sender = sender_clone;
|
||||
|
||||
while let Some(event) = player_event_channel.recv().await {
|
||||
match event {
|
||||
PlayerEvent::EndOfTrack { .. } => {
|
||||
let _ = sender.send(Message::Eos);
|
||||
}
|
||||
PlayerEvent::Unavailable { .. } => {
|
||||
let _ = sender.send(Message::Unavailable);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
state.replace(State {
|
||||
player,
|
||||
receiver,
|
||||
player_channel_handle,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cancel_setup(&self) {
|
||||
let mut setup_thread = self.setup_thread.lock().unwrap();
|
||||
|
||||
if let Some(setup) = setup_thread.take() {
|
||||
setup.abort_handle.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,15 @@ PARSER.add_argument('build_dir', type=P)
|
|||
PARSER.add_argument('src_dir', type=P)
|
||||
PARSER.add_argument('root_dir', type=P)
|
||||
PARSER.add_argument('target', choices=['release', 'debug'])
|
||||
PARSER.add_argument('include')
|
||||
PARSER.add_argument('extra_env')
|
||||
PARSER.add_argument('prefix', type=P)
|
||||
PARSER.add_argument('libdir', type=P)
|
||||
PARSER.add_argument('--version', default=None)
|
||||
PARSER.add_argument('--bin', default=None, type=P)
|
||||
PARSER.add_argument('--exts', nargs="+", default=[])
|
||||
PARSER.add_argument('--features', nargs="+", default=[])
|
||||
PARSER.add_argument('--packages', nargs="+", default=[])
|
||||
PARSER.add_argument('--examples', nargs="+", default=[])
|
||||
PARSER.add_argument('--lib-suffixes', nargs="+", default=[])
|
||||
PARSER.add_argument('--exe-suffix')
|
||||
PARSER.add_argument('--depfile')
|
||||
PARSER.add_argument('--disable-doc', action="store_true", default=False)
|
||||
|
||||
|
@ -33,6 +35,18 @@ def generate_depfile_for(fpath):
|
|||
with open(f"{file_stem}.d", 'r') as depfile:
|
||||
for l in depfile.readlines():
|
||||
if l.startswith(str(file_stem)):
|
||||
# We can't blindly split on `:` because on Windows that's part
|
||||
# of the drive letter. Lucky for us, the format of the dep file
|
||||
# is one of:
|
||||
#
|
||||
# /path/to/output: /path/to/src1 /path/to/src2
|
||||
# /path/to/output:
|
||||
#
|
||||
# So we parse these two cases specifically
|
||||
if l.endswith(':'):
|
||||
output = l[:-1]
|
||||
srcs = ''
|
||||
else:
|
||||
output, srcs = l.split(": ", maxsplit=2)
|
||||
|
||||
all_deps = []
|
||||
|
@ -53,33 +67,30 @@ def generate_depfile_for(fpath):
|
|||
|
||||
if __name__ == "__main__":
|
||||
opts = PARSER.parse_args()
|
||||
|
||||
logfile = open(opts.root_dir / 'meson-logs' /
|
||||
f'{opts.src_dir.name}-cargo-wrapper.log', 'w')
|
||||
logdir = opts.root_dir / 'meson-logs'
|
||||
logfile_path = logdir / f'{opts.src_dir.name}-cargo-wrapper.log'
|
||||
logfile = open(logfile_path, mode='w', buffering=1)
|
||||
|
||||
print(opts, file=logfile)
|
||||
cargo_target_dir = opts.build_dir / 'target'
|
||||
|
||||
env = os.environ.copy()
|
||||
env['CARGO_TARGET_DIR'] = str(cargo_target_dir)
|
||||
|
||||
pkg_config_path = env.get('PKG_CONFIG_PATH', '').split(':')
|
||||
if 'PKG_CONFIG_PATH' in env:
|
||||
pkg_config_path = env['PKG_CONFIG_PATH'].split(os.pathsep)
|
||||
else:
|
||||
pkg_config_path = []
|
||||
pkg_config_path.append(str(opts.root_dir / 'meson-uninstalled'))
|
||||
env['PKG_CONFIG_PATH'] = ':'.join(pkg_config_path)
|
||||
|
||||
if opts.extra_env:
|
||||
for e in opts.extra_env.split(','):
|
||||
k, v = e.split(':')
|
||||
env[k] = v
|
||||
env['PKG_CONFIG_PATH'] = os.pathsep.join(pkg_config_path)
|
||||
|
||||
features = opts.features
|
||||
if opts.command == 'build':
|
||||
cargo_cmd = ['cargo']
|
||||
if opts.bin:
|
||||
if opts.bin or opts.examples:
|
||||
cargo_cmd += ['build']
|
||||
else:
|
||||
cargo_cmd += ['cbuild']
|
||||
if not opts.disable_doc:
|
||||
cargo_cmd += ['--features', "doc"]
|
||||
features += ['doc']
|
||||
if opts.target == 'release':
|
||||
cargo_cmd.append('--release')
|
||||
elif opts.command == 'test':
|
||||
|
@ -89,40 +100,41 @@ if __name__ == "__main__":
|
|||
print("Unknown command:", opts.command, file=logfile)
|
||||
sys.exit(1)
|
||||
|
||||
cwd = None
|
||||
if not opts.bin:
|
||||
cargo_cmd.extend(['--manifest-path', opts.src_dir / 'Cargo.toml'])
|
||||
if features:
|
||||
cargo_cmd += ['--features', ','.join(features)]
|
||||
cargo_cmd += ['--target-dir', cargo_target_dir]
|
||||
cargo_cmd += ['--manifest-path', opts.src_dir / 'Cargo.toml']
|
||||
if opts.bin:
|
||||
cargo_cmd.extend(['--bin', opts.bin.name])
|
||||
else:
|
||||
if not opts.examples:
|
||||
cargo_cmd.extend(['--prefix', opts.prefix, '--libdir',
|
||||
opts.prefix / opts.libdir])
|
||||
for p in opts.include.split(','):
|
||||
for p in opts.packages:
|
||||
cargo_cmd.extend(['-p', p])
|
||||
else:
|
||||
cargo_cmd.extend(['--bin', opts.bin.name])
|
||||
cwd = opts.src_dir
|
||||
for e in opts.examples:
|
||||
cargo_cmd.extend(['--example', e])
|
||||
|
||||
def run(cargo_cmd, env, cwd=cwd):
|
||||
def run(cargo_cmd, env):
|
||||
print(cargo_cmd, env, file=logfile)
|
||||
try:
|
||||
subprocess.run(cargo_cmd, env=env, check=True, cwd=cwd)
|
||||
subprocess.run(cargo_cmd, env=env, cwd=opts.src_dir, check=True)
|
||||
except subprocess.SubprocessError:
|
||||
sys.exit(1)
|
||||
|
||||
run(cargo_cmd, env, cwd)
|
||||
run(cargo_cmd, env)
|
||||
|
||||
if opts.command == 'build':
|
||||
target_dir = cargo_target_dir / '**' / opts.target
|
||||
if opts.bin:
|
||||
if opts.exts[0]:
|
||||
ext = f'.{opts.exts[0]}'
|
||||
else:
|
||||
ext = ''
|
||||
exe = glob.glob(str(target_dir / opts.bin) + ext, recursive=True)[0]
|
||||
exe = glob.glob(str(target_dir / opts.bin) + opts.exe_suffix, recursive=True)[0]
|
||||
shutil.copy2(exe, opts.build_dir)
|
||||
depfile_content = generate_depfile_for(P(exe))
|
||||
else:
|
||||
# Copy so files to build dir
|
||||
depfile_content = ""
|
||||
for ext in opts.exts:
|
||||
for f in glob.glob(str(target_dir / f'*.{ext}'), recursive=True):
|
||||
for suffix in opts.lib_suffixes:
|
||||
for f in glob.glob(str(target_dir / f'*.{suffix}'), recursive=True):
|
||||
libfile = P(f)
|
||||
|
||||
depfile_content += generate_depfile_for(libfile)
|
||||
|
@ -137,6 +149,12 @@ if __name__ == "__main__":
|
|||
|
||||
print(f"Copying {copied_file}", file=logfile)
|
||||
shutil.copy2(f, opts.build_dir)
|
||||
# Copy examples to builddir
|
||||
for example in opts.examples:
|
||||
example_glob = str(target_dir / 'examples' / example) + opts.exe_suffix
|
||||
exe = glob.glob(example_glob, recursive=True)[0]
|
||||
shutil.copy2(exe, opts.build_dir)
|
||||
depfile_content += generate_depfile_for(P(exe))
|
||||
|
||||
with open(opts.depfile, 'w') as depfile:
|
||||
depfile.write(depfile_content)
|
||||
|
|
|
@ -7,7 +7,8 @@ import os
|
|||
from utils import iterate_plugins
|
||||
|
||||
# the csound version used on ci does not ship a .pc file
|
||||
IGNORE = ['csound']
|
||||
# threadshare we skip in meson static build as well
|
||||
IGNORE = ['csound', 'threadshare', 'gtk4']
|
||||
|
||||
outdir = sys.argv[1]
|
||||
|
||||
|
|
|
@ -3,4 +3,4 @@ source ./ci/env.sh
|
|||
set -e
|
||||
export CARGO_HOME='/usr/local/cargo'
|
||||
|
||||
cargo install cargo-c --version 0.9.12+cargo-0.64
|
||||
cargo install cargo-c --version 0.9.14+cargo-0.66
|
||||
|
|
|
@ -17,11 +17,18 @@ function Run-Tests {
|
|||
param (
|
||||
$Features
|
||||
)
|
||||
$local_exclude = $exclude_crates;
|
||||
|
||||
# In this case the plugin will pull x11/wayland features
|
||||
# which will fail to build on windows.
|
||||
if (($Features -eq '--all-features') -or ($Features -eq '')) {
|
||||
$local_exclude += @("--exclude", "gst-plugin-gtk4")
|
||||
}
|
||||
|
||||
Write-Host "Features: $Features"
|
||||
Write-Host "Exclude string: $exclude_crates"
|
||||
Write-Host "Exclude string: $local_exclude"
|
||||
|
||||
cargo build --color=always --workspace $exclude_crates --all-targets $Features
|
||||
cargo build --color=always --workspace $local_exclude --all-targets $Features
|
||||
|
||||
if (!$?) {
|
||||
Write-Host "Build failed"
|
||||
|
@ -29,7 +36,7 @@ function Run-Tests {
|
|||
}
|
||||
|
||||
$env:G_DEBUG="fatal_warnings"
|
||||
cargo test --no-fail-fast --color=always --workspace $exclude_crates --all-targets $Features
|
||||
cargo test --no-fail-fast --color=always --workspace $local_exclude --all-targets $Features
|
||||
|
||||
if (!$?) {
|
||||
Write-Host "Tests failed"
|
||||
|
|
64
deny.toml
64
deny.toml
|
@ -9,10 +9,13 @@ ignore = [
|
|||
"RUSTSEC-2021-0059",
|
||||
"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",
|
||||
# https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/256
|
||||
"RUSTSEC-2022-0048",
|
||||
]
|
||||
|
||||
[licenses]
|
||||
|
@ -55,12 +58,6 @@ wildcards = "allow"
|
|||
name = "time"
|
||||
version = "0.1"
|
||||
|
||||
# ignore duplicated textwrap dependency because clap depends on an old version
|
||||
# https://github.com/clap-rs/clap/pull/1994
|
||||
[[bans.skip]]
|
||||
name = "textwrap"
|
||||
version = "0.11"
|
||||
|
||||
# ignore duplicated rustc_version dependency because rav1e depends on an old version
|
||||
[[bans.skip]]
|
||||
name = "rustc_version"
|
||||
|
@ -69,31 +66,13 @@ version = "0.3"
|
|||
name = "semver"
|
||||
version = "0.11"
|
||||
|
||||
# ignore duplicated system-deps dependency because dav1d depends on an old version
|
||||
[[bans.skip]]
|
||||
name = "system-deps"
|
||||
version = "3"
|
||||
|
||||
[[bans.skip]]
|
||||
name = "version-compare"
|
||||
version = "0.0"
|
||||
|
||||
[[bans.skip]]
|
||||
name = "cfg-expr"
|
||||
version = "0.7"
|
||||
|
||||
# ignore duplicated crc dependency because ffv1 depends on an old version
|
||||
# https://github.com/rust-av/ffv1/issues/21
|
||||
[[bans.skip]]
|
||||
name = "crc"
|
||||
version = "1.8"
|
||||
|
||||
# ignore duplicated heck dependency because various crates depend on an old version
|
||||
[[bans.skip]]
|
||||
name = "heck"
|
||||
version = "0.3"
|
||||
|
||||
# ignore duplicated sha-1/digest/block-buffer dependencies because librespot depends on an old version
|
||||
# Ignore various duplicated dependencies because librespot depends on an old versions
|
||||
[[bans.skip]]
|
||||
name = "block-buffer"
|
||||
version = "0.9"
|
||||
|
@ -103,6 +82,12 @@ version = "0.9"
|
|||
[[bans.skip]]
|
||||
name = "sha-1"
|
||||
version = "0.9"
|
||||
[[bans.skip]]
|
||||
name = "env_logger"
|
||||
version = "0.9"
|
||||
[[bans.skip]]
|
||||
name = "hmac"
|
||||
version = "0.11"
|
||||
|
||||
# ignore duplicated wasi dependency because various crates depends on an old version
|
||||
[[bans.skip]]
|
||||
|
@ -114,6 +99,35 @@ version = "0.10"
|
|||
name = "spin"
|
||||
version = "0.5"
|
||||
|
||||
# cookie_store depends on older idna
|
||||
# https://github.com/pfernie/cookie_store/commit/b9c710f45550c5c8997f18a83e6fcc5998cf1726
|
||||
[[bans.skip]]
|
||||
name = "idna"
|
||||
version = "0.2"
|
||||
|
||||
# image depends on older gif
|
||||
# https://github.com/image-rs/image/pull/1826
|
||||
[[bans.skip]]
|
||||
name = "gif"
|
||||
version = "0.11"
|
||||
|
||||
# field-offset and nix depend on an older memoffset
|
||||
# https://github.com/Diggsey/rust-field-offset/pull/23
|
||||
# https://github.com/nix-rust/nix/pull/1885
|
||||
[[bans.skip]]
|
||||
name = "memoffset"
|
||||
version = "0.6"
|
||||
|
||||
# Various crates depend on an older version of hermit-abi
|
||||
[[bans.skip]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1"
|
||||
|
||||
# Various crates depend on an older version of base64
|
||||
[[bans.skip]]
|
||||
name = "base64"
|
||||
version = "0.13"
|
||||
|
||||
[sources]
|
||||
unknown-registry = "deny"
|
||||
unknown-git = "deny"
|
||||
|
|
|
@ -9,7 +9,7 @@ if meson.is_cross_build()
|
|||
subdir_done()
|
||||
endif
|
||||
|
||||
if static_build
|
||||
if default_library == 'static'
|
||||
if get_option('doc').enabled()
|
||||
error('Documentation enabled but not supported when building statically.')
|
||||
endif
|
||||
|
|
|
@ -165,6 +165,18 @@
|
|||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"stats": {
|
||||
"blurb": "Various statistics",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "stats, num-uploads-started=(int)0, num-uploads-completed=(int)0, num-bytes-uploaded=(int)0;",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstStructure",
|
||||
"writable": false
|
||||
}
|
||||
},
|
||||
"rank": "none"
|
||||
|
@ -1070,7 +1082,7 @@
|
|||
"writable": true
|
||||
}
|
||||
},
|
||||
"rank": "primary + 1"
|
||||
"rank": "primary"
|
||||
}
|
||||
},
|
||||
"filename": "gstdav1d",
|
||||
|
@ -1589,7 +1601,8 @@
|
|||
"sink": {
|
||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
"presence": "always",
|
||||
"type": "GstFMP4MuxPad"
|
||||
},
|
||||
"src": {
|
||||
"caps": "video/quicktime:\n variant: cmaf\n",
|
||||
|
@ -1615,9 +1628,10 @@
|
|||
"long-name": "DASHMP4Mux",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
|
||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
"presence": "always",
|
||||
"type": "GstFMP4MuxPad"
|
||||
},
|
||||
"src": {
|
||||
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
||||
|
@ -1643,9 +1657,10 @@
|
|||
"long-name": "ISOFMP4Mux",
|
||||
"pad-templates": {
|
||||
"sink_%%u": {
|
||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
|
||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "sink",
|
||||
"presence": "request"
|
||||
"presence": "request",
|
||||
"type": "GstFMP4MuxPad"
|
||||
},
|
||||
"src": {
|
||||
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
||||
|
@ -1673,7 +1688,8 @@
|
|||
"sink_%%u": {
|
||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nimage/jpeg:\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-alaw:\n channels: [ 1, 2 ]\n rate: [ 1, 2147483647 ]\naudio/x-mulaw:\n channels: [ 1, 2 ]\n rate: [ 1, 2147483647 ]\naudio/x-adpcm:\n layout: g726\n channels: 1\n rate: 8000\n bitrate: { (int)16000, (int)24000, (int)32000, (int)40000 }\napplication/x-onvif-metadata:\n parsed: true\n",
|
||||
"direction": "sink",
|
||||
"presence": "request"
|
||||
"presence": "request",
|
||||
"type": "GstFMP4MuxPad"
|
||||
},
|
||||
"src": {
|
||||
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
||||
|
@ -1752,6 +1768,20 @@
|
|||
"type": "guint64",
|
||||
"writable": true
|
||||
},
|
||||
"movie-timescale": {
|
||||
"blurb": "Timescale to use for the movie (units per second, 0 is automatic)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "-1",
|
||||
"min": "0",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"write-mehd": {
|
||||
"blurb": "Write movie extends header box with the duration at the end of the stream (needs a header-update-mode enabled)",
|
||||
"conditionally-available": false,
|
||||
|
@ -1797,6 +1827,33 @@
|
|||
"value": "2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"GstFMP4MuxPad": {
|
||||
"hierarchy": [
|
||||
"GstFMP4MuxPad",
|
||||
"GstAggregatorPad",
|
||||
"GstPad",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"kind": "object",
|
||||
"properties": {
|
||||
"trak-timescale": {
|
||||
"blurb": "Timescale to use for the track (units per second, 0 is automatic)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "-1",
|
||||
"min": "0",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"package": "gst-plugin-fmp4",
|
||||
|
@ -1895,7 +1952,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: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: { 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",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
}
|
||||
|
@ -2452,6 +2509,293 @@
|
|||
"tracers": {},
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
},
|
||||
"livesync": {
|
||||
"description": "Livesync Plugin",
|
||||
"elements": {
|
||||
"livesync": {
|
||||
"author": "Jan Alexander Steffens (heftig) <jan.steffens@ltnglobal.com>",
|
||||
"description": "Outputs livestream, inserting gap frames when input lags",
|
||||
"hierarchy": [
|
||||
"GstLiveSync",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Filter",
|
||||
"long-name": "Live Synchronizer",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "ANY",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
},
|
||||
"src": {
|
||||
"caps": "ANY",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"drop": {
|
||||
"blurb": "Number of incoming frames dropped",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": false
|
||||
},
|
||||
"duplicate": {
|
||||
"blurb": "Number of outgoing frames duplicated",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": false
|
||||
},
|
||||
"in": {
|
||||
"blurb": "Number of incoming frames accepted",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": false
|
||||
},
|
||||
"late-threshold": {
|
||||
"blurb": "Maximum time spent (in nanoseconds) before accepting one late buffer; -1 = never",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "2000000000",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": true
|
||||
},
|
||||
"latency": {
|
||||
"blurb": "Additional latency to allow upstream to take longer to produce buffers for the current position (in nanoseconds)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "9223372036854775807",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": true
|
||||
},
|
||||
"out": {
|
||||
"blurb": "Number of outgoing frames produced",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": false
|
||||
},
|
||||
"single-segment": {
|
||||
"blurb": "Timestamp buffers and eat segments so as to appear as one segment",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "false",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
}
|
||||
},
|
||||
"rank": "none"
|
||||
}
|
||||
},
|
||||
"filename": "gstlivesync",
|
||||
"license": "MPL",
|
||||
"other-types": {},
|
||||
"package": "gst-plugin-livesync",
|
||||
"source": "gst-plugin-livesync",
|
||||
"tracers": {},
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
},
|
||||
"mp4": {
|
||||
"description": "GStreamer Rust MP4 Plugin",
|
||||
"elements": {
|
||||
"isomp4mux": {
|
||||
"author": "Sebastian Dröge <sebastian@centricular.com>",
|
||||
"description": "ISO MP4 muxer",
|
||||
"hierarchy": [
|
||||
"GstISOMP4Mux",
|
||||
"GstRsMP4Mux",
|
||||
"GstAggregator",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Codec/Muxer",
|
||||
"pad-templates": {
|
||||
"sink_%%u": {
|
||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n bit-depth-luma: { (uint)8, (uint)10, (uint)12 }\nbit-depth-chroma: { (uint)8, (uint)10, (uint)12 }\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-opus:\nchannel-mapping-family: [ 0, 255 ]\n channels: [ 1, 8 ]\n rate: [ 1, 2147483647 ]\n",
|
||||
"direction": "sink",
|
||||
"presence": "request",
|
||||
"type": "GstRsMP4MuxPad"
|
||||
},
|
||||
"src": {
|
||||
"caps": "video/quicktime:\n variant: iso\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "marginal"
|
||||
},
|
||||
"onvifmp4mux": {
|
||||
"author": "Sebastian Dröge <sebastian@centricular.com>",
|
||||
"description": "ONVIF MP4 muxer",
|
||||
"hierarchy": [
|
||||
"GstONVIFMP4Mux",
|
||||
"GstRsMP4Mux",
|
||||
"GstAggregator",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Codec/Muxer",
|
||||
"pad-templates": {
|
||||
"sink_%%u": {
|
||||
"caps": "video/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\nimage/jpeg:\n width: [ 1, 65535 ]\n height: [ 1, 65535 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\naudio/x-alaw:\n channels: [ 1, 2 ]\n rate: [ 1, 2147483647 ]\naudio/x-mulaw:\n channels: [ 1, 2 ]\n rate: [ 1, 2147483647 ]\naudio/x-adpcm:\n layout: g726\n channels: 1\n rate: 8000\n bitrate: { (int)16000, (int)24000, (int)32000, (int)40000 }\napplication/x-onvif-metadata:\n parsed: true\n",
|
||||
"direction": "sink",
|
||||
"presence": "request",
|
||||
"type": "GstRsMP4MuxPad"
|
||||
},
|
||||
"src": {
|
||||
"caps": "video/quicktime:\n variant: iso\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "marginal"
|
||||
}
|
||||
},
|
||||
"filename": "gstmp4",
|
||||
"license": "MPL",
|
||||
"other-types": {
|
||||
"GstRsMP4Mux": {
|
||||
"hierarchy": [
|
||||
"GstRsMP4Mux",
|
||||
"GstAggregator",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"kind": "object",
|
||||
"properties": {
|
||||
"interleave-bytes": {
|
||||
"blurb": "Interleave between streams in bytes",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": true
|
||||
},
|
||||
"interleave-time": {
|
||||
"blurb": "Interleave between streams in nanoseconds",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "500000000",
|
||||
"max": "18446744073709551615",
|
||||
"min": "0",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "guint64",
|
||||
"writable": true
|
||||
},
|
||||
"movie-timescale": {
|
||||
"blurb": "Timescale to use for the movie (units per second, 0 is automatic)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "-1",
|
||||
"min": "0",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"GstRsMP4MuxPad": {
|
||||
"hierarchy": [
|
||||
"GstRsMP4MuxPad",
|
||||
"GstAggregatorPad",
|
||||
"GstPad",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"kind": "object",
|
||||
"properties": {
|
||||
"trak-timescale": {
|
||||
"blurb": "Timescale to use for the track (units per second, 0 is automatic)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "-1",
|
||||
"min": "0",
|
||||
"mutable": "ready",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"package": "gst-plugin-mp4",
|
||||
"source": "gst-plugin-mp4",
|
||||
"tracers": {},
|
||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
},
|
||||
"ndi": {
|
||||
"description": "GStreamer NewTek NDI Plugin",
|
||||
"elements": {
|
||||
|
@ -3628,6 +3972,22 @@
|
|||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"voice-activity-threshold": {
|
||||
"blurb": "Threshold of the voice activity detector below which to mute the output",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "1",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gfloat",
|
||||
"writable": true
|
||||
}
|
||||
},
|
||||
"rank": "none"
|
||||
},
|
||||
"ebur128level": {
|
||||
|
@ -4762,40 +5122,6 @@
|
|||
"rsonvif": {
|
||||
"description": "GStreamer Rust ONVIF Plugin",
|
||||
"elements": {
|
||||
"onvifaggregator": {
|
||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||
"description": "ONVIF metadata aggregator",
|
||||
"hierarchy": [
|
||||
"GstOnvifAggregator",
|
||||
"GstAggregator",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Aggregator",
|
||||
"long-name": "ONVIF metadata aggregator",
|
||||
"pad-templates": {
|
||||
"media": {
|
||||
"caps": "ANY",
|
||||
"direction": "sink",
|
||||
"presence": "always",
|
||||
"type": "GstAggregatorPad"
|
||||
},
|
||||
"meta": {
|
||||
"caps": "application/x-onvif-metadata:\n parsed: true\n",
|
||||
"direction": "sink",
|
||||
"presence": "always",
|
||||
"type": "GstAggregatorPad"
|
||||
},
|
||||
"src": {
|
||||
"caps": "ANY",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "primary"
|
||||
},
|
||||
"onvifmetadatacombiner": {
|
||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||
"description": "ONVIF metadata combiner",
|
||||
|
@ -4926,73 +5252,6 @@
|
|||
},
|
||||
"rank": "none"
|
||||
},
|
||||
"onvifoverlay": {
|
||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||
"description": "Renders ONVIF analytics meta over raw video frames",
|
||||
"hierarchy": [
|
||||
"GstOnvifOverlay",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Video/Overlay",
|
||||
"long-name": "ONVIF 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_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",
|
||||
"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_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",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"font-desc": {
|
||||
"blurb": "Pango font description of font to be used for rendering",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "monospace 12",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
}
|
||||
},
|
||||
"rank": "primary"
|
||||
},
|
||||
"rtponvifdepay": {
|
||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||
"description": "ONVIF metadata RTP depayloader",
|
||||
"hierarchy": [
|
||||
"GstOnvifDepay",
|
||||
"GstRTPBaseDepayload",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Depayloader/Network/RTP",
|
||||
"long-name": "ONVIF metadata RTP depayloader",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "application/x-rtp:\n media: application\n payload: [ 96, 127 ]\n clock-rate: 90000\n encoding-name: VND.ONVIF.METADATA\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
},
|
||||
"src": {
|
||||
"caps": "application/x-onvif-metadata:\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "primary"
|
||||
},
|
||||
"rtponvifmetadatadepay": {
|
||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||
"description": "ONVIF metadata RTP depayloader",
|
||||
|
@ -5046,33 +5305,6 @@
|
|||
}
|
||||
},
|
||||
"rank": "primary"
|
||||
},
|
||||
"rtponvifpay": {
|
||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||
"description": "ONVIF metadata RTP payloader",
|
||||
"hierarchy": [
|
||||
"GstOnvifPay",
|
||||
"GstRTPBasePayload",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Payloader/Network/RTP",
|
||||
"long-name": "ONVIF metadata RTP payloader",
|
||||
"pad-templates": {
|
||||
"sink": {
|
||||
"caps": "application/x-onvif-metadata:\n",
|
||||
"direction": "sink",
|
||||
"presence": "always"
|
||||
},
|
||||
"src": {
|
||||
"caps": "application/x-rtp:\n media: application\n payload: [ 96, 127 ]\n clock-rate: 90000\n encoding-name: VND.ONVIF.METADATA\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"rank": "primary"
|
||||
}
|
||||
},
|
||||
"filename": "gstrsonvif",
|
||||
|
@ -6201,6 +6433,32 @@
|
|||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"n-previous": {
|
||||
"blurb": "The number of previous text buffers to display before the current one",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "-1",
|
||||
"min": "0",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"previous-attributes": {
|
||||
"blurb": "Pango span attributes to set on the previous text",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "size=\"smaller\"",
|
||||
"mutable": "playing",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"separator": {
|
||||
"blurb": "Text inserted between each text buffers",
|
||||
"conditionally-available": false,
|
||||
|
@ -6427,6 +6685,106 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ts-audiotestsrc": {
|
||||
"author": "François Laignel <fengalin@free.fr>",
|
||||
"description": "Thread-sharing audio test source",
|
||||
"hierarchy": [
|
||||
"TsAudioTestSrc",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"klass": "Source/Test",
|
||||
"pad-templates": {
|
||||
"src": {
|
||||
"caps": "audio/x-raw:\n rate: [ 8000, 2147483646 ]\n channels: [ 1, 2147483646 ]\n layout: interleaved\n format: S16LE\n",
|
||||
"direction": "src",
|
||||
"presence": "always"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"buffer-duration": {
|
||||
"blurb": "Buffer duration in ms",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "10",
|
||||
"max": "-1",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"context": {
|
||||
"blurb": "Context name to share threads with",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"context-wait": {
|
||||
"blurb": "Throttle poll loop to run at most once every this many ms",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "0",
|
||||
"max": "1000",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"do-timestamp": {
|
||||
"blurb": "Apply current stream time to buffers",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "false",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"is-live": {
|
||||
"blurb": "Whether to act as a live source",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "false",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"num-buffers": {
|
||||
"blurb": "Number of buffers to output before sending EOS (-1 = unlimited)",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "-1",
|
||||
"max": "2147483647",
|
||||
"min": "-1",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gint",
|
||||
"writable": true
|
||||
}
|
||||
},
|
||||
"rank": "none"
|
||||
},
|
||||
"ts-input-selector": {
|
||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||
"description": "Simple input selector element",
|
||||
|
@ -7581,8 +7939,145 @@
|
|||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
},
|
||||
"webrtchttp": {
|
||||
"description": "GStreamer WebRTC Plugin for WebRTC HTTP protocols (WHIP)",
|
||||
"description": "GStreamer WebRTC Plugin for WebRTC HTTP protocols (WHIP/WHEP)",
|
||||
"elements": {
|
||||
"whepsrc": {
|
||||
"author": "Sanchayan Maity <sanchayan@asymptotic.io>",
|
||||
"description": "A bin to stream media using the WebRTC HTTP Egress Protocol (WHEP)",
|
||||
"hierarchy": [
|
||||
"GstWhepSrc",
|
||||
"GstBin",
|
||||
"GstElement",
|
||||
"GstObject",
|
||||
"GInitiallyUnowned",
|
||||
"GObject"
|
||||
],
|
||||
"interfaces": [
|
||||
"GstChildProxy"
|
||||
],
|
||||
"klass": "Source/Network/WebRTC",
|
||||
"long-name": "WHEP Source Bin",
|
||||
"pad-templates": {
|
||||
"src_%%u": {
|
||||
"caps": "application/x-rtp:\n",
|
||||
"direction": "src",
|
||||
"presence": "sometimes"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"audio-caps": {
|
||||
"blurb": "Governs what audio codecs will be proposed",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "audio/x-opus",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstCaps",
|
||||
"writable": true
|
||||
},
|
||||
"auth-token": {
|
||||
"blurb": "Authentication token to use, will be sent in the HTTP Header as 'Bearer <auth-token>'",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "NULL",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"ice-transport-policy": {
|
||||
"blurb": "The policy to apply for ICE transport",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "all (0)",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstRsWebRTCICETransportPolicy",
|
||||
"writable": true
|
||||
},
|
||||
"stun-server": {
|
||||
"blurb": "The STUN server of the form stun://hostname:port",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "NULL",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"timeout": {
|
||||
"blurb": "Value in seconds to timeout WHEP endpoint requests (0 = No timeout).",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "15",
|
||||
"max": "3600",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"turn-server": {
|
||||
"blurb": "The TURN server of the form turn(s)://username:password@host:port.",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "NULL",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"use-link-headers": {
|
||||
"blurb": "Use link headers to configure STUN/TURN servers if present in WHEP endpoint response.",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "false",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gboolean",
|
||||
"writable": true
|
||||
},
|
||||
"video-caps": {
|
||||
"blurb": "Governs what video codecs will be proposed",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "video/x-vp8; video/x-h264; video/x-vp9; video/x-h265; video/x-av1",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstCaps",
|
||||
"writable": true
|
||||
},
|
||||
"whep-endpoint": {
|
||||
"blurb": "The WHEP server endpoint to POST SDP offer to.",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "NULL",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
}
|
||||
},
|
||||
"rank": "marginal"
|
||||
},
|
||||
"whipsink": {
|
||||
"author": "Taruntej Kanakamalla <taruntej@asymptotic.io>",
|
||||
"description": "A bin to stream media using the WebRTC HTTP Ingestion Protocol (WHIP)",
|
||||
|
@ -7619,8 +8114,58 @@
|
|||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"ice-transport-policy": {
|
||||
"blurb": "The policy to apply for ICE transport",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "all (0)",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "GstRsWebRTCICETransportPolicy",
|
||||
"writable": true
|
||||
},
|
||||
"stun-server": {
|
||||
"blurb": "The STUN server of the form stun://hostname:port",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "NULL",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"timeout": {
|
||||
"blurb": "Value in seconds to timeout WHIP endpoint requests (0 = No timeout).",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "15",
|
||||
"max": "3600",
|
||||
"min": "0",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "guint",
|
||||
"writable": true
|
||||
},
|
||||
"turn-server": {
|
||||
"blurb": "The TURN server of the form turn(s)://username:password@host:port.",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
"controllable": false,
|
||||
"default": "NULL",
|
||||
"mutable": "null",
|
||||
"readable": true,
|
||||
"type": "gchararray",
|
||||
"writable": true
|
||||
},
|
||||
"use-link-headers": {
|
||||
"blurb": "Use link headers to configure ice-servers from the WHIP server response to the POST or OPTIONS request.\n If set to TRUE and the WHIP server returns valid ice-servers,\n this property overrides the ice-servers values set using the stun-server and turn-server properties.",
|
||||
"blurb": "Use link headers to configure ice-servers from the WHIP server response to the POST request.\n If set to TRUE and the WHIP server returns valid ice-servers,\n this property overrides the ice-servers values set using the stun-server and turn-server properties.",
|
||||
"conditionally-available": false,
|
||||
"construct": false,
|
||||
"construct-only": false,
|
||||
|
@ -7649,7 +8194,23 @@
|
|||
},
|
||||
"filename": "gstwebrtchttp",
|
||||
"license": "MPL",
|
||||
"other-types": {},
|
||||
"other-types": {
|
||||
"GstRsWebRTCICETransportPolicy": {
|
||||
"kind": "enum",
|
||||
"values": [
|
||||
{
|
||||
"desc": "All: get both STUN and TURN candidate pairs",
|
||||
"name": "all",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"desc": "Relay: get only TURN candidate pairs",
|
||||
"name": "relay",
|
||||
"value": "1"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"package": "gst-plugin-webrtchttp",
|
||||
"source": "gst-plugin-webrtchttp",
|
||||
"tracers": {},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-file"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
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.63"
|
|||
|
||||
[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.19", version = "0.19.1" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
once_cell = "1.0"
|
||||
|
||||
[lib]
|
||||
|
@ -20,7 +20,7 @@ crate-type = ["cdylib", "rlib"]
|
|||
path = "src/lib.rs"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { path="../../version-helper" }
|
||||
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -74,7 +74,7 @@ impl FileLocation {
|
|||
.parent()
|
||||
.expect("FileSink::set_location `location` with filename but without a parent")
|
||||
.to_owned();
|
||||
if parent_dir.is_relative() && parent_dir.components().next() == None {
|
||||
if parent_dir.is_relative() && parent_dir.components().next().is_none() {
|
||||
// `location` only contains the filename
|
||||
// need to specify "." for `canonicalize` to resolve the actual path
|
||||
parent_dir = PathBuf::from(".");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-sodium"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
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.63"
|
||||
|
||||
[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.19", version = "0.19" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
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.19"
|
||||
version = "0.19"
|
||||
package="gstreamer-check"
|
||||
|
||||
[dev-dependencies.gst-app]
|
||||
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
||||
branch = "0.19"
|
||||
version = "0.19"
|
||||
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 = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -53,7 +53,7 @@ struct Keys {
|
|||
|
||||
impl Keys {
|
||||
fn from_file(file: &Path) -> Result<Self, Box<dyn Error>> {
|
||||
let f = File::open(&file)?;
|
||||
let f = File::open(file)?;
|
||||
serde_json::from_reader(f).map_err(From::from)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ struct Keys {
|
|||
|
||||
impl Keys {
|
||||
fn from_file(file: &Path) -> Result<Self, Box<dyn Error>> {
|
||||
let f = File::open(&file)?;
|
||||
let f = File::open(file)?;
|
||||
serde_json::from_reader(f).map_err(From::from)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -498,7 +498,7 @@ impl Decrypter {
|
|||
gst::debug!(CAT, obj: pad, "Requested offset: {}", offset);
|
||||
gst::debug!(CAT, obj: pad, "Requested size: {}", requested_size);
|
||||
|
||||
let chunk_index = offset as u64 / block_size as u64;
|
||||
let chunk_index = offset / block_size as u64;
|
||||
gst::debug!(CAT, obj: pad, "Stream Block index: {}", chunk_index);
|
||||
|
||||
let pull_offset = offset - (chunk_index * block_size as u64);
|
||||
|
|
|
@ -75,7 +75,7 @@ fn test_pipeline() {
|
|||
};
|
||||
|
||||
let filesrc = gst::ElementFactory::make("filesrc")
|
||||
.property("location", &input_path.to_str().unwrap())
|
||||
.property("location", input_path.to_str().unwrap())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-threadshare"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||
license = "LGPL-2.1-or-later"
|
||||
description = "GStreamer Threadshare Plugin"
|
||||
|
@ -10,14 +10,15 @@ rust-version = "1.63"
|
|||
|
||||
[dependencies]
|
||||
async-task = "4.3.0"
|
||||
concurrent-queue = "1.2.2"
|
||||
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-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" }
|
||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16" }
|
||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
gst-net = { package = "gstreamer-net", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
once_cell = "1"
|
||||
pin-project-lite = "0.2.0"
|
||||
polling = "2.2.0"
|
||||
|
@ -33,8 +34,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.19", version = "0.19" }
|
||||
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
|
||||
[lib]
|
||||
name = "gstthreadshare"
|
||||
|
@ -42,7 +43,7 @@ crate-type = ["cdylib", "rlib"]
|
|||
path = "src/lib.rs"
|
||||
|
||||
[[example]]
|
||||
name = "benchmark"
|
||||
name = "ts-benchmark"
|
||||
path = "examples/benchmark.rs"
|
||||
|
||||
[[example]]
|
||||
|
@ -58,7 +59,7 @@ name = "ts-standalone"
|
|||
path = "examples/standalone/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { path="../../version-helper" }
|
||||
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||
cc = "1.0.38"
|
||||
pkg-config = "0.3.15"
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ fn main() {
|
|||
};
|
||||
let is_rtp = args.len() > 6 && (args[6] == "rtp");
|
||||
|
||||
let rtp_caps = gst::Caps::builder("audio/x-rtp")
|
||||
let rtp_caps = gst::Caps::builder("application/x-rtp")
|
||||
.field("media", "audio")
|
||||
.field("payload", 8i32)
|
||||
.field("clock-rate", 8000)
|
||||
|
@ -97,7 +97,7 @@ fn main() {
|
|||
"udpsrc" => {
|
||||
let source = gst::ElementFactory::make("udpsrc")
|
||||
.name(format!("source-{}", i).as_str())
|
||||
.property("port", 40000i32 + i as i32)
|
||||
.property("port", 5004i32 + i as i32)
|
||||
.property("retrieve-sender-address", false)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
@ -108,7 +108,7 @@ fn main() {
|
|||
let context = build_context();
|
||||
let source = gst::ElementFactory::make("ts-udpsrc")
|
||||
.name(format!("source-{}", i).as_str())
|
||||
.property("port", 40000i32 + i as i32)
|
||||
.property("port", 5004i32 + i as i32)
|
||||
.property("context", &context)
|
||||
.property("context-wait", wait)
|
||||
.build()
|
||||
|
@ -158,7 +158,7 @@ fn main() {
|
|||
let context = build_context();
|
||||
let source = gst::ElementFactory::make("ts-tonesrc")
|
||||
.name(format!("source-{}", i).as_str())
|
||||
.property("samples-per-buffer", (wait as u32) * 8000 / 1000)
|
||||
.property("samples-per-buffer", wait * 8000 / 1000)
|
||||
.property("context", &context)
|
||||
.property("context-wait", wait)
|
||||
.build()
|
||||
|
@ -184,22 +184,7 @@ fn main() {
|
|||
pipeline.add_many(elements).unwrap();
|
||||
gst::Element::link_many(elements).unwrap();
|
||||
} else {
|
||||
let queue = if let Some(context) = context {
|
||||
let queue = gst::ElementFactory::make("ts-queue")
|
||||
.name(format!("queue-{}", i).as_str())
|
||||
.property("context", &context)
|
||||
.property("context-wait", wait)
|
||||
.build()
|
||||
.unwrap();
|
||||
queue
|
||||
} else {
|
||||
gst::ElementFactory::make("queue")
|
||||
.name(format!("queue-{}", i).as_str())
|
||||
.build()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let elements = &[&source, &queue, &sink];
|
||||
let elements = &[&source, &sink];
|
||||
pipeline.add_many(elements).unwrap();
|
||||
gst::Element::link_many(elements).unwrap();
|
||||
}
|
||||
|
@ -268,14 +253,14 @@ fn main() {
|
|||
let elapsed = init.elapsed();
|
||||
gst::info!(
|
||||
CAT,
|
||||
"{:>6.2} / s / stream",
|
||||
"Thrpt: {:>6.2}",
|
||||
total_count * 1_000.0 / elapsed.as_millis() as f32
|
||||
);
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
gst::info!(
|
||||
CAT,
|
||||
"{:>6.2}% parked",
|
||||
"Parked: {:>6.2}%",
|
||||
(ctx_0.parked_duration() - parked_init).as_nanos() as f32 * 100.0
|
||||
/ elapsed.as_nanos() as f32
|
||||
);
|
||||
|
|
66
generic/threadshare/examples/standalone/args/clap_args.rs
Normal file
66
generic/threadshare/examples/standalone/args/clap_args.rs
Normal file
|
@ -0,0 +1,66 @@
|
|||
use super::super::CAT;
|
||||
use clap::Parser;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
||||
pub enum Sink {
|
||||
/// Item handling in PadHandler with async Mutex
|
||||
AsyncMutex,
|
||||
/// Item handling in PadHandler with sync Mutex
|
||||
SyncMutex,
|
||||
/// Item handling in runtime::Task
|
||||
Task,
|
||||
}
|
||||
|
||||
impl Sink {
|
||||
pub fn element_name(self) -> &'static str {
|
||||
use super::super::sink;
|
||||
use Sink::*;
|
||||
match self {
|
||||
AsyncMutex => sink::ASYNC_MUTEX_ELEMENT_NAME,
|
||||
SyncMutex => sink::SYNC_MUTEX_ELEMENT_NAME,
|
||||
Task => sink::TASK_ELEMENT_NAME,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version)]
|
||||
#[clap(
|
||||
about = "Standalone pipeline threadshare runtime test. Use `GST_DEBUG=ts-standalone*:4` for stats"
|
||||
)]
|
||||
pub struct Args {
|
||||
/// Parallel streams to process.
|
||||
#[clap(short, long, default_value_t = 5000)]
|
||||
pub streams: u32,
|
||||
|
||||
/// Threadshare groups.
|
||||
#[clap(short, long, default_value_t = 2)]
|
||||
pub groups: u32,
|
||||
|
||||
/// Threadshare Context wait in ms (max throttling duration).
|
||||
#[clap(short, long, default_value_t = 20)]
|
||||
pub wait: u32,
|
||||
|
||||
/// Buffer push period in ms.
|
||||
#[clap(short, long, default_value_t = 20)]
|
||||
pub push_period: u32,
|
||||
|
||||
/// Number of buffers per stream to output before sending EOS (-1 = unlimited).
|
||||
#[clap(short, long, default_value_t = 5000)]
|
||||
pub num_buffers: i32,
|
||||
|
||||
/// The Sink variant to use.
|
||||
#[clap(long, value_enum, default_value_t = Sink::SyncMutex)]
|
||||
pub sink: Sink,
|
||||
|
||||
/// Disables statistics logging.
|
||||
#[clap(short, long)]
|
||||
pub disable_stats_log: bool,
|
||||
}
|
||||
|
||||
pub fn args() -> Args {
|
||||
let args = Args::parse();
|
||||
gst::info!(CAT, "{:?}", args);
|
||||
|
||||
args
|
||||
}
|
47
generic/threadshare/examples/standalone/args/default_args.rs
Normal file
47
generic/threadshare/examples/standalone/args/default_args.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use super::super::CAT;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct SyncMutexSink;
|
||||
|
||||
impl SyncMutexSink {
|
||||
pub fn element_name(self) -> &'static str {
|
||||
super::super::sink::SYNC_MUTEX_ELEMENT_NAME
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Args {
|
||||
pub streams: u32,
|
||||
pub groups: u32,
|
||||
pub wait: u32,
|
||||
pub push_period: u32,
|
||||
pub num_buffers: i32,
|
||||
pub sink: SyncMutexSink,
|
||||
pub disable_stats_log: bool,
|
||||
}
|
||||
|
||||
impl Default for Args {
|
||||
fn default() -> Self {
|
||||
Args {
|
||||
streams: 5000,
|
||||
groups: 2,
|
||||
wait: 20,
|
||||
push_period: 20,
|
||||
num_buffers: 5000,
|
||||
sink: SyncMutexSink,
|
||||
disable_stats_log: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn args() -> Args {
|
||||
if std::env::args().len() > 1 {
|
||||
gst::warning!(CAT, "Ignoring command line arguments");
|
||||
gst::warning!(CAT, "Build with `--features=clap`");
|
||||
}
|
||||
|
||||
let args = Args::default();
|
||||
gst::warning!(CAT, "{:?}", args);
|
||||
|
||||
args
|
||||
}
|
9
generic/threadshare/examples/standalone/args/mod.rs
Normal file
9
generic/threadshare/examples/standalone/args/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
#[cfg(not(feature = "clap"))]
|
||||
mod default_args;
|
||||
#[cfg(not(feature = "clap"))]
|
||||
pub use default_args::*;
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
mod clap_args;
|
||||
#[cfg(feature = "clap")]
|
||||
pub use clap_args::*;
|
19
generic/threadshare/examples/standalone/macros.rs
Normal file
19
generic/threadshare/examples/standalone/macros.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
macro_rules! debug_or_trace {
|
||||
($cat:expr, $raise_log_level:expr, $qual:ident: $obj:expr, $rest:tt $(,)?) => {
|
||||
if $raise_log_level {
|
||||
gst::debug!($cat, $qual: $obj, $rest);
|
||||
} else {
|
||||
gst::trace!($cat, $qual: $obj, $rest);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! log_or_trace {
|
||||
($cat:expr, $raise_log_level:expr, $qual:ident: $obj:expr, $rest:tt $(,)?) => {
|
||||
if $raise_log_level {
|
||||
gst::log!($cat, $qual: $obj, $rest);
|
||||
} else {
|
||||
gst::trace!($cat, $qual: $obj, $rest);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,12 +1,21 @@
|
|||
use gst::glib;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
mod args;
|
||||
use args::*;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
mod sink;
|
||||
mod src;
|
||||
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"ts-standalone-test-main",
|
||||
"ts-standalone-main",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Thread-sharing standalone test main"),
|
||||
)
|
||||
|
@ -14,7 +23,9 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|||
|
||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
src::register(plugin)?;
|
||||
sink::register(plugin)?;
|
||||
sink::async_mutex::register(plugin)?;
|
||||
sink::sync_mutex::register(plugin)?;
|
||||
sink::task::register(plugin)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -31,91 +42,6 @@ gst::plugin_define!(
|
|||
env!("BUILD_REL_DATE")
|
||||
);
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
use clap::Parser;
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version)]
|
||||
#[clap(
|
||||
about = "Standalone pipeline threadshare runtime test. Use `GST_DEBUG=ts-standalone*:4` for stats"
|
||||
)]
|
||||
struct Args {
|
||||
/// Parallel streams to process.
|
||||
#[clap(short, long, default_value_t = 5000)]
|
||||
streams: u32,
|
||||
|
||||
/// Threadshare groups.
|
||||
#[clap(short, long, default_value_t = 2)]
|
||||
groups: u32,
|
||||
|
||||
/// Threadshare Context wait in ms (max throttling duration).
|
||||
#[clap(short, long, default_value_t = 20)]
|
||||
wait: u32,
|
||||
|
||||
/// Buffer push period in ms.
|
||||
#[clap(short, long, default_value_t = 20)]
|
||||
push_period: u32,
|
||||
|
||||
/// Number of buffers per stream to output before sending EOS (-1 = unlimited).
|
||||
#[clap(short, long, default_value_t = 5000)]
|
||||
num_buffers: i32,
|
||||
|
||||
/// Disables statistics logging.
|
||||
#[clap(short, long)]
|
||||
disable_stats_log: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "clap"))]
|
||||
#[derive(Debug)]
|
||||
struct Args {
|
||||
streams: u32,
|
||||
groups: u32,
|
||||
wait: u32,
|
||||
push_period: u32,
|
||||
num_buffers: i32,
|
||||
disable_stats_log: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "clap"))]
|
||||
impl Default for Args {
|
||||
fn default() -> Self {
|
||||
Args {
|
||||
streams: 5000,
|
||||
groups: 2,
|
||||
wait: 20,
|
||||
push_period: 20,
|
||||
num_buffers: 5000,
|
||||
disable_stats_log: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn args() -> Args {
|
||||
#[cfg(feature = "clap")]
|
||||
let args = {
|
||||
let args = Args::parse();
|
||||
gst::info!(CAT, "{:?}", args);
|
||||
|
||||
args
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "clap"))]
|
||||
let args = {
|
||||
if std::env::args().len() > 1 {
|
||||
gst::warning!(CAT, "Ignoring command line arguments");
|
||||
gst::warning!(CAT, "Build with `--features=clap`");
|
||||
}
|
||||
|
||||
let args = Args::default();
|
||||
gst::warning!(CAT, "{:?}", args);
|
||||
|
||||
args
|
||||
};
|
||||
|
||||
args
|
||||
}
|
||||
|
||||
fn main() {
|
||||
use gst::prelude::*;
|
||||
use std::time::Instant;
|
||||
|
@ -133,8 +59,8 @@ fn main() {
|
|||
for i in 0..args.streams {
|
||||
let ctx_name = format!("standalone {}", i % args.groups);
|
||||
|
||||
let src = gst::ElementFactory::make("ts-standalone-test-src")
|
||||
.name(format!("src-{}", i).as_str())
|
||||
let src = gst::ElementFactory::make(src::ELEMENT_NAME)
|
||||
.name(format!("src-{i}").as_str())
|
||||
.property("context", &ctx_name)
|
||||
.property("context-wait", args.wait)
|
||||
.property("push-period", args.push_period)
|
||||
|
@ -142,16 +68,16 @@ fn main() {
|
|||
.build()
|
||||
.unwrap();
|
||||
|
||||
let sink = gst::ElementFactory::make("ts-standalone-test-sink")
|
||||
.name(format!("sink-{}", i).as_str())
|
||||
let sink = gst::ElementFactory::make(args.sink.element_name())
|
||||
.name(format!("sink-{i}").as_str())
|
||||
.property("context", &ctx_name)
|
||||
.property("context-wait", args.wait)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
if i == 0 {
|
||||
src.set_property("raise-log-level", true);
|
||||
sink.set_property("raise-log-level", true);
|
||||
src.set_property("main-elem", true);
|
||||
sink.set_property("main-elem", true);
|
||||
|
||||
if !args.disable_stats_log {
|
||||
// Don't use the last 5 secs in stats
|
||||
|
@ -179,30 +105,46 @@ fn main() {
|
|||
let l = glib::MainLoop::new(None, false);
|
||||
|
||||
let bus = pipeline.bus().unwrap();
|
||||
let terminated_count = Arc::new(AtomicU32::new(0));
|
||||
let pipeline_clone = pipeline.clone();
|
||||
let l_clone = l.clone();
|
||||
bus.add_watch(move |_, msg| {
|
||||
use gst::MessageView;
|
||||
|
||||
match msg.view() {
|
||||
MessageView::Eos(_) => {
|
||||
// Actually, we don't post EOS (see sinks impl).
|
||||
gst::info!(CAT, "Received eos");
|
||||
l_clone.quit();
|
||||
|
||||
glib::Continue(false)
|
||||
}
|
||||
MessageView::Error(err) => {
|
||||
MessageView::Error(msg) => {
|
||||
if let gst::MessageView::Error(msg) = msg.message().view() {
|
||||
if msg.error().matches(gst::LibraryError::Shutdown) {
|
||||
if terminated_count.fetch_add(1, Ordering::SeqCst) == args.streams - 1 {
|
||||
gst::info!(CAT, "Received all shutdown requests");
|
||||
l_clone.quit();
|
||||
|
||||
return glib::Continue(false);
|
||||
} else {
|
||||
return glib::Continue(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gst::error!(
|
||||
CAT,
|
||||
"Error from {:?}: {} ({:?})",
|
||||
err.src().map(|s| s.path_string()),
|
||||
err.error(),
|
||||
err.debug()
|
||||
msg.src().map(|s| s.path_string()),
|
||||
msg.error(),
|
||||
msg.debug()
|
||||
);
|
||||
l_clone.quit();
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
|
||||
glib::Continue(true)
|
||||
glib::Continue(false)
|
||||
}
|
||||
_ => glib::Continue(true),
|
||||
}
|
||||
})
|
||||
.expect("Failed to add bus watch");
|
||||
|
||||
|
|
334
generic/threadshare/examples/standalone/sink/async_mutex/imp.rs
Normal file
334
generic/threadshare/examples/standalone/sink/async_mutex/imp.rs
Normal file
|
@ -0,0 +1,334 @@
|
|||
// Copyright (C) 2022 François Laignel <fengalin@free.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures::prelude::*;
|
||||
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
use gst::EventView;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use gstthreadshare::runtime::executor::block_on_or_add_sub_task;
|
||||
use gstthreadshare::runtime::{prelude::*, PadSink};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use super::super::{Settings, Stats, CAT};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct PadSinkHandlerInner {
|
||||
is_flushing: bool,
|
||||
is_main_elem: bool,
|
||||
last_dts: Option<gst::ClockTime>,
|
||||
segment_start: Option<gst::ClockTime>,
|
||||
stats: Option<Box<Stats>>,
|
||||
}
|
||||
|
||||
impl PadSinkHandlerInner {
|
||||
fn handle_buffer(
|
||||
&mut self,
|
||||
elem: &super::AsyncMutexSink,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<(), gst::FlowError> {
|
||||
if self.is_flushing {
|
||||
log_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: elem,
|
||||
"Discarding {buffer:?} (flushing)"
|
||||
);
|
||||
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: elem, "Received {buffer:?}");
|
||||
|
||||
let dts = buffer
|
||||
.dts()
|
||||
.expect("Buffer without dts")
|
||||
.checked_sub(self.segment_start.expect("Buffer without Time Segment"))
|
||||
.expect("dts before Segment start");
|
||||
|
||||
if let Some(last_dts) = self.last_dts {
|
||||
let cur_ts = elem.current_running_time().unwrap();
|
||||
let latency: Duration = (cur_ts - dts).into();
|
||||
let interval: Duration = (dts - last_dts).into();
|
||||
|
||||
if let Some(stats) = self.stats.as_mut() {
|
||||
stats.add_buffer(latency, interval);
|
||||
}
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: elem, "o latency {latency:.2?}");
|
||||
debug_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: elem,
|
||||
"o interval {interval:.2?}",
|
||||
);
|
||||
}
|
||||
|
||||
self.last_dts = Some(dts);
|
||||
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: elem, "Buffer processed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct AsyncPadSinkHandler(Arc<futures::lock::Mutex<PadSinkHandlerInner>>);
|
||||
|
||||
impl PadSinkHandler for AsyncPadSinkHandler {
|
||||
type ElementImpl = AsyncMutexSink;
|
||||
|
||||
fn sink_chain(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::AsyncMutexSink,
|
||||
buffer: gst::Buffer,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
async move {
|
||||
if self.0.lock().await.handle_buffer(&elem, buffer).is_err() {
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_event_serialized(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::AsyncMutexSink,
|
||||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
async move {
|
||||
match event.view() {
|
||||
EventView::Eos(_) => {
|
||||
{
|
||||
let mut inner = self.0.lock().await;
|
||||
debug_or_trace!(CAT, inner.is_main_elem, obj: elem, "EOS");
|
||||
inner.is_flushing = true;
|
||||
}
|
||||
|
||||
// When each element sends its own EOS message,
|
||||
// it takes ages for the pipeline to process all of them.
|
||||
// Let's just post an error message and let main shuts down
|
||||
// after all streams have posted this message.
|
||||
let _ = elem
|
||||
.post_message(gst::message::Error::new(gst::LibraryError::Shutdown, "EOS"));
|
||||
}
|
||||
EventView::FlushStop(_) => {
|
||||
self.0.lock().await.is_flushing = false;
|
||||
}
|
||||
EventView::Segment(evt) => {
|
||||
if let Some(time_seg) = evt.segment().downcast_ref::<gst::ClockTime>() {
|
||||
self.0.lock().await.segment_start = time_seg.start();
|
||||
}
|
||||
}
|
||||
EventView::SinkMessage(evt) => {
|
||||
let _ = elem.post_message(evt.message());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_event(self, _pad: &gst::Pad, _imp: &AsyncMutexSink, event: gst::Event) -> bool {
|
||||
if let EventView::FlushStart(..) = event.view() {
|
||||
block_on_or_add_sub_task(async move { self.0.lock().await.is_flushing = true });
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncPadSinkHandler {
|
||||
fn prepare(&self, is_main_elem: bool, stats: Option<Stats>) {
|
||||
futures::executor::block_on(async move {
|
||||
let mut inner = self.0.lock().await;
|
||||
inner.is_main_elem = is_main_elem;
|
||||
inner.stats = stats.map(Box::new);
|
||||
});
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
futures::executor::block_on(async move {
|
||||
let mut inner = self.0.lock().await;
|
||||
|
||||
inner.is_flushing = false;
|
||||
inner.last_dts = None;
|
||||
|
||||
if let Some(stats) = inner.stats.as_mut() {
|
||||
stats.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
futures::executor::block_on(async move {
|
||||
let mut inner = self.0.lock().await;
|
||||
inner.is_flushing = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AsyncMutexSink {
|
||||
sink_pad: PadSink,
|
||||
sink_pad_handler: AsyncPadSinkHandler,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
impl AsyncMutexSink {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Preparing");
|
||||
let stats = if settings.logs_stats {
|
||||
Some(Stats::new(
|
||||
settings.max_buffers,
|
||||
settings.push_period + settings.context_wait / 2,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.sink_pad_handler.prepare(settings.is_main_elem, stats);
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopping");
|
||||
self.sink_pad_handler.stop();
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Starting");
|
||||
self.sink_pad_handler.start();
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for AsyncMutexSink {
|
||||
const NAME: &'static str = "TsStandaloneAsyncMutexSink";
|
||||
type Type = super::AsyncMutexSink;
|
||||
type ParentType = gst::Element;
|
||||
|
||||
fn with_class(klass: &Self::Class) -> Self {
|
||||
let sink_pad_handler = AsyncPadSinkHandler::default();
|
||||
Self {
|
||||
sink_pad: PadSink::new(
|
||||
gst::Pad::from_template(&klass.pad_template("sink").unwrap(), Some("sink")),
|
||||
sink_pad_handler.clone(),
|
||||
),
|
||||
sink_pad_handler,
|
||||
settings: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for AsyncMutexSink {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(Settings::properties);
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
self.settings.lock().unwrap().set_property(id, value, pspec);
|
||||
}
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
self.settings.lock().unwrap().property(id, pspec)
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.add_pad(self.sink_pad.gst_pad()).unwrap();
|
||||
obj.set_element_flags(gst::ElementFlags::SINK);
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for AsyncMutexSink {}
|
||||
|
||||
impl ElementImpl for AsyncMutexSink {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"Thread-sharing standalone test async mutex sink",
|
||||
"Sink/Test",
|
||||
"Thread-sharing standalone test async mutex sink",
|
||||
"François Laignel <fengalin@free.fr>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let caps = gst::Caps::new_any();
|
||||
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
|
||||
fn change_state(
|
||||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
self.prepare().map_err(|err| {
|
||||
self.post_error_message(err);
|
||||
gst::StateChangeError
|
||||
})?;
|
||||
}
|
||||
gst::StateChange::ReadyToPaused => {
|
||||
self.start().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
gst::StateChange::PausedToReady => {
|
||||
self.stop().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.parent_change_state(transition)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct AsyncMutexSink(ObjectSubclass<imp::AsyncMutexSink>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
super::ASYNC_MUTEX_ELEMENT_NAME,
|
||||
gst::Rank::None,
|
||||
AsyncMutexSink::static_type(),
|
||||
)
|
||||
}
|
|
@ -1,864 +0,0 @@
|
|||
// Copyright (C) 2022 François Laignel <fengalin@free.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures::prelude::*;
|
||||
use futures::stream::Peekable;
|
||||
|
||||
use gst::error_msg;
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
use gst::EventView;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use gstthreadshare::runtime::prelude::*;
|
||||
use gstthreadshare::runtime::{Context, PadSink, Task};
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::task::Poll;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"ts-standalone-test-sink",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Thread-sharing standalone test sink"),
|
||||
)
|
||||
});
|
||||
|
||||
const DEFAULT_CONTEXT: &str = "";
|
||||
const DEFAULT_CONTEXT_WAIT: Duration = Duration::from_millis(20);
|
||||
const DEFAULT_PUSH_PERIOD: Duration = Duration::from_millis(20);
|
||||
const DEFAULT_MAX_BUFFERS: i32 = 50 * (100 - 25);
|
||||
|
||||
const LOG_PERIOD: Duration = Duration::from_secs(20);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Settings {
|
||||
context: String,
|
||||
context_wait: Duration,
|
||||
raise_log_level: bool,
|
||||
logs_stats: bool,
|
||||
push_period: Duration,
|
||||
max_buffers: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings {
|
||||
context: DEFAULT_CONTEXT.into(),
|
||||
context_wait: DEFAULT_CONTEXT_WAIT,
|
||||
raise_log_level: false,
|
||||
logs_stats: false,
|
||||
push_period: DEFAULT_PUSH_PERIOD,
|
||||
max_buffers: Some(DEFAULT_MAX_BUFFERS as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum StreamItem {
|
||||
Buffer(gst::Buffer),
|
||||
Event(gst::Event),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TestSinkPadHandler;
|
||||
|
||||
impl PadSinkHandler for TestSinkPadHandler {
|
||||
type ElementImpl = TestSink;
|
||||
|
||||
fn sink_chain(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::TestSink,
|
||||
buffer: gst::Buffer,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
let sender = elem.imp().clone_item_sender();
|
||||
async move {
|
||||
if sender.send_async(StreamItem::Buffer(buffer)).await.is_err() {
|
||||
gst::debug!(CAT, obj: elem, "Flushing");
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_chain_list(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::TestSink,
|
||||
list: gst::BufferList,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
let sender = elem.imp().clone_item_sender();
|
||||
async move {
|
||||
for buffer in list.iter_owned() {
|
||||
if sender.send_async(StreamItem::Buffer(buffer)).await.is_err() {
|
||||
gst::debug!(CAT, obj: elem, "Flushing");
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_event_serialized(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::TestSink,
|
||||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
let sender = elem.imp().clone_item_sender();
|
||||
async move {
|
||||
if let EventView::FlushStop(_) = event.view() {
|
||||
let imp = elem.imp();
|
||||
return imp.task.flush_stop().await_maybe_on_context().is_ok();
|
||||
} else if sender.send_async(StreamItem::Event(event)).await.is_err() {
|
||||
gst::debug!(CAT, obj: elem, "Flushing");
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_event(self, _pad: &gst::Pad, imp: &TestSink, event: gst::Event) -> bool {
|
||||
if let EventView::FlushStart(..) = event.view() {
|
||||
return imp.task.flush_start().await_maybe_on_context().is_ok();
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Stats {
|
||||
must_log: bool,
|
||||
ramp_up_instant: Option<Instant>,
|
||||
log_start_instant: Option<Instant>,
|
||||
last_delta_instant: Option<Instant>,
|
||||
max_buffers: Option<f32>,
|
||||
buffer_count: f32,
|
||||
buffer_count_delta: f32,
|
||||
latency_sum: f32,
|
||||
latency_square_sum: f32,
|
||||
latency_sum_delta: f32,
|
||||
latency_square_sum_delta: f32,
|
||||
latency_min: Duration,
|
||||
latency_min_delta: Duration,
|
||||
latency_max: Duration,
|
||||
latency_max_delta: Duration,
|
||||
interval_sum: f32,
|
||||
interval_square_sum: f32,
|
||||
interval_sum_delta: f32,
|
||||
interval_square_sum_delta: f32,
|
||||
interval_min: Duration,
|
||||
interval_min_delta: Duration,
|
||||
interval_max: Duration,
|
||||
interval_max_delta: Duration,
|
||||
interval_late_warn: Duration,
|
||||
interval_late_count: f32,
|
||||
interval_late_count_delta: f32,
|
||||
#[cfg(feature = "tuning")]
|
||||
parked_duration_init: Duration,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
fn start(&mut self) {
|
||||
if !self.must_log {
|
||||
return;
|
||||
}
|
||||
|
||||
self.buffer_count = 0.0;
|
||||
self.buffer_count_delta = 0.0;
|
||||
self.latency_sum = 0.0;
|
||||
self.latency_square_sum = 0.0;
|
||||
self.latency_sum_delta = 0.0;
|
||||
self.latency_square_sum_delta = 0.0;
|
||||
self.latency_min = Duration::MAX;
|
||||
self.latency_min_delta = Duration::MAX;
|
||||
self.latency_max = Duration::ZERO;
|
||||
self.latency_max_delta = Duration::ZERO;
|
||||
self.interval_sum = 0.0;
|
||||
self.interval_square_sum = 0.0;
|
||||
self.interval_sum_delta = 0.0;
|
||||
self.interval_square_sum_delta = 0.0;
|
||||
self.interval_min = Duration::MAX;
|
||||
self.interval_min_delta = Duration::MAX;
|
||||
self.interval_max = Duration::ZERO;
|
||||
self.interval_max_delta = Duration::ZERO;
|
||||
self.interval_late_count = 0.0;
|
||||
self.interval_late_count_delta = 0.0;
|
||||
self.last_delta_instant = None;
|
||||
self.log_start_instant = None;
|
||||
|
||||
self.ramp_up_instant = Some(Instant::now());
|
||||
gst::info!(CAT, "First stats logs in {:2?}", 2 * LOG_PERIOD);
|
||||
}
|
||||
|
||||
fn is_active(&mut self) -> bool {
|
||||
if !self.must_log {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(ramp_up_instant) = self.ramp_up_instant {
|
||||
if ramp_up_instant.elapsed() < LOG_PERIOD {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.ramp_up_instant = None;
|
||||
gst::info!(CAT, "Ramp up complete. Stats logs in {:2?}", LOG_PERIOD);
|
||||
self.log_start_instant = Some(Instant::now());
|
||||
self.last_delta_instant = self.log_start_instant;
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
{
|
||||
self.parked_duration_init = Context::current().unwrap().parked_duration();
|
||||
}
|
||||
}
|
||||
|
||||
use std::cmp::Ordering::*;
|
||||
match self.max_buffers.opt_cmp(self.buffer_count) {
|
||||
Some(Equal) => {
|
||||
self.log_global();
|
||||
self.buffer_count += 1.0;
|
||||
false
|
||||
}
|
||||
Some(Less) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_buffer(&mut self, latency: Duration, interval: Duration) {
|
||||
if !self.is_active() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.buffer_count += 1.0;
|
||||
self.buffer_count_delta += 1.0;
|
||||
|
||||
// Latency
|
||||
let latency_f32 = latency.as_nanos() as f32;
|
||||
let latency_square = latency_f32.powi(2);
|
||||
|
||||
self.latency_sum += latency_f32;
|
||||
self.latency_square_sum += latency_square;
|
||||
self.latency_min = self.latency_min.min(latency);
|
||||
self.latency_max = self.latency_max.max(latency);
|
||||
|
||||
self.latency_sum_delta += latency_f32;
|
||||
self.latency_square_sum_delta += latency_square;
|
||||
self.latency_min_delta = self.latency_min_delta.min(latency);
|
||||
self.latency_max_delta = self.latency_max_delta.max(latency);
|
||||
|
||||
// Interval
|
||||
let interval_f32 = interval.as_nanos() as f32;
|
||||
let interval_square = interval_f32.powi(2);
|
||||
|
||||
self.interval_sum += interval_f32;
|
||||
self.interval_square_sum += interval_square;
|
||||
self.interval_min = self.interval_min.min(interval);
|
||||
self.interval_max = self.interval_max.max(interval);
|
||||
|
||||
self.interval_sum_delta += interval_f32;
|
||||
self.interval_square_sum_delta += interval_square;
|
||||
self.interval_min_delta = self.interval_min_delta.min(interval);
|
||||
self.interval_max_delta = self.interval_max_delta.max(interval);
|
||||
|
||||
if interval > self.interval_late_warn {
|
||||
self.interval_late_count += 1.0;
|
||||
self.interval_late_count_delta += 1.0;
|
||||
}
|
||||
|
||||
let delta_duration = match self.last_delta_instant {
|
||||
Some(last_delta) => last_delta.elapsed(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if delta_duration < LOG_PERIOD {
|
||||
return;
|
||||
}
|
||||
|
||||
self.last_delta_instant = Some(Instant::now());
|
||||
|
||||
gst::info!(CAT, "Delta stats:");
|
||||
let interval_mean = self.interval_sum_delta / self.buffer_count_delta;
|
||||
let interval_std_dev = f32::sqrt(
|
||||
self.interval_square_sum_delta / self.buffer_count_delta - interval_mean.powi(2),
|
||||
);
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o interval: mean {:4.2?} σ {:4.1?} [{:4.1?}, {:4.1?}]",
|
||||
Duration::from_nanos(interval_mean as u64),
|
||||
Duration::from_nanos(interval_std_dev as u64),
|
||||
self.interval_min_delta,
|
||||
self.interval_max_delta,
|
||||
);
|
||||
|
||||
if self.interval_late_count_delta > f32::EPSILON {
|
||||
gst::warning!(
|
||||
CAT,
|
||||
"o {:5.2}% late buffers",
|
||||
100f32 * self.interval_late_count_delta / self.buffer_count_delta
|
||||
);
|
||||
}
|
||||
|
||||
self.interval_sum_delta = 0.0;
|
||||
self.interval_square_sum_delta = 0.0;
|
||||
self.interval_min_delta = Duration::MAX;
|
||||
self.interval_max_delta = Duration::ZERO;
|
||||
self.interval_late_count_delta = 0.0;
|
||||
|
||||
let latency_mean = self.latency_sum_delta / self.buffer_count_delta;
|
||||
let latency_std_dev = f32::sqrt(
|
||||
self.latency_square_sum_delta / self.buffer_count_delta - latency_mean.powi(2),
|
||||
);
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o latency: mean {:4.2?} σ {:4.1?} [{:4.1?}, {:4.1?}]",
|
||||
Duration::from_nanos(latency_mean as u64),
|
||||
Duration::from_nanos(latency_std_dev as u64),
|
||||
self.latency_min_delta,
|
||||
self.latency_max_delta,
|
||||
);
|
||||
|
||||
self.latency_sum_delta = 0.0;
|
||||
self.latency_square_sum_delta = 0.0;
|
||||
self.latency_min_delta = Duration::MAX;
|
||||
self.latency_max_delta = Duration::ZERO;
|
||||
|
||||
self.buffer_count_delta = 0.0;
|
||||
}
|
||||
|
||||
fn log_global(&mut self) {
|
||||
if self.buffer_count < 1.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let _log_start = if let Some(log_start) = self.log_start_instant {
|
||||
log_start
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
gst::info!(CAT, "Global stats:");
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
{
|
||||
let duration = _log_start.elapsed();
|
||||
let parked_duration =
|
||||
Context::current().unwrap().parked_duration() - self.parked_duration_init;
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o parked: {parked_duration:4.2?} ({:5.2?}%)",
|
||||
(parked_duration.as_nanos() as f32 * 100.0 / duration.as_nanos() as f32)
|
||||
);
|
||||
}
|
||||
|
||||
let interval_mean = self.interval_sum / self.buffer_count;
|
||||
let interval_std_dev =
|
||||
f32::sqrt(self.interval_square_sum / self.buffer_count - interval_mean.powi(2));
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o interval: mean {:4.2?} σ {:4.1?} [{:4.1?}, {:4.1?}]",
|
||||
Duration::from_nanos(interval_mean as u64),
|
||||
Duration::from_nanos(interval_std_dev as u64),
|
||||
self.interval_min,
|
||||
self.interval_max,
|
||||
);
|
||||
|
||||
if self.interval_late_count > f32::EPSILON {
|
||||
gst::warning!(
|
||||
CAT,
|
||||
"o {:5.2}% late buffers",
|
||||
100f32 * self.interval_late_count / self.buffer_count
|
||||
);
|
||||
}
|
||||
|
||||
let latency_mean = self.latency_sum / self.buffer_count;
|
||||
let latency_std_dev =
|
||||
f32::sqrt(self.latency_square_sum / self.buffer_count - latency_mean.powi(2));
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o latency: mean {:4.2?} σ {:4.1?} [{:4.1?}, {:4.1?}]",
|
||||
Duration::from_nanos(latency_mean as u64),
|
||||
Duration::from_nanos(latency_std_dev as u64),
|
||||
self.latency_min,
|
||||
self.latency_max,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct TestSinkTask {
|
||||
element: super::TestSink,
|
||||
raise_log_level: bool,
|
||||
last_dts: Option<gst::ClockTime>,
|
||||
item_receiver: Peekable<flume::r#async::RecvStream<'static, StreamItem>>,
|
||||
stats: Stats,
|
||||
segment: Option<gst::Segment>,
|
||||
}
|
||||
|
||||
impl TestSinkTask {
|
||||
fn new(element: &super::TestSink, item_receiver: flume::Receiver<StreamItem>) -> Self {
|
||||
TestSinkTask {
|
||||
element: element.clone(),
|
||||
raise_log_level: false,
|
||||
last_dts: None,
|
||||
item_receiver: item_receiver.into_stream().peekable(),
|
||||
stats: Stats::default(),
|
||||
segment: None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn flush(&mut self) {
|
||||
// Purge the channel
|
||||
while let Poll::Ready(Some(_item)) = futures::poll!(self.item_receiver.next()) {}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskImpl for TestSinkTask {
|
||||
type Item = StreamItem;
|
||||
|
||||
fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
let sink = self.element.imp();
|
||||
let settings = sink.settings.lock().unwrap();
|
||||
self.raise_log_level = settings.raise_log_level;
|
||||
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Preparing Task");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Preparing Task");
|
||||
}
|
||||
|
||||
self.stats.must_log = settings.logs_stats;
|
||||
self.stats.max_buffers = settings.max_buffers.map(|max_buffers| max_buffers as f32);
|
||||
self.stats.interval_late_warn = settings.push_period + settings.context_wait / 2;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async {
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Starting Task");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Starting Task");
|
||||
}
|
||||
|
||||
self.last_dts = None;
|
||||
self.stats.start();
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async {
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Stopping Task");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Stopping Task");
|
||||
}
|
||||
|
||||
self.flush().await;
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn try_next(&mut self) -> BoxFuture<'_, Result<StreamItem, gst::FlowError>> {
|
||||
async move {
|
||||
let item = self.item_receiver.next().await.unwrap();
|
||||
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Popped item");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Popped item");
|
||||
}
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn handle_item(&mut self, item: StreamItem) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||
async move {
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "Received {:?}", item);
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Received {:?}", item);
|
||||
}
|
||||
|
||||
match item {
|
||||
StreamItem::Buffer(buffer) => {
|
||||
let dts = self
|
||||
.segment
|
||||
.as_ref()
|
||||
.and_then(|segment| {
|
||||
segment
|
||||
.downcast_ref::<gst::format::Time>()
|
||||
.and_then(|segment| segment.to_running_time(buffer.dts()))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if let Some(last_dts) = self.last_dts {
|
||||
let cur_ts = self.element.current_running_time().unwrap();
|
||||
let latency: Duration = (cur_ts - dts).into();
|
||||
let interval: Duration = (dts - last_dts).into();
|
||||
|
||||
self.stats.add_buffer(latency, interval);
|
||||
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "o latency {:.2?}", latency);
|
||||
gst::debug!(CAT, obj: self.element, "o interval {:.2?}", interval);
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "o latency {:.2?}", latency);
|
||||
gst::trace!(CAT, obj: self.element, "o interval {:.2?}", interval);
|
||||
}
|
||||
}
|
||||
|
||||
self.last_dts = Some(dts);
|
||||
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Buffer processed");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Buffer processed");
|
||||
}
|
||||
}
|
||||
StreamItem::Event(event) => match event.view() {
|
||||
EventView::Eos(_) => {
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "EOS");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "EOS");
|
||||
}
|
||||
|
||||
let elem = self.element.clone();
|
||||
self.element.call_async(move |_| {
|
||||
let _ =
|
||||
elem.post_message(gst::message::Eos::builder().src(&elem).build());
|
||||
});
|
||||
|
||||
return Err(gst::FlowError::Eos);
|
||||
}
|
||||
EventView::Segment(e) => {
|
||||
self.segment = Some(e.segment().clone());
|
||||
}
|
||||
EventView::SinkMessage(e) => {
|
||||
let _ = self.element.post_message(e.message());
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestSink {
|
||||
sink_pad: PadSink,
|
||||
task: Task,
|
||||
item_sender: Mutex<Option<flume::Sender<StreamItem>>>,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
impl TestSink {
|
||||
#[track_caller]
|
||||
fn clone_item_sender(&self) -> flume::Sender<StreamItem> {
|
||||
self.item_sender.lock().unwrap().as_ref().unwrap().clone()
|
||||
}
|
||||
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Preparing");
|
||||
}
|
||||
|
||||
let context = {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
|
||||
Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
|
||||
error_msg!(
|
||||
gst::ResourceError::OpenWrite,
|
||||
["Failed to acquire Context: {}", err]
|
||||
)
|
||||
})?
|
||||
};
|
||||
|
||||
// Enable backpressure for items
|
||||
let (item_sender, item_receiver) = flume::bounded(0);
|
||||
let task_impl = TestSinkTask::new(&*self.obj(), item_receiver);
|
||||
self.task.prepare(task_impl, context).block_on()?;
|
||||
|
||||
*self.item_sender.lock().unwrap() = Some(item_sender);
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Prepared");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Prepared");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Unpreparing");
|
||||
}
|
||||
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Unprepared");
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Stopping");
|
||||
}
|
||||
|
||||
self.task.stop().block_on()?;
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Stopped");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Starting");
|
||||
}
|
||||
|
||||
self.task.start().block_on()?;
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Started");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for TestSink {
|
||||
const NAME: &'static str = "StandaloneTestSink";
|
||||
type Type = super::TestSink;
|
||||
type ParentType = gst::Element;
|
||||
|
||||
fn with_class(klass: &Self::Class) -> Self {
|
||||
Self {
|
||||
sink_pad: PadSink::new(
|
||||
gst::Pad::from_template(&klass.pad_template("sink").unwrap(), Some("sink")),
|
||||
TestSinkPadHandler,
|
||||
),
|
||||
task: Task::default(),
|
||||
item_sender: Default::default(),
|
||||
settings: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for TestSink {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecString::builder("context")
|
||||
.nick("Context")
|
||||
.blurb("Context name to share threads with")
|
||||
.default_value(Some(DEFAULT_CONTEXT))
|
||||
.build(),
|
||||
glib::ParamSpecUInt::builder("context-wait")
|
||||
.nick("Context Wait")
|
||||
.blurb("Throttle poll loop to run at most once every this many ms")
|
||||
.maximum(1000)
|
||||
.default_value(DEFAULT_CONTEXT_WAIT.as_millis() as u32)
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("raise-log-level")
|
||||
.nick("Raise log level")
|
||||
.blurb("Raises the log level so that this element stands out")
|
||||
.write_only()
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("logs-stats")
|
||||
.nick("Logs Stats")
|
||||
.blurb("Whether statistics should be logged")
|
||||
.write_only()
|
||||
.build(),
|
||||
glib::ParamSpecUInt::builder("push-period")
|
||||
.nick("Src buffer Push Period")
|
||||
.blurb("Push period used by `src` element (used for stats warnings)")
|
||||
.default_value(DEFAULT_PUSH_PERIOD.as_millis() as u32)
|
||||
.build(),
|
||||
glib::ParamSpecInt::builder("max-buffers")
|
||||
.nick("Max Buffers")
|
||||
.blurb("Number of buffers to count before stopping stats (-1 = unlimited)")
|
||||
.minimum(-1i32)
|
||||
.default_value(DEFAULT_MAX_BUFFERS)
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
match pspec.name() {
|
||||
"context" => {
|
||||
settings.context = value
|
||||
.get::<Option<String>>()
|
||||
.expect("type checked upstream")
|
||||
.unwrap_or_else(|| DEFAULT_CONTEXT.into());
|
||||
}
|
||||
"context-wait" => {
|
||||
settings.context_wait = Duration::from_millis(
|
||||
value.get::<u32>().expect("type checked upstream").into(),
|
||||
);
|
||||
}
|
||||
"raise-log-level" => {
|
||||
settings.raise_log_level = value.get::<bool>().expect("type checked upstream");
|
||||
}
|
||||
"logs-stats" => {
|
||||
let logs_stats = value.get().expect("type checked upstream");
|
||||
settings.logs_stats = logs_stats;
|
||||
}
|
||||
"push-period" => {
|
||||
settings.push_period = Duration::from_millis(
|
||||
value.get::<u32>().expect("type checked upstream").into(),
|
||||
);
|
||||
}
|
||||
"max-buffers" => {
|
||||
let value = value.get::<i32>().expect("type checked upstream");
|
||||
settings.max_buffers = if value > 0 { Some(value as u32) } else { None };
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
match pspec.name() {
|
||||
"context" => settings.context.to_value(),
|
||||
"context-wait" => (settings.context_wait.as_millis() as u32).to_value(),
|
||||
"raise-log-level" => settings.raise_log_level.to_value(),
|
||||
"push-period" => (settings.push_period.as_millis() as u32).to_value(),
|
||||
"max-buffers" => settings
|
||||
.max_buffers
|
||||
.and_then(|val| val.try_into().ok())
|
||||
.unwrap_or(-1i32)
|
||||
.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.add_pad(self.sink_pad.gst_pad()).unwrap();
|
||||
obj.set_element_flags(gst::ElementFlags::SINK);
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for TestSink {}
|
||||
|
||||
impl ElementImpl for TestSink {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"Thread-sharing standalone test sink",
|
||||
"Sink/Test",
|
||||
"Thread-sharing standalone test sink",
|
||||
"François Laignel <fengalin@free.fr>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let caps = gst::Caps::new_any();
|
||||
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
|
||||
fn change_state(
|
||||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
self.prepare().map_err(|err| {
|
||||
self.post_error_message(err);
|
||||
gst::StateChangeError
|
||||
})?;
|
||||
}
|
||||
gst::StateChange::ReadyToPaused => {
|
||||
self.start().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
gst::StateChange::PausedToReady => {
|
||||
self.stop().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
gst::StateChange::ReadyToNull => {
|
||||
self.unprepare();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.parent_change_state(transition)
|
||||
}
|
||||
}
|
|
@ -1,17 +1,22 @@
|
|||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
pub mod async_mutex;
|
||||
pub mod sync_mutex;
|
||||
pub mod task;
|
||||
|
||||
mod imp;
|
||||
mod settings;
|
||||
pub use settings::Settings;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct TestSink(ObjectSubclass<imp::TestSink>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
mod stats;
|
||||
pub use stats::Stats;
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"ts-standalone-test-sink",
|
||||
gst::Rank::None,
|
||||
TestSink::static_type(),
|
||||
pub const ASYNC_MUTEX_ELEMENT_NAME: &str = "ts-standalone-async-mutex-sink";
|
||||
pub const SYNC_MUTEX_ELEMENT_NAME: &str = "ts-standalone-sync-mutex-sink";
|
||||
pub const TASK_ELEMENT_NAME: &str = "ts-standalone-task-sink";
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"ts-standalone-sink",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Thread-sharing standalone test sink"),
|
||||
)
|
||||
}
|
||||
});
|
||||
|
|
115
generic/threadshare/examples/standalone/sink/settings.rs
Normal file
115
generic/threadshare/examples/standalone/sink/settings.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
const DEFAULT_CONTEXT: &str = "";
|
||||
const DEFAULT_CONTEXT_WAIT: Duration = Duration::from_millis(20);
|
||||
const DEFAULT_PUSH_PERIOD: Duration = Duration::from_millis(20);
|
||||
const DEFAULT_MAX_BUFFERS: i32 = 50 * (100 - 25);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Settings {
|
||||
pub context: String,
|
||||
pub context_wait: Duration,
|
||||
pub is_main_elem: bool,
|
||||
pub logs_stats: bool,
|
||||
pub push_period: Duration,
|
||||
pub max_buffers: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings {
|
||||
context: DEFAULT_CONTEXT.into(),
|
||||
context_wait: DEFAULT_CONTEXT_WAIT,
|
||||
is_main_elem: false,
|
||||
logs_stats: false,
|
||||
push_period: DEFAULT_PUSH_PERIOD,
|
||||
max_buffers: Some(DEFAULT_MAX_BUFFERS as u32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
pub fn properties() -> Vec<glib::ParamSpec> {
|
||||
vec![
|
||||
glib::ParamSpecString::builder("context")
|
||||
.nick("Context")
|
||||
.blurb("Context name to share threads with")
|
||||
.default_value(Some(DEFAULT_CONTEXT))
|
||||
.build(),
|
||||
glib::ParamSpecUInt::builder("context-wait")
|
||||
.nick("Context Wait")
|
||||
.blurb("Throttle poll loop to run at most once every this many ms")
|
||||
.maximum(1000)
|
||||
.default_value(DEFAULT_CONTEXT_WAIT.as_millis() as u32)
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("main-elem")
|
||||
.nick("Main Element")
|
||||
.blurb("Declare this element as the main one")
|
||||
.write_only()
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("logs-stats")
|
||||
.nick("Logs Stats")
|
||||
.blurb("Whether statistics should be logged")
|
||||
.write_only()
|
||||
.build(),
|
||||
glib::ParamSpecUInt::builder("push-period")
|
||||
.nick("Src buffer Push Period")
|
||||
.blurb("Push period used by `src` element (used for stats warnings)")
|
||||
.default_value(DEFAULT_PUSH_PERIOD.as_millis() as u32)
|
||||
.build(),
|
||||
glib::ParamSpecInt::builder("max-buffers")
|
||||
.nick("Max Buffers")
|
||||
.blurb("Number of buffers to count before stopping stats (-1 = unlimited)")
|
||||
.minimum(-1i32)
|
||||
.default_value(DEFAULT_MAX_BUFFERS)
|
||||
.build(),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn set_property(&mut self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
match pspec.name() {
|
||||
"context" => {
|
||||
self.context = value
|
||||
.get::<Option<String>>()
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| DEFAULT_CONTEXT.into());
|
||||
}
|
||||
"context-wait" => {
|
||||
self.context_wait = Duration::from_millis(value.get::<u32>().unwrap().into());
|
||||
}
|
||||
"main-elem" => {
|
||||
self.is_main_elem = value.get::<bool>().unwrap();
|
||||
}
|
||||
"logs-stats" => {
|
||||
let logs_stats = value.get().unwrap();
|
||||
self.logs_stats = logs_stats;
|
||||
}
|
||||
"push-period" => {
|
||||
self.push_period = Duration::from_millis(value.get::<u32>().unwrap().into());
|
||||
}
|
||||
"max-buffers" => {
|
||||
let value = value.get::<i32>().unwrap();
|
||||
self.max_buffers = if value > 0 { Some(value as u32) } else { None };
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"context" => self.context.to_value(),
|
||||
"context-wait" => (self.context_wait.as_millis() as u32).to_value(),
|
||||
"main-elem" => self.is_main_elem.to_value(),
|
||||
"push-period" => (self.push_period.as_millis() as u32).to_value(),
|
||||
"max-buffers" => self
|
||||
.max_buffers
|
||||
.and_then(|val| val.try_into().ok())
|
||||
.unwrap_or(-1i32)
|
||||
.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
270
generic/threadshare/examples/standalone/sink/stats.rs
Normal file
270
generic/threadshare/examples/standalone/sink/stats.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
use gst::prelude::*;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
use gstthreadshare::runtime::Context;
|
||||
|
||||
use super::CAT;
|
||||
|
||||
const LOG_PERIOD: Duration = Duration::from_secs(20);
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Stats {
|
||||
ramp_up_instant: Option<Instant>,
|
||||
log_start_instant: Option<Instant>,
|
||||
last_delta_instant: Option<Instant>,
|
||||
max_buffers: Option<f32>,
|
||||
buffer_count: f32,
|
||||
buffer_count_delta: f32,
|
||||
latency_sum: f32,
|
||||
latency_square_sum: f32,
|
||||
latency_sum_delta: f32,
|
||||
latency_square_sum_delta: f32,
|
||||
latency_min: Duration,
|
||||
latency_min_delta: Duration,
|
||||
latency_max: Duration,
|
||||
latency_max_delta: Duration,
|
||||
interval_sum: f32,
|
||||
interval_square_sum: f32,
|
||||
interval_sum_delta: f32,
|
||||
interval_square_sum_delta: f32,
|
||||
interval_min: Duration,
|
||||
interval_min_delta: Duration,
|
||||
interval_max: Duration,
|
||||
interval_max_delta: Duration,
|
||||
interval_late_warn: Duration,
|
||||
interval_late_count: f32,
|
||||
interval_late_count_delta: f32,
|
||||
#[cfg(feature = "tuning")]
|
||||
parked_duration_init: Duration,
|
||||
}
|
||||
|
||||
impl Stats {
|
||||
pub fn new(max_buffers: Option<u32>, interval_late_warn: Duration) -> Self {
|
||||
Stats {
|
||||
max_buffers: max_buffers.map(|max_buffers| max_buffers as f32),
|
||||
interval_late_warn,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(&mut self) {
|
||||
self.buffer_count = 0.0;
|
||||
self.buffer_count_delta = 0.0;
|
||||
self.latency_sum = 0.0;
|
||||
self.latency_square_sum = 0.0;
|
||||
self.latency_sum_delta = 0.0;
|
||||
self.latency_square_sum_delta = 0.0;
|
||||
self.latency_min = Duration::MAX;
|
||||
self.latency_min_delta = Duration::MAX;
|
||||
self.latency_max = Duration::ZERO;
|
||||
self.latency_max_delta = Duration::ZERO;
|
||||
self.interval_sum = 0.0;
|
||||
self.interval_square_sum = 0.0;
|
||||
self.interval_sum_delta = 0.0;
|
||||
self.interval_square_sum_delta = 0.0;
|
||||
self.interval_min = Duration::MAX;
|
||||
self.interval_min_delta = Duration::MAX;
|
||||
self.interval_max = Duration::ZERO;
|
||||
self.interval_max_delta = Duration::ZERO;
|
||||
self.interval_late_count = 0.0;
|
||||
self.interval_late_count_delta = 0.0;
|
||||
self.last_delta_instant = None;
|
||||
self.log_start_instant = None;
|
||||
|
||||
self.ramp_up_instant = Some(Instant::now());
|
||||
gst::info!(CAT, "First stats logs in {:2?}", 2 * LOG_PERIOD);
|
||||
}
|
||||
|
||||
pub fn is_active(&mut self) -> bool {
|
||||
if let Some(ramp_up_instant) = self.ramp_up_instant {
|
||||
if ramp_up_instant.elapsed() < LOG_PERIOD {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.ramp_up_instant = None;
|
||||
gst::info!(CAT, "Ramp up complete. Stats logs in {:2?}", LOG_PERIOD);
|
||||
self.log_start_instant = Some(Instant::now());
|
||||
self.last_delta_instant = self.log_start_instant;
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
{
|
||||
self.parked_duration_init = Context::current().unwrap().parked_duration();
|
||||
}
|
||||
}
|
||||
|
||||
use std::cmp::Ordering::*;
|
||||
match self.max_buffers.opt_cmp(self.buffer_count) {
|
||||
Some(Equal) => {
|
||||
self.log_global();
|
||||
self.buffer_count += 1.0;
|
||||
false
|
||||
}
|
||||
Some(Less) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_buffer(&mut self, latency: Duration, interval: Duration) {
|
||||
if !self.is_active() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.buffer_count += 1.0;
|
||||
self.buffer_count_delta += 1.0;
|
||||
|
||||
// Latency
|
||||
let latency_f32 = latency.as_nanos() as f32;
|
||||
let latency_square = latency_f32.powi(2);
|
||||
|
||||
self.latency_sum += latency_f32;
|
||||
self.latency_square_sum += latency_square;
|
||||
self.latency_min = self.latency_min.min(latency);
|
||||
self.latency_max = self.latency_max.max(latency);
|
||||
|
||||
self.latency_sum_delta += latency_f32;
|
||||
self.latency_square_sum_delta += latency_square;
|
||||
self.latency_min_delta = self.latency_min_delta.min(latency);
|
||||
self.latency_max_delta = self.latency_max_delta.max(latency);
|
||||
|
||||
// Interval
|
||||
let interval_f32 = interval.as_nanos() as f32;
|
||||
let interval_square = interval_f32.powi(2);
|
||||
|
||||
self.interval_sum += interval_f32;
|
||||
self.interval_square_sum += interval_square;
|
||||
self.interval_min = self.interval_min.min(interval);
|
||||
self.interval_max = self.interval_max.max(interval);
|
||||
|
||||
self.interval_sum_delta += interval_f32;
|
||||
self.interval_square_sum_delta += interval_square;
|
||||
self.interval_min_delta = self.interval_min_delta.min(interval);
|
||||
self.interval_max_delta = self.interval_max_delta.max(interval);
|
||||
|
||||
if interval > self.interval_late_warn {
|
||||
self.interval_late_count += 1.0;
|
||||
self.interval_late_count_delta += 1.0;
|
||||
}
|
||||
|
||||
let delta_duration = match self.last_delta_instant {
|
||||
Some(last_delta) => last_delta.elapsed(),
|
||||
None => return,
|
||||
};
|
||||
|
||||
if delta_duration < LOG_PERIOD {
|
||||
return;
|
||||
}
|
||||
|
||||
self.last_delta_instant = Some(Instant::now());
|
||||
|
||||
gst::info!(CAT, "Delta stats:");
|
||||
let interval_mean = self.interval_sum_delta / self.buffer_count_delta;
|
||||
let interval_std_dev = f32::sqrt(
|
||||
self.interval_square_sum_delta / self.buffer_count_delta - interval_mean.powi(2),
|
||||
);
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o interval: mean {:4.2?} σ {:4.1?} [{:4.1?}, {:4.1?}]",
|
||||
Duration::from_nanos(interval_mean as u64),
|
||||
Duration::from_nanos(interval_std_dev as u64),
|
||||
self.interval_min_delta,
|
||||
self.interval_max_delta,
|
||||
);
|
||||
|
||||
if self.interval_late_count_delta > f32::EPSILON {
|
||||
gst::warning!(
|
||||
CAT,
|
||||
"o {:5.2}% late buffers",
|
||||
100f32 * self.interval_late_count_delta / self.buffer_count_delta
|
||||
);
|
||||
}
|
||||
|
||||
self.interval_sum_delta = 0.0;
|
||||
self.interval_square_sum_delta = 0.0;
|
||||
self.interval_min_delta = Duration::MAX;
|
||||
self.interval_max_delta = Duration::ZERO;
|
||||
self.interval_late_count_delta = 0.0;
|
||||
|
||||
let latency_mean = self.latency_sum_delta / self.buffer_count_delta;
|
||||
let latency_std_dev = f32::sqrt(
|
||||
self.latency_square_sum_delta / self.buffer_count_delta - latency_mean.powi(2),
|
||||
);
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o latency: mean {:4.2?} σ {:4.1?} [{:4.1?}, {:4.1?}]",
|
||||
Duration::from_nanos(latency_mean as u64),
|
||||
Duration::from_nanos(latency_std_dev as u64),
|
||||
self.latency_min_delta,
|
||||
self.latency_max_delta,
|
||||
);
|
||||
|
||||
self.latency_sum_delta = 0.0;
|
||||
self.latency_square_sum_delta = 0.0;
|
||||
self.latency_min_delta = Duration::MAX;
|
||||
self.latency_max_delta = Duration::ZERO;
|
||||
|
||||
self.buffer_count_delta = 0.0;
|
||||
}
|
||||
|
||||
pub fn log_global(&mut self) {
|
||||
if self.buffer_count < 1.0 {
|
||||
return;
|
||||
}
|
||||
|
||||
let _log_start = if let Some(log_start) = self.log_start_instant {
|
||||
log_start
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
gst::info!(CAT, "Global stats:");
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
{
|
||||
let duration = _log_start.elapsed();
|
||||
let parked_duration =
|
||||
Context::current().unwrap().parked_duration() - self.parked_duration_init;
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o parked: {parked_duration:4.2?} ({:5.2?}%)",
|
||||
(parked_duration.as_nanos() as f32 * 100.0 / duration.as_nanos() as f32)
|
||||
);
|
||||
}
|
||||
|
||||
let interval_mean = self.interval_sum / self.buffer_count;
|
||||
let interval_std_dev =
|
||||
f32::sqrt(self.interval_square_sum / self.buffer_count - interval_mean.powi(2));
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o interval: mean {:4.2?} σ {:4.1?} [{:4.1?}, {:4.1?}]",
|
||||
Duration::from_nanos(interval_mean as u64),
|
||||
Duration::from_nanos(interval_std_dev as u64),
|
||||
self.interval_min,
|
||||
self.interval_max,
|
||||
);
|
||||
|
||||
if self.interval_late_count > f32::EPSILON {
|
||||
gst::warning!(
|
||||
CAT,
|
||||
"o {:5.2}% late buffers",
|
||||
100f32 * self.interval_late_count / self.buffer_count
|
||||
);
|
||||
}
|
||||
|
||||
let latency_mean = self.latency_sum / self.buffer_count;
|
||||
let latency_std_dev =
|
||||
f32::sqrt(self.latency_square_sum / self.buffer_count - latency_mean.powi(2));
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"o latency: mean {:4.2?} σ {:4.1?} [{:4.1?}, {:4.1?}]",
|
||||
Duration::from_nanos(latency_mean as u64),
|
||||
Duration::from_nanos(latency_std_dev as u64),
|
||||
self.latency_min,
|
||||
self.latency_max,
|
||||
);
|
||||
}
|
||||
}
|
327
generic/threadshare/examples/standalone/sink/sync_mutex/imp.rs
Normal file
327
generic/threadshare/examples/standalone/sink/sync_mutex/imp.rs
Normal file
|
@ -0,0 +1,327 @@
|
|||
// Copyright (C) 2022 François Laignel <fengalin@free.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures::prelude::*;
|
||||
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
use gst::EventView;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use gstthreadshare::runtime::{prelude::*, PadSink};
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::Duration;
|
||||
|
||||
use super::super::{Settings, Stats, CAT};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct PadSinkHandlerInner {
|
||||
is_flushing: bool,
|
||||
is_main_elem: bool,
|
||||
last_dts: Option<gst::ClockTime>,
|
||||
segment_start: Option<gst::ClockTime>,
|
||||
stats: Option<Box<Stats>>,
|
||||
}
|
||||
|
||||
impl PadSinkHandlerInner {
|
||||
fn handle_buffer(
|
||||
&mut self,
|
||||
elem: &super::DirectSink,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<(), gst::FlowError> {
|
||||
if self.is_flushing {
|
||||
log_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: elem,
|
||||
"Discarding {buffer:?} (flushing)"
|
||||
);
|
||||
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: elem, "Received {buffer:?}");
|
||||
|
||||
let dts = buffer
|
||||
.dts()
|
||||
.expect("Buffer without dts")
|
||||
.checked_sub(self.segment_start.expect("Buffer without Time Segment"))
|
||||
.expect("dts before Segment start");
|
||||
|
||||
if let Some(last_dts) = self.last_dts {
|
||||
let cur_ts = elem.current_running_time().unwrap();
|
||||
let latency: Duration = (cur_ts - dts).into();
|
||||
let interval: Duration = (dts - last_dts).into();
|
||||
|
||||
if let Some(stats) = self.stats.as_mut() {
|
||||
stats.add_buffer(latency, interval);
|
||||
}
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: elem, "o latency {latency:.2?}");
|
||||
debug_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: elem,
|
||||
"o interval {interval:.2?}",
|
||||
);
|
||||
}
|
||||
|
||||
self.last_dts = Some(dts);
|
||||
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: elem, "Buffer processed");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct SyncPadSinkHandler(Arc<Mutex<PadSinkHandlerInner>>);
|
||||
|
||||
impl PadSinkHandler for SyncPadSinkHandler {
|
||||
type ElementImpl = DirectSink;
|
||||
|
||||
fn sink_chain(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::DirectSink,
|
||||
buffer: gst::Buffer,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
async move {
|
||||
if self.0.lock().unwrap().handle_buffer(&elem, buffer).is_err() {
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_event_serialized(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::DirectSink,
|
||||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
async move {
|
||||
match event.view() {
|
||||
EventView::Eos(_) => {
|
||||
{
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
debug_or_trace!(CAT, inner.is_main_elem, obj: elem, "EOS");
|
||||
inner.is_flushing = true;
|
||||
}
|
||||
|
||||
// When each element sends its own EOS message,
|
||||
// it takes ages for the pipeline to process all of them.
|
||||
// Let's just post an error message and let main shuts down
|
||||
// after all streams have posted this message.
|
||||
let _ = elem
|
||||
.post_message(gst::message::Error::new(gst::LibraryError::Shutdown, "EOS"));
|
||||
}
|
||||
EventView::FlushStop(_) => {
|
||||
self.0.lock().unwrap().is_flushing = false;
|
||||
}
|
||||
EventView::Segment(evt) => {
|
||||
if let Some(time_seg) = evt.segment().downcast_ref::<gst::ClockTime>() {
|
||||
self.0.lock().unwrap().segment_start = time_seg.start();
|
||||
}
|
||||
}
|
||||
EventView::SinkMessage(evt) => {
|
||||
let _ = elem.post_message(evt.message());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_event(self, _pad: &gst::Pad, _imp: &DirectSink, event: gst::Event) -> bool {
|
||||
if let EventView::FlushStart(..) = event.view() {
|
||||
self.0.lock().unwrap().is_flushing = true;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl SyncPadSinkHandler {
|
||||
fn prepare(&self, is_main_elem: bool, stats: Option<Stats>) {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
inner.is_main_elem = is_main_elem;
|
||||
inner.stats = stats.map(Box::new);
|
||||
}
|
||||
|
||||
fn start(&self) {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
|
||||
inner.is_flushing = false;
|
||||
inner.last_dts = None;
|
||||
|
||||
if let Some(stats) = inner.stats.as_mut() {
|
||||
stats.start();
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&self) {
|
||||
let mut inner = self.0.lock().unwrap();
|
||||
inner.is_flushing = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DirectSink {
|
||||
sink_pad: PadSink,
|
||||
sink_pad_handler: SyncPadSinkHandler,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
impl DirectSink {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Preparing");
|
||||
let stats = if settings.logs_stats {
|
||||
Some(Stats::new(
|
||||
settings.max_buffers,
|
||||
settings.push_period + settings.context_wait / 2,
|
||||
))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.sink_pad_handler.prepare(settings.is_main_elem, stats);
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopping");
|
||||
self.sink_pad_handler.stop();
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Starting");
|
||||
self.sink_pad_handler.start();
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for DirectSink {
|
||||
const NAME: &'static str = "TsStandaloneDirectSink";
|
||||
type Type = super::DirectSink;
|
||||
type ParentType = gst::Element;
|
||||
|
||||
fn with_class(klass: &Self::Class) -> Self {
|
||||
let sink_pad_handler = SyncPadSinkHandler::default();
|
||||
Self {
|
||||
sink_pad: PadSink::new(
|
||||
gst::Pad::from_template(&klass.pad_template("sink").unwrap(), Some("sink")),
|
||||
sink_pad_handler.clone(),
|
||||
),
|
||||
sink_pad_handler,
|
||||
settings: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for DirectSink {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(Settings::properties);
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
self.settings.lock().unwrap().set_property(id, value, pspec);
|
||||
}
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
self.settings.lock().unwrap().property(id, pspec)
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.add_pad(self.sink_pad.gst_pad()).unwrap();
|
||||
obj.set_element_flags(gst::ElementFlags::SINK);
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for DirectSink {}
|
||||
|
||||
impl ElementImpl for DirectSink {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"Thread-sharing standalone test direct sink",
|
||||
"Sink/Test",
|
||||
"Thread-sharing standalone test direct sink",
|
||||
"François Laignel <fengalin@free.fr>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let caps = gst::Caps::new_any();
|
||||
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
|
||||
fn change_state(
|
||||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
self.prepare().map_err(|err| {
|
||||
self.post_error_message(err);
|
||||
gst::StateChangeError
|
||||
})?;
|
||||
}
|
||||
gst::StateChange::ReadyToPaused => {
|
||||
self.start().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
gst::StateChange::PausedToReady => {
|
||||
self.stop().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.parent_change_state(transition)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct DirectSink(ObjectSubclass<imp::DirectSink>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
super::SYNC_MUTEX_ELEMENT_NAME,
|
||||
gst::Rank::None,
|
||||
DirectSink::static_type(),
|
||||
)
|
||||
}
|
402
generic/threadshare/examples/standalone/sink/task/imp.rs
Normal file
402
generic/threadshare/examples/standalone/sink/task/imp.rs
Normal file
|
@ -0,0 +1,402 @@
|
|||
// Copyright (C) 2022 François Laignel <fengalin@free.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures::prelude::*;
|
||||
|
||||
use gst::error_msg;
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
use gst::EventView;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use gstthreadshare::runtime::prelude::*;
|
||||
use gstthreadshare::runtime::{Context, PadSink, Task};
|
||||
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::super::{Settings, Stats, CAT};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum StreamItem {
|
||||
Buffer(gst::Buffer),
|
||||
Event(gst::Event),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct TaskPadSinkHandler;
|
||||
|
||||
impl PadSinkHandler for TaskPadSinkHandler {
|
||||
type ElementImpl = TaskSink;
|
||||
|
||||
fn sink_chain(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::TaskSink,
|
||||
buffer: gst::Buffer,
|
||||
) -> BoxFuture<'static, Result<gst::FlowSuccess, gst::FlowError>> {
|
||||
let sender = elem.imp().clone_item_sender();
|
||||
async move {
|
||||
if sender.send_async(StreamItem::Buffer(buffer)).await.is_err() {
|
||||
return Err(gst::FlowError::Flushing);
|
||||
}
|
||||
|
||||
Ok(gst::FlowSuccess::Ok)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_event_serialized(
|
||||
self,
|
||||
_pad: gst::Pad,
|
||||
elem: super::TaskSink,
|
||||
event: gst::Event,
|
||||
) -> BoxFuture<'static, bool> {
|
||||
let sender = elem.imp().clone_item_sender();
|
||||
async move {
|
||||
match event.view() {
|
||||
EventView::Segment(_) => {
|
||||
let _ = sender.send_async(StreamItem::Event(event)).await;
|
||||
}
|
||||
EventView::Eos(_) => {
|
||||
let is_main_elem = elem.imp().settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, obj: elem, "EOS");
|
||||
|
||||
// When each element sends its own EOS message,
|
||||
// it takes ages for the pipeline to process all of them.
|
||||
// Let's just post an error message and let main shuts down
|
||||
// after all streams have posted this message.
|
||||
let _ = elem
|
||||
.post_message(gst::message::Error::new(gst::LibraryError::Shutdown, "EOS"));
|
||||
}
|
||||
EventView::FlushStop(_) => {
|
||||
let imp = elem.imp();
|
||||
return imp.task.flush_stop().await_maybe_on_context().is_ok();
|
||||
}
|
||||
EventView::SinkMessage(evt) => {
|
||||
let _ = elem.post_message(evt.message());
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn sink_event(self, _pad: &gst::Pad, imp: &TaskSink, event: gst::Event) -> bool {
|
||||
if let EventView::FlushStart(..) = event.view() {
|
||||
return imp.task.flush_start().await_maybe_on_context().is_ok();
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskSinkTask {
|
||||
elem: super::TaskSink,
|
||||
item_receiver: flume::Receiver<StreamItem>,
|
||||
is_main_elem: bool,
|
||||
last_dts: Option<gst::ClockTime>,
|
||||
segment_start: Option<gst::ClockTime>,
|
||||
stats: Option<Box<Stats>>,
|
||||
}
|
||||
|
||||
impl TaskSinkTask {
|
||||
fn new(
|
||||
elem: &super::TaskSink,
|
||||
item_receiver: flume::Receiver<StreamItem>,
|
||||
is_main_elem: bool,
|
||||
stats: Option<Box<Stats>>,
|
||||
) -> Self {
|
||||
TaskSinkTask {
|
||||
elem: elem.clone(),
|
||||
item_receiver,
|
||||
is_main_elem,
|
||||
last_dts: None,
|
||||
stats,
|
||||
segment_start: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) {
|
||||
// Purge the channel
|
||||
while !self.item_receiver.is_empty() {}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskImpl for TaskSinkTask {
|
||||
type Item = StreamItem;
|
||||
|
||||
fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Preparing Task");
|
||||
future::ok(()).boxed()
|
||||
}
|
||||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Starting Task");
|
||||
self.last_dts = None;
|
||||
if let Some(stats) = self.stats.as_mut() {
|
||||
stats.start();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Stopping Task");
|
||||
self.flush();
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn try_next(&mut self) -> BoxFuture<'_, Result<StreamItem, gst::FlowError>> {
|
||||
self.item_receiver
|
||||
.recv_async()
|
||||
.map(|opt_item| Ok(opt_item.unwrap()))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn handle_item(&mut self, item: StreamItem) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||
async move {
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Received {item:?}");
|
||||
|
||||
match item {
|
||||
StreamItem::Buffer(buffer) => {
|
||||
let dts = buffer
|
||||
.dts()
|
||||
.expect("Buffer without dts")
|
||||
.checked_sub(self.segment_start.expect("Buffer without Time Segment"))
|
||||
.expect("dts before Segment start");
|
||||
|
||||
if let Some(last_dts) = self.last_dts {
|
||||
let cur_ts = self.elem.current_running_time().unwrap();
|
||||
let latency: Duration = (cur_ts - dts).into();
|
||||
let interval: Duration = (dts - last_dts).into();
|
||||
|
||||
if let Some(stats) = self.stats.as_mut() {
|
||||
stats.add_buffer(latency, interval);
|
||||
}
|
||||
|
||||
debug_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: self.elem,
|
||||
"o latency {latency:.2?}",
|
||||
);
|
||||
debug_or_trace!(
|
||||
CAT,
|
||||
self.is_main_elem,
|
||||
obj: self.elem,
|
||||
"o interval {interval:.2?}",
|
||||
);
|
||||
}
|
||||
|
||||
self.last_dts = Some(dts);
|
||||
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Buffer processed");
|
||||
}
|
||||
StreamItem::Event(evt) => {
|
||||
if let EventView::Segment(evt) = evt.view() {
|
||||
if let Some(time_seg) = evt.segment().downcast_ref::<gst::ClockTime>() {
|
||||
self.segment_start = time_seg.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TaskSink {
|
||||
sink_pad: PadSink,
|
||||
task: Task,
|
||||
item_sender: Mutex<Option<flume::Sender<StreamItem>>>,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
impl TaskSink {
|
||||
#[track_caller]
|
||||
fn clone_item_sender(&self) -> flume::Sender<StreamItem> {
|
||||
self.item_sender.lock().unwrap().as_ref().unwrap().clone()
|
||||
}
|
||||
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let stats = if settings.logs_stats {
|
||||
Some(Box::new(Stats::new(
|
||||
settings.max_buffers,
|
||||
settings.push_period + settings.context_wait / 2,
|
||||
)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Preparing");
|
||||
|
||||
let ts_ctx = Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
|
||||
error_msg!(
|
||||
gst::ResourceError::OpenWrite,
|
||||
["Failed to acquire Context: {}", err]
|
||||
)
|
||||
})?;
|
||||
|
||||
// Enable backpressure for items
|
||||
let (item_sender, item_receiver) = flume::bounded(0);
|
||||
let task_impl = TaskSinkTask::new(&self.obj(), item_receiver, settings.is_main_elem, stats);
|
||||
self.task.prepare(task_impl, ts_ctx).block_on()?;
|
||||
|
||||
*self.item_sender.lock().unwrap() = Some(item_sender);
|
||||
|
||||
debug_or_trace!(CAT, settings.is_main_elem, imp: self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Unpreparing");
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopping");
|
||||
self.task.stop().block_on()?;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Starting");
|
||||
self.task.start().block_on()?;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for TaskSink {
|
||||
const NAME: &'static str = "TsStandaloneTaskSink";
|
||||
type Type = super::TaskSink;
|
||||
type ParentType = gst::Element;
|
||||
|
||||
fn with_class(klass: &Self::Class) -> Self {
|
||||
Self {
|
||||
sink_pad: PadSink::new(
|
||||
gst::Pad::from_template(&klass.pad_template("sink").unwrap(), Some("sink")),
|
||||
TaskPadSinkHandler,
|
||||
),
|
||||
task: Task::default(),
|
||||
item_sender: Default::default(),
|
||||
settings: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for TaskSink {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(Settings::properties);
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
self.settings.lock().unwrap().set_property(id, value, pspec);
|
||||
}
|
||||
|
||||
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
self.settings.lock().unwrap().property(id, pspec)
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.add_pad(self.sink_pad.gst_pad()).unwrap();
|
||||
obj.set_element_flags(gst::ElementFlags::SINK);
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for TaskSink {}
|
||||
|
||||
impl ElementImpl for TaskSink {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"Thread-sharing standalone test task sink",
|
||||
"Sink/Test",
|
||||
"Thread-sharing standalone test task sink",
|
||||
"François Laignel <fengalin@free.fr>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let caps = gst::Caps::new_any();
|
||||
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![sink_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
|
||||
fn change_state(
|
||||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
self.prepare().map_err(|err| {
|
||||
self.post_error_message(err);
|
||||
gst::StateChangeError
|
||||
})?;
|
||||
}
|
||||
gst::StateChange::ReadyToPaused => {
|
||||
self.start().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
gst::StateChange::PausedToReady => {
|
||||
self.stop().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
gst::StateChange::ReadyToNull => {
|
||||
self.unprepare();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.parent_change_state(transition)
|
||||
}
|
||||
}
|
17
generic/threadshare/examples/standalone/sink/task/mod.rs
Normal file
17
generic/threadshare/examples/standalone/sink/task/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct TaskSink(ObjectSubclass<imp::TaskSink>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
super::TASK_ELEMENT_NAME,
|
||||
gst::Rank::None,
|
||||
TaskSink::static_type(),
|
||||
)
|
||||
}
|
|
@ -19,11 +19,11 @@ use std::sync::Mutex;
|
|||
use std::time::Duration;
|
||||
|
||||
use gstthreadshare::runtime::prelude::*;
|
||||
use gstthreadshare::runtime::{timer, Context, PadSrc, Task};
|
||||
use gstthreadshare::runtime::{task, timer, Context, PadSrc, Task};
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"ts-standalone-test-src",
|
||||
super::ELEMENT_NAME,
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Thread-sharing standalone test src"),
|
||||
)
|
||||
|
@ -39,7 +39,7 @@ struct Settings {
|
|||
context: String,
|
||||
context_wait: Duration,
|
||||
push_period: gst::ClockTime,
|
||||
raise_log_level: bool,
|
||||
is_main_elem: bool,
|
||||
num_buffers: Option<u32>,
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ impl Default for Settings {
|
|||
context: DEFAULT_CONTEXT.into(),
|
||||
context_wait: DEFAULT_CONTEXT_WAIT,
|
||||
push_period: DEFAULT_PUSH_PERIOD,
|
||||
raise_log_level: false,
|
||||
is_main_elem: false,
|
||||
num_buffers: Some(DEFAULT_NUM_BUFFERS as u32),
|
||||
}
|
||||
}
|
||||
|
@ -63,19 +63,18 @@ impl PadSrcHandler for TestSrcPadHandler {
|
|||
|
||||
#[derive(Debug)]
|
||||
struct SrcTask {
|
||||
element: super::TestSrc,
|
||||
elem: super::TestSrc,
|
||||
buffer_pool: gst::BufferPool,
|
||||
timer: Option<timer::Interval>,
|
||||
raise_log_level: bool,
|
||||
is_main_elem: bool,
|
||||
push_period: gst::ClockTime,
|
||||
need_initial_events: bool,
|
||||
need_segment: bool,
|
||||
num_buffers: Option<u32>,
|
||||
buffer_count: u32,
|
||||
}
|
||||
|
||||
impl SrcTask {
|
||||
fn new(element: super::TestSrc) -> Self {
|
||||
fn new(elem: super::TestSrc) -> Self {
|
||||
let buffer_pool = gst::BufferPool::new();
|
||||
let mut pool_config = buffer_pool.config();
|
||||
pool_config
|
||||
|
@ -84,13 +83,12 @@ impl SrcTask {
|
|||
buffer_pool.set_config(pool_config).unwrap();
|
||||
|
||||
SrcTask {
|
||||
element,
|
||||
elem,
|
||||
buffer_pool,
|
||||
timer: None,
|
||||
raise_log_level: false,
|
||||
is_main_elem: false,
|
||||
push_period: gst::ClockTime::ZERO,
|
||||
need_initial_events: true,
|
||||
need_segment: true,
|
||||
num_buffers: Some(DEFAULT_NUM_BUFFERS as u32),
|
||||
buffer_count: 0,
|
||||
}
|
||||
|
@ -98,34 +96,48 @@ impl SrcTask {
|
|||
}
|
||||
|
||||
impl TaskImpl for SrcTask {
|
||||
type Item = gst::Buffer;
|
||||
type Item = ();
|
||||
|
||||
fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
let src = self.element.imp();
|
||||
let settings = src.settings.lock().unwrap();
|
||||
self.raise_log_level = settings.raise_log_level;
|
||||
let imp = self.elem.imp();
|
||||
let settings = imp.settings.lock().unwrap();
|
||||
self.is_main_elem = settings.is_main_elem;
|
||||
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Preparing Task");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Preparing Task");
|
||||
}
|
||||
log_or_trace!(CAT, self.is_main_elem, imp: imp, "Preparing Task");
|
||||
|
||||
self.push_period = settings.push_period;
|
||||
self.num_buffers = settings.num_buffers;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
future::ok(()).boxed()
|
||||
}
|
||||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async {
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Starting Task");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Starting Task");
|
||||
async move {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Starting Task");
|
||||
|
||||
if self.need_initial_events {
|
||||
let imp = self.elem.imp();
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Pushing initial events");
|
||||
|
||||
let stream_id =
|
||||
format!("{:08x}{:08x}", rand::random::<u32>(), rand::random::<u32>());
|
||||
let stream_start_evt = gst::event::StreamStart::builder(&stream_id)
|
||||
.group_id(gst::GroupId::next())
|
||||
.build();
|
||||
imp.src_pad.push_event(stream_start_evt).await;
|
||||
|
||||
imp.src_pad
|
||||
.push_event(gst::event::Caps::new(
|
||||
&gst::Caps::builder("foo/bar").build(),
|
||||
))
|
||||
.await;
|
||||
|
||||
let segment_evt =
|
||||
gst::event::Segment::new(&gst::FormattedSegment::<gst::format::Time>::new());
|
||||
imp.src_pad.push_event(segment_evt).await;
|
||||
|
||||
self.need_initial_events = false;
|
||||
}
|
||||
|
||||
self.timer = Some(
|
||||
|
@ -138,178 +150,100 @@ impl TaskImpl for SrcTask {
|
|||
);
|
||||
self.buffer_count = 0;
|
||||
self.buffer_pool.set_active(true).unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Stopping Task");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Stopping Task");
|
||||
}
|
||||
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Stopping Task");
|
||||
self.buffer_pool.set_active(false).unwrap();
|
||||
self.timer = None;
|
||||
self.need_initial_events = true;
|
||||
self.need_segment = true;
|
||||
|
||||
future::ok(()).boxed()
|
||||
}
|
||||
|
||||
fn try_next(&mut self) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||
async move {
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Awaiting timer");
|
||||
self.timer.as_mut().unwrap().next().await;
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Timer ticked");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn try_next(&mut self) -> BoxFuture<'_, Result<gst::Buffer, gst::FlowError>> {
|
||||
fn handle_item(&mut self, _: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||
async move {
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Awaiting timer");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Awaiting timer");
|
||||
}
|
||||
|
||||
self.timer.as_mut().unwrap().next().await;
|
||||
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Timer ticked");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Timer ticked");
|
||||
}
|
||||
|
||||
self.buffer_pool
|
||||
let buffer = self
|
||||
.buffer_pool
|
||||
.acquire_buffer(None)
|
||||
.map(|mut buffer| {
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
let rtime = self.element.current_running_time().unwrap();
|
||||
let rtime = self.elem.current_running_time().unwrap();
|
||||
buffer.set_dts(rtime);
|
||||
}
|
||||
buffer
|
||||
})
|
||||
.map_err(|err| {
|
||||
gst::error!(CAT, obj: self.element, "Failed to acquire buffer {}", err);
|
||||
gst::error!(CAT, obj: self.elem, "Failed to acquire buffer {err}");
|
||||
err
|
||||
})
|
||||
})?;
|
||||
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Forwarding buffer");
|
||||
self.elem.imp().src_pad.push(buffer).await?;
|
||||
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Successfully pushed buffer");
|
||||
|
||||
self.buffer_count += 1;
|
||||
|
||||
if self.num_buffers.opt_eq(self.buffer_count) == Some(true) {
|
||||
return Err(gst::FlowError::Eos);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn handle_item(&mut self, buffer: gst::Buffer) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||
fn handle_loop_error(&mut self, err: gst::FlowError) -> BoxFuture<'_, task::Trigger> {
|
||||
async move {
|
||||
let res = self.push(buffer).await;
|
||||
match res {
|
||||
Ok(_) => {
|
||||
if self.raise_log_level {
|
||||
gst::log!(CAT, obj: self.element, "Successfully pushed buffer");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Successfully pushed buffer");
|
||||
}
|
||||
}
|
||||
Err(gst::FlowError::Eos) => {
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "EOS");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "EOS");
|
||||
}
|
||||
let test_src = self.element.imp();
|
||||
test_src.src_pad.push_event(gst::event::Eos::new()).await;
|
||||
match err {
|
||||
gst::FlowError::Eos => {
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Pushing EOS");
|
||||
|
||||
return Err(gst::FlowError::Eos);
|
||||
let imp = self.elem.imp();
|
||||
if !imp.src_pad.push_event(gst::event::Eos::new()).await {
|
||||
gst::error!(CAT, imp: imp, "Error pushing EOS");
|
||||
}
|
||||
Err(gst::FlowError::Flushing) => {
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "Flushing");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Flushing");
|
||||
|
||||
task::Trigger::Stop
|
||||
}
|
||||
gst::FlowError::Flushing => {
|
||||
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Flushing");
|
||||
|
||||
task::Trigger::FlushStart
|
||||
}
|
||||
Err(err) => {
|
||||
gst::error!(CAT, obj: self.element, "Got error {}", err);
|
||||
err => {
|
||||
gst::error!(CAT, obj: self.elem, "Got error {err}");
|
||||
gst::element_error!(
|
||||
&self.element,
|
||||
&self.elem,
|
||||
gst::StreamError::Failed,
|
||||
("Internal data stream error"),
|
||||
["streaming stopped, reason {}", err]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
res.map(drop)
|
||||
task::Trigger::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
impl SrcTask {
|
||||
async fn push(&mut self, buffer: gst::Buffer) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "Pushing {:?}", buffer);
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Pushing {:?}", buffer);
|
||||
}
|
||||
|
||||
let test_src = self.element.imp();
|
||||
|
||||
if self.need_initial_events {
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "Pushing initial events");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Pushing initial events");
|
||||
}
|
||||
|
||||
let stream_id = format!("{:08x}{:08x}", rand::random::<u32>(), rand::random::<u32>());
|
||||
let stream_start_evt = gst::event::StreamStart::builder(&stream_id)
|
||||
.group_id(gst::GroupId::next())
|
||||
.build();
|
||||
test_src.src_pad.push_event(stream_start_evt).await;
|
||||
|
||||
test_src
|
||||
.src_pad
|
||||
.push_event(gst::event::Caps::new(
|
||||
&gst::Caps::builder("foo/bar").build(),
|
||||
))
|
||||
.await;
|
||||
|
||||
self.need_initial_events = false;
|
||||
}
|
||||
|
||||
if self.need_segment {
|
||||
let segment_evt =
|
||||
gst::event::Segment::new(&gst::FormattedSegment::<gst::format::Time>::new());
|
||||
test_src.src_pad.push_event(segment_evt).await;
|
||||
|
||||
self.need_segment = false;
|
||||
}
|
||||
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "Forwarding buffer");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Forwarding buffer");
|
||||
}
|
||||
|
||||
let ok = test_src.src_pad.push(buffer).await?;
|
||||
|
||||
self.buffer_count += 1;
|
||||
|
||||
if self.num_buffers.opt_eq(self.buffer_count).unwrap_or(false) {
|
||||
if self.raise_log_level {
|
||||
gst::debug!(CAT, obj: self.element, "Pushing EOS");
|
||||
} else {
|
||||
gst::trace!(CAT, obj: self.element, "Pushing EOS");
|
||||
}
|
||||
|
||||
let test_src = self.element.imp();
|
||||
if !test_src.src_pad.push_event(gst::event::Eos::new()).await {
|
||||
gst::error!(CAT, obj: self.element, "Error pushing EOS");
|
||||
}
|
||||
return Err(gst::FlowError::Eos);
|
||||
}
|
||||
|
||||
Ok(ok)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestSrc {
|
||||
src_pad: PadSrc,
|
||||
|
@ -319,16 +253,11 @@ pub struct TestSrc {
|
|||
|
||||
impl TestSrc {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Preparing");
|
||||
}
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let context =
|
||||
Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
|
||||
let ts_ctx = Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
|
||||
gst::error_msg!(
|
||||
gst::ResourceError::OpenRead,
|
||||
["Failed to acquire Context: {}", err]
|
||||
|
@ -337,88 +266,44 @@ impl TestSrc {
|
|||
drop(settings);
|
||||
|
||||
self.task
|
||||
.prepare(SrcTask::new(self.obj().clone()), context)
|
||||
.prepare(SrcTask::new(self.instance().clone()), ts_ctx)
|
||||
.block_on()?;
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Prepared");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Prepared");
|
||||
}
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Unpreparing");
|
||||
}
|
||||
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Unpreparing");
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Unprepared");
|
||||
}
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Stopping");
|
||||
}
|
||||
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopping");
|
||||
self.task.stop().block_on()?;
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Stopped");
|
||||
}
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Starting");
|
||||
}
|
||||
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Starting");
|
||||
self.task.start().block_on()?;
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Started");
|
||||
}
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Pausing");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Pausing");
|
||||
}
|
||||
|
||||
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Pausing");
|
||||
self.task.pause().block_on()?;
|
||||
|
||||
if raise_log_level {
|
||||
gst::debug!(CAT, imp: self, "Paused");
|
||||
} else {
|
||||
gst::trace!(CAT, imp: self, "Paused");
|
||||
}
|
||||
debug_or_trace!(CAT, is_main_elem, imp: self, "Paused");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -462,9 +347,9 @@ impl ObjectImpl for TestSrc {
|
|||
.blurb("Push a new buffer every this many ms")
|
||||
.default_value(DEFAULT_PUSH_PERIOD.mseconds() as u32)
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("raise-log-level")
|
||||
.nick("Raise log level")
|
||||
.blurb("Raises the log level so that this element stands out")
|
||||
glib::ParamSpecBoolean::builder("main-elem")
|
||||
.nick("Main Element")
|
||||
.blurb("Declare this element as the main one")
|
||||
.write_only()
|
||||
.build(),
|
||||
glib::ParamSpecInt::builder("num-buffers")
|
||||
|
@ -485,24 +370,21 @@ impl ObjectImpl for TestSrc {
|
|||
"context" => {
|
||||
settings.context = value
|
||||
.get::<Option<String>>()
|
||||
.expect("type checked upstream")
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| DEFAULT_CONTEXT.into());
|
||||
}
|
||||
"context-wait" => {
|
||||
settings.context_wait = Duration::from_millis(
|
||||
value.get::<u32>().expect("type checked upstream").into(),
|
||||
);
|
||||
settings.context_wait = Duration::from_millis(value.get::<u32>().unwrap().into());
|
||||
}
|
||||
"push-period" => {
|
||||
settings.push_period = gst::ClockTime::from_mseconds(
|
||||
value.get::<u32>().expect("type checked upstream").into(),
|
||||
);
|
||||
let value: u64 = value.get::<u32>().unwrap().into();
|
||||
settings.push_period = value.mseconds();
|
||||
}
|
||||
"raise-log-level" => {
|
||||
settings.raise_log_level = value.get::<bool>().expect("type checked upstream");
|
||||
"main-elem" => {
|
||||
settings.is_main_elem = value.get::<bool>().unwrap();
|
||||
}
|
||||
"num-buffers" => {
|
||||
let value = value.get::<i32>().expect("type checked upstream");
|
||||
let value = value.get::<i32>().unwrap();
|
||||
settings.num_buffers = if value > 0 { Some(value as u32) } else { None };
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
|
@ -515,7 +397,7 @@ impl ObjectImpl for TestSrc {
|
|||
"context" => settings.context.to_value(),
|
||||
"context-wait" => (settings.context_wait.as_millis() as u32).to_value(),
|
||||
"push-period" => (settings.push_period.mseconds() as u32).to_value(),
|
||||
"raise-log-level" => settings.raise_log_level.to_value(),
|
||||
"main-elem" => settings.is_main_elem.to_value(),
|
||||
"num-buffers" => settings
|
||||
.num_buffers
|
||||
.and_then(|val| val.try_into().ok())
|
||||
|
@ -571,7 +453,7 @@ impl ElementImpl for TestSrc {
|
|||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
|
|
|
@ -3,6 +3,8 @@ use gst::prelude::*;
|
|||
|
||||
mod imp;
|
||||
|
||||
pub const ELEMENT_NAME: &str = "ts-standalone-src";
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct TestSrc(ObjectSubclass<imp::TestSrc>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
|
@ -10,7 +12,7 @@ glib::wrapper! {
|
|||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"ts-standalone-test-src",
|
||||
"ts-standalone-src",
|
||||
gst::Rank::None,
|
||||
TestSrc::static_type(),
|
||||
)
|
||||
|
|
|
@ -17,19 +17,45 @@
|
|||
//
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::net;
|
||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||
use std::{env, thread, time};
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"ts-udpsrc-benchmark-sender",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Thread-sharing UDP src benchmark sender"),
|
||||
)
|
||||
});
|
||||
|
||||
fn main() {
|
||||
gst::init().unwrap();
|
||||
gstthreadshare::plugin_register_static().unwrap();
|
||||
|
||||
let args = env::args().collect::<Vec<_>>();
|
||||
assert!(args.len() > 1);
|
||||
let n_streams: u16 = args[1].parse().unwrap();
|
||||
|
||||
if args.len() > 2 && args[2] == "rtp" {
|
||||
send_rtp_buffers(n_streams);
|
||||
let num_buffers: Option<i32> = if args.len() > 3 {
|
||||
args[3].parse().ok()
|
||||
} else {
|
||||
send_raw_buffers(n_streams);
|
||||
None
|
||||
};
|
||||
|
||||
if args.len() > 2 {
|
||||
match args[2].as_str() {
|
||||
"raw" => send_raw_buffers(n_streams),
|
||||
"rtp" => send_rtp_buffers(n_streams, num_buffers),
|
||||
_ => send_test_buffers(n_streams, num_buffers),
|
||||
}
|
||||
} else {
|
||||
send_test_buffers(n_streams, num_buffers);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +64,7 @@ fn send_raw_buffers(n_streams: u16) {
|
|||
let socket = net::UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||
|
||||
let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||
let destinations = (40000..(40000 + n_streams))
|
||||
let destinations = (5004..(5004 + n_streams))
|
||||
.map(|port| SocketAddr::new(ipaddr, port))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
@ -60,43 +86,60 @@ fn send_raw_buffers(n_streams: u16) {
|
|||
}
|
||||
}
|
||||
|
||||
fn send_rtp_buffers(n_streams: u16) {
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
gst::init().unwrap();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
use std::path::Path;
|
||||
|
||||
let mut path = Path::new("target/debug");
|
||||
if !path.exists() {
|
||||
path = Path::new("../../target/debug");
|
||||
}
|
||||
|
||||
gst::Registry::get().scan_path(path);
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
use std::path::Path;
|
||||
|
||||
let mut path = Path::new("target/release");
|
||||
if !path.exists() {
|
||||
path = Path::new("../../target/release");
|
||||
}
|
||||
|
||||
gst::Registry::get().scan_path(path);
|
||||
}
|
||||
|
||||
let l = glib::MainLoop::new(None, false);
|
||||
fn send_test_buffers(n_streams: u16, num_buffers: Option<i32>) {
|
||||
let pipeline = gst::Pipeline::default();
|
||||
for i in 0..n_streams {
|
||||
let src = gst::ElementFactory::make("audiotestsrc")
|
||||
.name(format!("audiotestsrc-{}", i).as_str())
|
||||
let src = gst::ElementFactory::make("ts-audiotestsrc")
|
||||
.name(format!("ts-audiotestsrc-{}", i).as_str())
|
||||
.property("context-wait", 20u32)
|
||||
.property("is-live", true)
|
||||
.property("do-timestamp", true)
|
||||
.build()
|
||||
.unwrap();
|
||||
src.set_property("is-live", true);
|
||||
|
||||
if let Some(num_buffers) = num_buffers {
|
||||
src.set_property("num-buffers", num_buffers);
|
||||
}
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
if i == 0 {
|
||||
src.set_property("main-elem", true);
|
||||
}
|
||||
|
||||
let sink = gst::ElementFactory::make("ts-udpsink")
|
||||
.name(format!("udpsink-{}", i).as_str())
|
||||
.property("clients", format!("127.0.0.1:{}", i + 5004))
|
||||
.property("context-wait", 20u32)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let elements = &[&src, &sink];
|
||||
pipeline.add_many(elements).unwrap();
|
||||
gst::Element::link_many(elements).unwrap();
|
||||
}
|
||||
|
||||
run(pipeline);
|
||||
}
|
||||
|
||||
fn send_rtp_buffers(n_streams: u16, num_buffers: Option<i32>) {
|
||||
let pipeline = gst::Pipeline::default();
|
||||
for i in 0..n_streams {
|
||||
let src = gst::ElementFactory::make("ts-audiotestsrc")
|
||||
.name(format!("ts-audiotestsrc-{}", i).as_str())
|
||||
.property("context-wait", 20u32)
|
||||
.property("is-live", true)
|
||||
.property("do-timestamp", true)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
if let Some(num_buffers) = num_buffers {
|
||||
src.set_property("num-buffers", num_buffers);
|
||||
}
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
if i == 0 {
|
||||
src.set_property("main-elem", true);
|
||||
}
|
||||
|
||||
let enc = gst::ElementFactory::make("alawenc")
|
||||
.name(format!("alawenc-{}", i).as_str())
|
||||
|
@ -106,11 +149,11 @@ fn send_rtp_buffers(n_streams: u16) {
|
|||
.name(format!("rtppcmapay-{}", i).as_str())
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let sink = gst::ElementFactory::make("ts-udpsink")
|
||||
.name(format!("udpsink-{}", i).as_str())
|
||||
.property("clients", format!("127.0.0.1:{}", i + 40000))
|
||||
.property("context", "context-udpsink")
|
||||
.property("context-wait", 20u32)
|
||||
.property("clients", format!("127.0.0.1:{}", i + 5004))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
@ -119,6 +162,42 @@ fn send_rtp_buffers(n_streams: u16) {
|
|||
gst::Element::link_many(elements).unwrap();
|
||||
}
|
||||
|
||||
run(pipeline);
|
||||
}
|
||||
|
||||
fn run(pipeline: gst::Pipeline) {
|
||||
let l = glib::MainLoop::new(None, false);
|
||||
|
||||
let bus = pipeline.bus().unwrap();
|
||||
let l_clone = l.clone();
|
||||
bus.add_watch(move |_, msg| {
|
||||
use gst::MessageView;
|
||||
match msg.view() {
|
||||
MessageView::Eos(_) => {
|
||||
gst::info!(CAT, "Received eos");
|
||||
l_clone.quit();
|
||||
|
||||
glib::Continue(false)
|
||||
}
|
||||
MessageView::Error(msg) => {
|
||||
gst::error!(
|
||||
CAT,
|
||||
"Error from {:?}: {} ({:?})",
|
||||
msg.src().map(|s| s.path_string()),
|
||||
msg.error(),
|
||||
msg.debug()
|
||||
);
|
||||
l_clone.quit();
|
||||
|
||||
glib::Continue(false)
|
||||
}
|
||||
_ => glib::Continue(true),
|
||||
}
|
||||
})
|
||||
.expect("Failed to add bus watch");
|
||||
|
||||
pipeline.set_state(gst::State::Playing).unwrap();
|
||||
l.run();
|
||||
|
||||
pipeline.set_state(gst::State::Null).unwrap();
|
||||
}
|
||||
|
|
735
generic/threadshare/src/audiotestsrc/imp.rs
Normal file
735
generic/threadshare/src/audiotestsrc/imp.rs
Normal file
|
@ -0,0 +1,735 @@
|
|||
// Copyright (C) 2022 François Laignel <fengalin@free.fr>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use futures::future::BoxFuture;
|
||||
use futures::prelude::*;
|
||||
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::mem::size_of;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
#[cfg(feature = "tuning")]
|
||||
use std::time::Instant;
|
||||
|
||||
use crate::runtime::prelude::*;
|
||||
use crate::runtime::{self, task, timer, PadSrc, Task};
|
||||
|
||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||
gst::DebugCategory::new(
|
||||
"ts-audiotestsrc",
|
||||
gst::DebugColorFlags::empty(),
|
||||
Some("Thread-sharing audio test src"),
|
||||
)
|
||||
});
|
||||
|
||||
const DEFAULT_CONTEXT: &str = "";
|
||||
const DEFAULT_CONTEXT_WAIT: Duration = Duration::ZERO;
|
||||
const DEFAULT_BUFFER_DURATION: gst::ClockTime = gst::ClockTime::from_mseconds(10);
|
||||
const DEFAULT_DO_TIMESTAMP: bool = false;
|
||||
const DEFAULT_IS_LIVE: bool = false;
|
||||
const DEFAULT_NUM_BUFFERS: i32 = -1;
|
||||
|
||||
const DEFAULT_CHANNELS: usize = 1;
|
||||
const DEFAULT_FREQ: f32 = 440.0;
|
||||
const DEFAULT_VOLUME: f32 = 0.8;
|
||||
const DEFAULT_RATE: u32 = 44_100;
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
const RAMPUP_BUFFER_COUNT: u32 = 500;
|
||||
#[cfg(feature = "tuning")]
|
||||
const LOG_BUFFER_INTERVAL: u32 = 2000;
|
||||
|
||||
static DEFAULT_CAPS: Lazy<gst::Caps> = Lazy::new(|| {
|
||||
gst_audio::AudioCapsBuilder::new_interleaved()
|
||||
.format(gst_audio::AUDIO_FORMAT_S16)
|
||||
.rate_range(8_000..i32::MAX)
|
||||
.channels_range(1..i32::MAX)
|
||||
.build()
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Settings {
|
||||
context: String,
|
||||
context_wait: Duration,
|
||||
do_timestamp: bool,
|
||||
is_live: bool,
|
||||
buffer_duration: gst::ClockTime,
|
||||
num_buffers: Option<u32>,
|
||||
#[cfg(feature = "tuning")]
|
||||
is_main_elem: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Settings {
|
||||
context: DEFAULT_CONTEXT.into(),
|
||||
context_wait: DEFAULT_CONTEXT_WAIT,
|
||||
do_timestamp: DEFAULT_DO_TIMESTAMP,
|
||||
is_live: DEFAULT_IS_LIVE,
|
||||
buffer_duration: DEFAULT_BUFFER_DURATION,
|
||||
num_buffers: None,
|
||||
#[cfg(feature = "tuning")]
|
||||
is_main_elem: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct AudioTestSrcPadHandler;
|
||||
impl PadSrcHandler for AudioTestSrcPadHandler {
|
||||
type ElementImpl = AudioTestSrc;
|
||||
|
||||
fn src_query(self, pad: &gst::Pad, imp: &Self::ElementImpl, query: &mut gst::QueryRef) -> bool {
|
||||
gst::debug!(CAT, obj: pad, "Received {query:?}");
|
||||
|
||||
if let gst::QueryViewMut::Latency(q) = query.view_mut() {
|
||||
let settings = imp.settings.lock().unwrap();
|
||||
let min_latency = if settings.is_live {
|
||||
settings.buffer_duration
|
||||
} else {
|
||||
gst::ClockTime::ZERO
|
||||
};
|
||||
|
||||
q.set(
|
||||
settings.is_live,
|
||||
min_latency,
|
||||
min_latency
|
||||
+ runtime::Context::current().map_or(gst::ClockTime::ZERO, |ctx| {
|
||||
gst::ClockTime::try_from(ctx.wait_duration()).unwrap()
|
||||
}),
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
gst::Pad::query_default(pad, Some(&*imp.obj()), query)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum Negotiation {
|
||||
Unchanged,
|
||||
Changed,
|
||||
}
|
||||
|
||||
impl Negotiation {
|
||||
fn has_changed(self) -> bool {
|
||||
matches!(self, Negotiation::Changed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AudioTestSrcTask {
|
||||
elem: super::AudioTestSrc,
|
||||
buffer_pool: gst::BufferPool,
|
||||
rate: u32,
|
||||
channels: usize,
|
||||
do_timestamp: bool,
|
||||
is_live: bool,
|
||||
buffer_duration: gst::ClockTime,
|
||||
need_initial_events: bool,
|
||||
step: f32,
|
||||
accumulator: f32,
|
||||
last_buffer_end: Option<gst::ClockTime>,
|
||||
caps: gst::Caps,
|
||||
buffer_count: u32,
|
||||
num_buffers: Option<u32>,
|
||||
#[cfg(feature = "tuning")]
|
||||
is_main_elem: bool,
|
||||
#[cfg(feature = "tuning")]
|
||||
parked_duration_init: Option<Duration>,
|
||||
#[cfg(feature = "tuning")]
|
||||
log_start: Instant,
|
||||
}
|
||||
|
||||
impl AudioTestSrcTask {
|
||||
fn new(elem: super::AudioTestSrc) -> Self {
|
||||
AudioTestSrcTask {
|
||||
elem,
|
||||
buffer_pool: gst::BufferPool::new(),
|
||||
rate: DEFAULT_RATE,
|
||||
channels: DEFAULT_CHANNELS,
|
||||
do_timestamp: DEFAULT_DO_TIMESTAMP,
|
||||
is_live: DEFAULT_IS_LIVE,
|
||||
buffer_duration: DEFAULT_BUFFER_DURATION,
|
||||
need_initial_events: true,
|
||||
step: 0.0,
|
||||
accumulator: 0.0,
|
||||
last_buffer_end: None,
|
||||
caps: gst::Caps::new_empty(),
|
||||
buffer_count: 0,
|
||||
num_buffers: None,
|
||||
#[cfg(feature = "tuning")]
|
||||
is_main_elem: false,
|
||||
#[cfg(feature = "tuning")]
|
||||
parked_duration_init: None,
|
||||
#[cfg(feature = "tuning")]
|
||||
log_start: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn negotiate(&mut self) -> Result<Negotiation, gst::ErrorMessage> {
|
||||
let imp = self.elem.imp();
|
||||
let pad = imp.src_pad.gst_pad();
|
||||
|
||||
if !pad.check_reconfigure() {
|
||||
return Ok(Negotiation::Unchanged);
|
||||
}
|
||||
|
||||
let mut caps = pad.peer_query_caps(Some(&DEFAULT_CAPS));
|
||||
gst::debug!(CAT, imp: imp, "Peer returned {caps:?}");
|
||||
|
||||
if caps.is_empty() {
|
||||
pad.mark_reconfigure();
|
||||
let err = gst::error_msg!(gst::CoreError::Pad, ["No common Caps"]);
|
||||
gst::error!(CAT, imp: imp, "{err}");
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if caps.is_any() {
|
||||
gst::debug!(CAT, imp: imp, "Using our own Caps");
|
||||
caps = DEFAULT_CAPS.clone();
|
||||
}
|
||||
|
||||
{
|
||||
let caps = caps.make_mut();
|
||||
let s = caps.structure_mut(0).ok_or_else(|| {
|
||||
let err = gst::error_msg!(gst::CoreError::Pad, ["Invalid peer Caps structure"]);
|
||||
gst::error!(CAT, imp: imp, "{err}");
|
||||
err
|
||||
})?;
|
||||
|
||||
s.fixate_field_nearest_int("rate", DEFAULT_RATE as i32);
|
||||
self.rate = s.get::<i32>("rate").unwrap() as u32;
|
||||
self.step = 2.0 * std::f32::consts::PI * DEFAULT_FREQ / (self.rate as f32);
|
||||
|
||||
s.fixate_field_nearest_int("channels", DEFAULT_CHANNELS as i32);
|
||||
self.channels = s.get::<i32>("channels").unwrap() as usize;
|
||||
|
||||
if self.channels > 2 {
|
||||
s.set::<gst::Bitmask>(
|
||||
"channel-mask",
|
||||
gst_audio::AudioChannelPosition::fallback_mask(self.channels as u32).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
caps.fixate();
|
||||
gst::debug!(CAT, imp: imp, "fixated to {caps:?}");
|
||||
|
||||
imp.src_pad.push_event(gst::event::Caps::new(&caps)).await;
|
||||
|
||||
self.caps = caps;
|
||||
|
||||
Ok(Negotiation::Changed)
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskImpl for AudioTestSrcTask {
|
||||
type Item = gst::Buffer;
|
||||
|
||||
fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
gst::log!(CAT, obj: self.elem, "Preparing Task");
|
||||
|
||||
let imp = self.elem.imp();
|
||||
let settings = imp.settings.lock().unwrap();
|
||||
self.do_timestamp = settings.do_timestamp;
|
||||
self.is_live = settings.is_live;
|
||||
self.buffer_duration = settings.buffer_duration;
|
||||
self.num_buffers = settings.num_buffers;
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
{
|
||||
self.is_main_elem = settings.is_main_elem;
|
||||
}
|
||||
|
||||
future::ok(()).boxed()
|
||||
}
|
||||
|
||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
async move {
|
||||
gst::log!(CAT, obj: self.elem, "Starting Task");
|
||||
|
||||
if self.need_initial_events {
|
||||
gst::debug!(CAT, obj: self.elem, "Pushing initial events");
|
||||
|
||||
let stream_id =
|
||||
format!("{:08x}{:08x}", rand::random::<u32>(), rand::random::<u32>());
|
||||
let stream_start_evt = gst::event::StreamStart::builder(&stream_id)
|
||||
.group_id(gst::GroupId::next())
|
||||
.build();
|
||||
self.elem.imp().src_pad.push_event(stream_start_evt).await;
|
||||
}
|
||||
|
||||
if self.negotiate().await?.has_changed() {
|
||||
let bytes_per_buffer = (self.rate as u64)
|
||||
* self.buffer_duration.mseconds()
|
||||
* self.channels as u64
|
||||
* size_of::<i16>() as u64
|
||||
/ 1_000;
|
||||
|
||||
let mut pool_config = self.buffer_pool.config();
|
||||
pool_config
|
||||
.as_mut()
|
||||
.set_params(Some(&self.caps), bytes_per_buffer as u32, 2, 6);
|
||||
self.buffer_pool.set_config(pool_config).unwrap();
|
||||
}
|
||||
|
||||
assert!(!self.caps.is_empty());
|
||||
self.buffer_pool.set_active(true).unwrap();
|
||||
|
||||
if self.need_initial_events {
|
||||
let segment_evt =
|
||||
gst::event::Segment::new(&gst::FormattedSegment::<gst::format::Time>::new());
|
||||
self.elem.imp().src_pad.push_event(segment_evt).await;
|
||||
|
||||
self.need_initial_events = false;
|
||||
}
|
||||
|
||||
self.buffer_count = 0;
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
if self.is_main_elem {
|
||||
self.parked_duration_init = None;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn pause(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
gst::log!(CAT, obj: self.elem, "Pausing Task");
|
||||
self.buffer_pool.set_active(false).unwrap();
|
||||
|
||||
future::ok(()).boxed()
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||
gst::log!(CAT, obj: self.elem, "Stopping Task");
|
||||
|
||||
self.need_initial_events = true;
|
||||
self.accumulator = 0.0;
|
||||
self.last_buffer_end = None;
|
||||
|
||||
future::ok(()).boxed()
|
||||
}
|
||||
|
||||
fn try_next(&mut self) -> BoxFuture<'_, Result<gst::Buffer, gst::FlowError>> {
|
||||
let mut buffer = match self.buffer_pool.acquire_buffer(None) {
|
||||
Ok(buffer) => buffer,
|
||||
Err(err) => {
|
||||
gst::error!(CAT, obj: self.elem, "Failed to acquire buffer {}", err);
|
||||
return future::err(err).boxed();
|
||||
}
|
||||
};
|
||||
|
||||
let buffer_mut = buffer.get_mut().unwrap();
|
||||
|
||||
let start = if self.is_live | self.do_timestamp {
|
||||
self.last_buffer_end
|
||||
.or_else(|| self.elem.current_running_time())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
{
|
||||
use std::io::Write;
|
||||
|
||||
let mut mapped = buffer_mut.map_writable().unwrap();
|
||||
let slice = mapped.as_mut_slice();
|
||||
slice
|
||||
.chunks_mut(self.channels * size_of::<i16>())
|
||||
.for_each(|frame| {
|
||||
let sample = ((self.accumulator.sin() * DEFAULT_VOLUME * (i16::MAX as f32))
|
||||
as i16)
|
||||
.to_ne_bytes();
|
||||
|
||||
frame.chunks_mut(size_of::<i16>()).for_each(|mut channel| {
|
||||
let _ = channel.write(&sample).unwrap();
|
||||
});
|
||||
|
||||
self.accumulator += self.step;
|
||||
if self.accumulator >= 2.0 * std::f32::consts::PI {
|
||||
self.accumulator = -2.0 * std::f32::consts::PI;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if self.do_timestamp {
|
||||
buffer_mut.set_dts(start);
|
||||
buffer_mut.set_duration(self.buffer_duration);
|
||||
}
|
||||
|
||||
self.last_buffer_end = start.opt_add(self.buffer_duration);
|
||||
|
||||
async move {
|
||||
if self.is_live {
|
||||
if let Some(delay) = self
|
||||
.last_buffer_end
|
||||
.unwrap()
|
||||
.checked_sub(self.elem.current_running_time().unwrap())
|
||||
{
|
||||
// Wait for all samples to fit in last time slice
|
||||
timer::delay_for_at_least(delay.into()).await;
|
||||
}
|
||||
} else {
|
||||
// Let the scheduler share time with other tasks
|
||||
runtime::executor::yield_now().await;
|
||||
}
|
||||
|
||||
Ok(buffer)
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn handle_item(&mut self, buffer: gst::Buffer) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||
async move {
|
||||
let imp = self.elem.imp();
|
||||
|
||||
gst::debug!(CAT, imp: imp, "Pushing {buffer:?}");
|
||||
imp.src_pad.push(buffer).await?;
|
||||
gst::log!(CAT, imp: imp, "Successfully pushed buffer");
|
||||
|
||||
self.buffer_count += 1;
|
||||
|
||||
#[cfg(feature = "tuning")]
|
||||
if self.is_main_elem {
|
||||
if let Some(parked_duration_init) = self.parked_duration_init {
|
||||
if self.buffer_count % LOG_BUFFER_INTERVAL == 0 {
|
||||
let parked_duration =
|
||||
runtime::Context::current().unwrap().parked_duration()
|
||||
- parked_duration_init;
|
||||
|
||||
gst::info!(
|
||||
CAT,
|
||||
"Parked: {:5.2?}%",
|
||||
parked_duration.as_nanos() as f32 * 100.0
|
||||
/ self.log_start.elapsed().as_nanos() as f32,
|
||||
);
|
||||
}
|
||||
} else if self.buffer_count == RAMPUP_BUFFER_COUNT {
|
||||
self.parked_duration_init =
|
||||
Some(runtime::Context::current().unwrap().parked_duration());
|
||||
self.log_start = Instant::now();
|
||||
|
||||
gst::info!(CAT, "Ramp up complete");
|
||||
}
|
||||
}
|
||||
|
||||
if self.num_buffers.opt_eq(self.buffer_count) == Some(true) {
|
||||
return Err(gst::FlowError::Eos);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn handle_loop_error(&mut self, err: gst::FlowError) -> BoxFuture<'_, task::Trigger> {
|
||||
async move {
|
||||
match err {
|
||||
gst::FlowError::Flushing => {
|
||||
gst::debug!(CAT, obj: self.elem, "Flushing");
|
||||
|
||||
task::Trigger::FlushStart
|
||||
}
|
||||
gst::FlowError::Eos => {
|
||||
gst::debug!(CAT, obj: self.elem, "EOS");
|
||||
self.elem
|
||||
.imp()
|
||||
.src_pad
|
||||
.push_event(gst::event::Eos::new())
|
||||
.await;
|
||||
|
||||
task::Trigger::Stop
|
||||
}
|
||||
err => {
|
||||
gst::error!(CAT, obj: self.elem, "Got error {err}");
|
||||
gst::element_error!(
|
||||
&self.elem,
|
||||
gst::StreamError::Failed,
|
||||
("Internal data stream error"),
|
||||
["streaming stopped, reason {}", err]
|
||||
);
|
||||
|
||||
task::Trigger::Error
|
||||
}
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AudioTestSrc {
|
||||
src_pad: PadSrc,
|
||||
task: Task,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
impl AudioTestSrc {
|
||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Preparing");
|
||||
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let context =
|
||||
runtime::Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
|
||||
gst::error_msg!(
|
||||
gst::ResourceError::OpenRead,
|
||||
["Failed to acquire Context: {}", err]
|
||||
)
|
||||
})?;
|
||||
drop(settings);
|
||||
|
||||
self.task
|
||||
.prepare(AudioTestSrcTask::new(self.obj().clone()), context)
|
||||
.block_on()?;
|
||||
|
||||
gst::debug!(CAT, imp: self, "Prepared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unprepare(&self) {
|
||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
||||
self.task.unprepare().block_on().unwrap();
|
||||
gst::debug!(CAT, imp: self, "Unprepared");
|
||||
}
|
||||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Stopping");
|
||||
self.task.stop().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Stopped");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Starting");
|
||||
self.task.start().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Started");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||
gst::debug!(CAT, imp: self, "Pausing");
|
||||
self.task.pause().block_on()?;
|
||||
gst::debug!(CAT, imp: self, "Paused");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for AudioTestSrc {
|
||||
const NAME: &'static str = "TsAudioTestSrc";
|
||||
type Type = super::AudioTestSrc;
|
||||
type ParentType = gst::Element;
|
||||
|
||||
fn with_class(klass: &Self::Class) -> Self {
|
||||
Self {
|
||||
src_pad: PadSrc::new(
|
||||
gst::Pad::from_template(&klass.pad_template("src").unwrap(), Some("src")),
|
||||
AudioTestSrcPadHandler,
|
||||
),
|
||||
task: Task::default(),
|
||||
settings: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for AudioTestSrc {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpecString::builder("context")
|
||||
.nick("Context")
|
||||
.blurb("Context name to share threads with")
|
||||
.default_value(Some(DEFAULT_CONTEXT))
|
||||
.build(),
|
||||
glib::ParamSpecUInt::builder("context-wait")
|
||||
.nick("Context Wait")
|
||||
.blurb("Throttle poll loop to run at most once every this many ms")
|
||||
.maximum(1000)
|
||||
.default_value(DEFAULT_CONTEXT_WAIT.as_millis() as u32)
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("do-timestamp")
|
||||
.nick("Do timestamp")
|
||||
.blurb("Apply current stream time to buffers")
|
||||
.build(),
|
||||
glib::ParamSpecBoolean::builder("is-live")
|
||||
.nick("Is live")
|
||||
.blurb("Whether to act as a live source")
|
||||
.build(),
|
||||
glib::ParamSpecUInt::builder("buffer-duration")
|
||||
.nick("Buffer duration")
|
||||
.blurb("Buffer duration in ms")
|
||||
.default_value(DEFAULT_BUFFER_DURATION.mseconds() as u32)
|
||||
.build(),
|
||||
glib::ParamSpecInt::builder("num-buffers")
|
||||
.nick("Num Buffers")
|
||||
.blurb("Number of buffers to output before sending EOS (-1 = unlimited)")
|
||||
.minimum(-1i32)
|
||||
.default_value(DEFAULT_NUM_BUFFERS)
|
||||
.build(),
|
||||
#[cfg(feature = "tuning")]
|
||||
glib::ParamSpecBoolean::builder("main-elem")
|
||||
.nick("Main Element")
|
||||
.blurb("Declare this element as the main one")
|
||||
.write_only()
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
match pspec.name() {
|
||||
"context" => {
|
||||
settings.context = value
|
||||
.get::<Option<String>>()
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| DEFAULT_CONTEXT.into());
|
||||
}
|
||||
"context-wait" => {
|
||||
settings.context_wait = Duration::from_millis(value.get::<u32>().unwrap().into());
|
||||
}
|
||||
"do-timestamp" => {
|
||||
settings.do_timestamp = value.get::<bool>().unwrap();
|
||||
}
|
||||
"is-live" => {
|
||||
settings.is_live = value.get::<bool>().unwrap();
|
||||
}
|
||||
"buffer-duration" => {
|
||||
settings.buffer_duration = (value.get::<u32>().unwrap() as u64).mseconds();
|
||||
}
|
||||
"num-buffers" => {
|
||||
let value = value.get::<i32>().unwrap();
|
||||
settings.num_buffers = if value > 0 { Some(value as u32) } else { None };
|
||||
}
|
||||
#[cfg(feature = "tuning")]
|
||||
"main-elem" => {
|
||||
settings.is_main_elem = value.get::<bool>().unwrap();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
match pspec.name() {
|
||||
"context" => settings.context.to_value(),
|
||||
"context-wait" => (settings.context_wait.as_millis() as u32).to_value(),
|
||||
"do-timestamp" => settings.do_timestamp.to_value(),
|
||||
"is-live" => settings.is_live.to_value(),
|
||||
"buffer-duration" => (settings.buffer_duration.mseconds() as u32).to_value(),
|
||||
"num-buffers" => settings
|
||||
.num_buffers
|
||||
.and_then(|val| val.try_into().ok())
|
||||
.unwrap_or(-1i32)
|
||||
.to_value(),
|
||||
#[cfg(feature = "tuning")]
|
||||
"main-elem" => settings.is_main_elem.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
obj.add_pad(self.src_pad.gst_pad()).unwrap();
|
||||
obj.set_element_flags(gst::ElementFlags::SOURCE);
|
||||
}
|
||||
}
|
||||
|
||||
impl GstObjectImpl for AudioTestSrc {}
|
||||
|
||||
impl ElementImpl for AudioTestSrc {
|
||||
fn metadata() -> Option<&'static gst::subclass::ElementMetadata> {
|
||||
static ELEMENT_METADATA: Lazy<gst::subclass::ElementMetadata> = Lazy::new(|| {
|
||||
gst::subclass::ElementMetadata::new(
|
||||
"Thread-sharing audio test source",
|
||||
"Source/Test",
|
||||
"Thread-sharing audio test source",
|
||||
"François Laignel <fengalin@free.fr>",
|
||||
)
|
||||
});
|
||||
|
||||
Some(&*ELEMENT_METADATA)
|
||||
}
|
||||
|
||||
fn pad_templates() -> &'static [gst::PadTemplate] {
|
||||
static PAD_TEMPLATES: Lazy<Vec<gst::PadTemplate>> = Lazy::new(|| {
|
||||
let src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&DEFAULT_CAPS,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
vec![src_pad_template]
|
||||
});
|
||||
|
||||
PAD_TEMPLATES.as_ref()
|
||||
}
|
||||
|
||||
fn change_state(
|
||||
&self,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||
|
||||
match transition {
|
||||
gst::StateChange::NullToReady => {
|
||||
self.prepare().map_err(|err| {
|
||||
self.post_error_message(err);
|
||||
gst::StateChangeError
|
||||
})?;
|
||||
}
|
||||
gst::StateChange::PlayingToPaused => {
|
||||
self.pause().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
gst::StateChange::ReadyToNull => {
|
||||
self.unprepare();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let mut success = self.parent_change_state(transition)?;
|
||||
|
||||
match transition {
|
||||
gst::StateChange::ReadyToPaused => {
|
||||
self.pause().map_err(|_| gst::StateChangeError)?;
|
||||
success = gst::StateChangeSuccess::NoPreroll;
|
||||
}
|
||||
gst::StateChange::PausedToPlaying => {
|
||||
self.start().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
gst::StateChange::PlayingToPaused => {
|
||||
success = gst::StateChangeSuccess::NoPreroll;
|
||||
}
|
||||
gst::StateChange::PausedToReady => {
|
||||
self.stop().map_err(|_| gst::StateChangeError)?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(success)
|
||||
}
|
||||
}
|
17
generic/threadshare/src/audiotestsrc/mod.rs
Normal file
17
generic/threadshare/src/audiotestsrc/mod.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct AudioTestSrc(ObjectSubclass<imp::AudioTestSrc>) @extends gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"ts-audiotestsrc",
|
||||
gst::Rank::None,
|
||||
AudioTestSrc::static_type(),
|
||||
)
|
||||
}
|
|
@ -28,7 +28,7 @@ use gst_rtp::RTPBuffer;
|
|||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use std::cmp::{max, min, Ordering};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeSet, VecDeque};
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
|
@ -412,7 +412,7 @@ impl SinkHandler {
|
|||
}
|
||||
|
||||
if let Some(last_in_seqnum) = inner.last_in_seqnum {
|
||||
let gap = gst_rtp::compare_seqnum(last_in_seqnum as u16, seq);
|
||||
let gap = gst_rtp::compare_seqnum(last_in_seqnum, seq);
|
||||
if gap == 1 {
|
||||
self.calculate_packet_spacing(inner, &mut state, rtptime, pts);
|
||||
} else {
|
||||
|
@ -463,7 +463,7 @@ impl SinkHandler {
|
|||
state.equidistant += 1;
|
||||
}
|
||||
|
||||
state.equidistant = min(max(state.equidistant, -7), 7);
|
||||
state.equidistant = state.equidistant.clamp(-7, 7);
|
||||
|
||||
inner.last_rtptime = Some(rtptime);
|
||||
|
||||
|
@ -679,7 +679,7 @@ impl SrcHandler {
|
|||
// FIXME reason why we can expect Some for the 2 lines below
|
||||
let mut last_popped_pts = state.last_popped_pts.unwrap();
|
||||
let interval = pts.into().unwrap().saturating_sub(last_popped_pts);
|
||||
let spacing = interval / (gap as u64 + 1);
|
||||
let spacing = interval / (gap + 1);
|
||||
|
||||
*discont = true;
|
||||
|
||||
|
@ -1259,7 +1259,7 @@ impl JitterBuffer {
|
|||
|
||||
self.task
|
||||
.prepare(
|
||||
JitterBufferTask::new(&*self.obj(), &self.src_pad_handler, &self.sink_pad_handler),
|
||||
JitterBufferTask::new(&self.obj(), &self.src_pad_handler, &self.sink_pad_handler),
|
||||
context,
|
||||
)
|
||||
.block_on()?;
|
||||
|
|
|
@ -16,29 +16,30 @@
|
|||
#[macro_use]
|
||||
pub mod runtime;
|
||||
|
||||
pub mod socket;
|
||||
mod tcpclientsrc;
|
||||
mod udpsink;
|
||||
mod udpsrc;
|
||||
|
||||
mod appsrc;
|
||||
mod audiotestsrc;
|
||||
pub mod dataqueue;
|
||||
mod inputselector;
|
||||
mod jitterbuffer;
|
||||
mod proxy;
|
||||
mod queue;
|
||||
pub mod socket;
|
||||
mod tcpclientsrc;
|
||||
mod udpsink;
|
||||
mod udpsrc;
|
||||
|
||||
use gst::glib;
|
||||
|
||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
udpsrc::register(plugin)?;
|
||||
udpsink::register(plugin)?;
|
||||
tcpclientsrc::register(plugin)?;
|
||||
queue::register(plugin)?;
|
||||
proxy::register(plugin)?;
|
||||
appsrc::register(plugin)?;
|
||||
jitterbuffer::register(plugin)?;
|
||||
audiotestsrc::register(plugin)?;
|
||||
inputselector::register(plugin)?;
|
||||
jitterbuffer::register(plugin)?;
|
||||
proxy::register(plugin)?;
|
||||
queue::register(plugin)?;
|
||||
tcpclientsrc::register(plugin)?;
|
||||
udpsink::register(plugin)?;
|
||||
udpsrc::register(plugin)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -336,13 +336,13 @@ fn eos() {
|
|||
.name("src-eos")
|
||||
.property("caps", &caps)
|
||||
.property("do-timestamp", true)
|
||||
.property("context", &CONTEXT)
|
||||
.property("context", CONTEXT)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let queue = gst::ElementFactory::make("ts-queue")
|
||||
.name("queue-eos")
|
||||
.property("context", &CONTEXT)
|
||||
.property("context", CONTEXT)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
|
@ -636,7 +636,7 @@ fn socket_play_null_play() {
|
|||
let sink = gst::ElementFactory::make("ts-udpsink")
|
||||
.name(format!("sink-{}", TEST).as_str())
|
||||
.property("socket", &socket)
|
||||
.property("context", &TEST)
|
||||
.property("context", TEST)
|
||||
.property("context-wait", 20u32)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
|
|
@ -100,6 +100,6 @@ fn test_chain() {
|
|||
assert!(buf == [42, 43, 44, 45, 0]);
|
||||
});
|
||||
|
||||
let buf = gst::Buffer::from_slice(&[42, 43, 44, 45]);
|
||||
let buf = gst::Buffer::from_slice([42, 43, 44, 45]);
|
||||
assert!(h.push(buf) == Ok(gst::FlowSuccess::Ok));
|
||||
}
|
||||
|
|
524
meson.build
524
meson.build
|
@ -1,11 +1,16 @@
|
|||
project('gst-plugins-rs',
|
||||
'rust',
|
||||
'c',
|
||||
version: '0.9.0-alpha.1',
|
||||
version: '0.9.5',
|
||||
meson_version : '>= 0.60')
|
||||
|
||||
python = import('python').find_installation()
|
||||
# dependencies.py needs a toml parsing module
|
||||
python = import('python').find_installation(modules: ['tomllib'], required: false)
|
||||
if not python.found()
|
||||
python = import('python').find_installation(modules: ['tomli'])
|
||||
endif
|
||||
fs = import('fs')
|
||||
host_system = host_machine.system()
|
||||
|
||||
if get_option('debug')
|
||||
target = 'debug'
|
||||
|
@ -23,9 +28,9 @@ if not cargo_c.found()
|
|||
endif
|
||||
|
||||
system = build_machine.system()
|
||||
ext_exe = ''
|
||||
exe_suffix = ''
|
||||
if system == 'windows'
|
||||
ext_exe = 'exe'
|
||||
exe_suffix = '.exe'
|
||||
ext_dynamic = 'dll'
|
||||
ext_static = 'lib'
|
||||
elif system == 'darwin'
|
||||
|
@ -36,135 +41,18 @@ else
|
|||
ext_static = 'a'
|
||||
endif
|
||||
|
||||
# workspace name -> lib name
|
||||
# kept in the same order as the `members` list in Cargo.toml
|
||||
plugins = {
|
||||
'gst-plugin-audiofx': 'libgstrsaudiofx',
|
||||
'gst-plugin-claxon': 'libgstclaxon',
|
||||
# csound has an external dependency, see below
|
||||
'gst-plugin-lewton': 'libgstlewton',
|
||||
'gst-plugin-spotify': 'libgstspotify',
|
||||
|
||||
'gst-plugin-file': 'libgstrsfile',
|
||||
# sodium has an external dependency, see below
|
||||
'gst-plugin-threadshare': 'libgstthreadshare',
|
||||
|
||||
'gst-plugin-fmp4': 'libgstfmp4',
|
||||
|
||||
'gst-plugin-aws': 'libgstaws',
|
||||
'gst-plugin-hlssink3': 'libgsthlssink3',
|
||||
'gst-plugin-ndi': 'libgstndi',
|
||||
'gst-plugin-onvif': 'libgstrsonvif',
|
||||
'gst-plugin-raptorq': 'libgstraptorq',
|
||||
'gst-plugin-reqwest': 'libgstreqwest',
|
||||
'gst-plugin-rtp': 'libgstrsrtp',
|
||||
'gst-plugin-webrtchttp': 'libgstwebrtchttp',
|
||||
'gst-plugin-webrtc': 'libgstrswebrtc',
|
||||
|
||||
'gst-plugin-textahead': 'libgsttextahead',
|
||||
'gst-plugin-json': 'libgstjson',
|
||||
'gst-plugin-regex': 'libgstregex',
|
||||
'gst-plugin-textwrap': 'libgsttextwrap',
|
||||
|
||||
'gst-plugin-fallbackswitch': 'libgstfallbackswitch',
|
||||
'gst-plugin-togglerecord': 'libgsttogglerecord',
|
||||
'gst-plugin-tracers': 'libgstrstracers',
|
||||
'gst-plugin-uriplaylistbin': 'libgsturiplaylistbin',
|
||||
|
||||
'gst-plugin-cdg': 'libgstcdg',
|
||||
# closedcaption has an external dependency, see below
|
||||
# dav1d has an external dependency, see below
|
||||
'gst-plugin-ffv1': 'libgstffv1',
|
||||
'gst-plugin-flavors': 'libgstrsflv',
|
||||
'gst-plugin-gif': 'libgstgif',
|
||||
# gtk4 has an external dependency, see below
|
||||
'gst-plugin-hsv': 'libgsthsv',
|
||||
'gst-plugin-png': 'libgstrspng',
|
||||
'gst-plugin-rav1e': 'libgstrav1e',
|
||||
# videofx has an external dependency, see below
|
||||
|
||||
# FIXME: libwebp-sys2 will build its bundled version on msvc and apple platforms
|
||||
# https://github.com/qnighy/libwebp-sys2-rs/issues/4
|
||||
'gst-plugin-webp': 'libgstrswebp',
|
||||
}
|
||||
|
||||
# Extra env to pass to cargo
|
||||
extra_env = {}
|
||||
|
||||
if dependency('cairo-gobject', required : get_option('videofx')).found()
|
||||
plugins += {'gst-plugin-videofx': 'libgstrsvideofx',}
|
||||
endif
|
||||
|
||||
if dependency('pangocairo', required : get_option('closedcaption')).found()
|
||||
plugins += {'gst-plugin-closedcaption' : 'libgstrsclosedcaption',}
|
||||
endif
|
||||
|
||||
if dependency('dav1d', version : '>= 1.0.0', required : get_option('dav1d')).found()
|
||||
plugins += {'gst-plugin-dav1d' : 'libgstdav1d'}
|
||||
endif
|
||||
|
||||
sodium = get_option ('sodium')
|
||||
if sodium == 'system'
|
||||
dependency('libsodium')
|
||||
plugins += {'gst-plugin-sodium': 'libgstsodium'}
|
||||
extra_env += {'SODIUM_USE_PKG_CONFIG': '1'}
|
||||
elif sodium == 'built-in'
|
||||
plugins += {'gst-plugin-sodium': 'libgstsodium'}
|
||||
endif
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
csound_option = get_option('csound')
|
||||
# try first to find csound using pkg-config
|
||||
csound_dep = dependency('', required: false)
|
||||
if not csound_dep.found() and not csound_option.disabled()
|
||||
# if csound isn't distributed with pkg-config then user needs to define CSOUND_LIB_DIR with its location
|
||||
|
||||
res = run_command(python, '-c', 'import os; print(os.environ["CSOUND_LIB_DIR"])', check: false)
|
||||
if res.returncode() == 0
|
||||
csound_libdir = res.stdout().strip()
|
||||
csound_dep = cc.find_library('csound64', dirs: csound_libdir, required: false)
|
||||
if csound_dep.found()
|
||||
extra_env += {'CSOUND_LIB_DIR': csound_libdir}
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
if csound_dep.found()
|
||||
plugins += {'gst-plugin-csound' : 'libgstcsound'}
|
||||
elif csound_option.enabled()
|
||||
error('csound option is enabled, but csound64 library could not be found and CSOUND_LIB_DIR was not set')
|
||||
else
|
||||
message('csound not found, disabling its plugin')
|
||||
endif
|
||||
|
||||
if dependency('gtk4', required : get_option('gtk4')).found()
|
||||
plugins += {'gst-plugin-gtk4' : 'libgstgtk4',}
|
||||
endif
|
||||
|
||||
output = []
|
||||
|
||||
extensions = []
|
||||
|
||||
# Add the plugin file as output
|
||||
if get_option('default_library') == 'shared' or get_option('default_library') == 'both'
|
||||
extensions += [ext_dynamic]
|
||||
foreach p, lib : plugins
|
||||
output += [lib + '.' + ext_dynamic]
|
||||
endforeach
|
||||
endif
|
||||
|
||||
static_build = get_option('default_library') == 'static'
|
||||
if static_build or get_option('default_library') == 'both'
|
||||
extensions += [ext_static]
|
||||
foreach p, lib : plugins
|
||||
output += [lib + '.' + ext_static]
|
||||
endforeach
|
||||
endif
|
||||
# Used to not lookup the same dependency multiple times which clutters logs
|
||||
deps_cache = {}
|
||||
|
||||
# Need to depends on all gstreamer-rs deps to ensure they are built
|
||||
# before gstreamer-rs when building with gst-build.
|
||||
# Custom targets can't depend on dependency() objects so we have to depend
|
||||
# on the library variable from the subproject instead.
|
||||
gst_req = '>= 1.18.0'
|
||||
glib_req = '>=2.62'
|
||||
gst_req = '>=1.20.0'
|
||||
depends = []
|
||||
|
||||
deps = [
|
||||
|
@ -174,6 +62,7 @@ deps = [
|
|||
['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'],
|
||||
|
@ -181,8 +70,8 @@ deps = [
|
|||
['gstreamer-webrtc-1.0', 'gst-plugins-bad', 'gstwebrtc_dep', 'gstwebrtc'],
|
||||
]
|
||||
|
||||
# Used to not lookup the same dependency multiple times which clutters logs
|
||||
deps_cache = {}
|
||||
glib_dep = dependency('glib-2.0', version: glib_req)
|
||||
deps_cache += {'glib-2.0': glib_dep}
|
||||
|
||||
foreach d: deps
|
||||
dep = dependency(d[0], version: gst_req,
|
||||
|
@ -195,22 +84,315 @@ foreach d: deps
|
|||
endif
|
||||
endforeach
|
||||
|
||||
include = ','.join(plugins.keys())
|
||||
# kept in the same order as the `members` list in Cargo.toml
|
||||
plugins = {
|
||||
'audiofx': {
|
||||
'library': 'libgstrsaudiofx',
|
||||
'examples': ['hrtfrender'],
|
||||
},
|
||||
'claxon': {'library': 'libgstclaxon'},
|
||||
# csound has a non-trivial external dependency, see below
|
||||
'lewton': {'library': 'libgstlewton'},
|
||||
'spotify': {'library': 'libgstspotify'},
|
||||
|
||||
# serialize extra_env
|
||||
extra_env_list = []
|
||||
foreach key, value : extra_env
|
||||
extra_env_list += key + ':' + value
|
||||
'file': {'library': 'libgstrsfile'},
|
||||
# sodium can have an external dependency, see below
|
||||
'threadshare': {
|
||||
'library': 'libgstthreadshare',
|
||||
'examples': [
|
||||
'ts-benchmark',
|
||||
'udpsrc-benchmark-sender',
|
||||
'tcpclientsrc-benchmark-sender',
|
||||
'ts-standalone',
|
||||
],
|
||||
},
|
||||
|
||||
'mp4': {'library': 'libgstmp4'},
|
||||
'fmp4': {
|
||||
'library': 'libgstfmp4',
|
||||
'examples': [
|
||||
'dash_vod',
|
||||
'hls_live',
|
||||
'hls_vod',
|
||||
],
|
||||
},
|
||||
|
||||
'aws': {
|
||||
'library': 'libgstaws',
|
||||
'extra-deps': {'openssl': '>=1.1'},
|
||||
},
|
||||
'hlssink3': {'library': 'libgsthlssink3'},
|
||||
'ndi': {'library': 'libgstndi'},
|
||||
'onvif': {
|
||||
'library': 'libgstrsonvif',
|
||||
'extra-deps': {'pangocairo': ''},
|
||||
},
|
||||
'raptorq': {'library': 'libgstraptorq'},
|
||||
'reqwest': {'library': 'libgstreqwest'},
|
||||
'rtp': {'library': 'libgstrsrtp'},
|
||||
'webrtchttp': {'library': 'libgstwebrtchttp'},
|
||||
'webrtc': {
|
||||
'library': 'libgstrswebrtc',
|
||||
'examples': ['webrtcsink-stats-server'],
|
||||
},
|
||||
|
||||
'textahead': {'library': 'libgsttextahead'},
|
||||
'json': {'library': 'libgstjson'},
|
||||
'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',
|
||||
'examples': ['playlist'],
|
||||
'features': ['clap'],
|
||||
},
|
||||
|
||||
'cdg': {'library': 'libgstcdg'},
|
||||
'closedcaption': {
|
||||
'library': 'libgstrsclosedcaption',
|
||||
'extra-deps': {
|
||||
'pango': '',
|
||||
'pangocairo': '',
|
||||
'cairo-gobject': '',
|
||||
}
|
||||
},
|
||||
'dav1d': {
|
||||
'library': 'libgstdav1d',
|
||||
'extra-deps': {'dav1d': '>=1.0'},
|
||||
},
|
||||
'ffv1': {'library': 'libgstffv1'},
|
||||
'flavors': {'library': 'libgstrsflv'},
|
||||
'gif': {
|
||||
'library': 'libgstgif',
|
||||
'examples': ['testvideosrc2gif'],
|
||||
},
|
||||
# gtk4 is added below
|
||||
'hsv': {'library': 'libgsthsv'},
|
||||
'png': {
|
||||
'library': 'libgstrspng',
|
||||
'examples': ['pngenc'],
|
||||
},
|
||||
'rav1e': {'library': 'libgstrav1e'},
|
||||
'videofx': {
|
||||
'library': 'libgstrsvideofx',
|
||||
'extra-deps': {'cairo-gobject': ''},
|
||||
},
|
||||
}
|
||||
|
||||
# Won't build on platforms where it bundles the sources because of:
|
||||
# https://github.com/qnighy/libwebp-sys2-rs/issues/12
|
||||
# the fix is:
|
||||
# https://github.com/qnighy/libwebp-sys2-rs/pull/13
|
||||
if host_system not in ['windows', 'darwin']
|
||||
# FIXME: libwebp-sys2 will build its bundled version on msvc and apple platforms
|
||||
# https://github.com/qnighy/libwebp-sys2-rs/issues/4
|
||||
plugins += {'webp': {
|
||||
'library': 'libgstrswebp',
|
||||
'extra-deps': {'libwebpdemux': ''},
|
||||
}}
|
||||
endif
|
||||
|
||||
sodium_opt = get_option('sodium')
|
||||
if sodium_opt.allowed()
|
||||
sodium_plugin = {'sodium': {
|
||||
'library': 'libgstsodium',
|
||||
'examples': ['generate-keys', 'encrypt-example', 'decrypt-example'],
|
||||
'features': ['serde', 'serde_json', 'clap'],
|
||||
}}
|
||||
if get_option('sodium-source') == 'system'
|
||||
sodium_dep = dependency('libsodium', required: sodium_opt.enabled())
|
||||
extra_env += {'SODIUM_USE_PKG_CONFIG': '1'}
|
||||
if sodium_dep.found()
|
||||
plugins += sodium_plugin
|
||||
endif
|
||||
else
|
||||
plugins += sodium_plugin
|
||||
endif
|
||||
endif
|
||||
|
||||
cc = meson.get_compiler('c')
|
||||
csound_option = get_option('csound')
|
||||
if csound_option.allowed()
|
||||
# if csound isn't distributed with pkg-config then user needs to define CSOUND_LIB_DIR with its location
|
||||
|
||||
res = run_command(python, '-c', 'import os; print(os.environ["CSOUND_LIB_DIR"])', check: false)
|
||||
if res.returncode() == 0
|
||||
csound_libdir = res.stdout().strip()
|
||||
csound_dep = cc.find_library('csound64', dirs: csound_libdir, required: false)
|
||||
if csound_dep.found()
|
||||
plugins += {'csound': {
|
||||
'library': 'libgstcsound',
|
||||
'examples': ['csound-effect'],
|
||||
}}
|
||||
extra_env += {'CSOUND_LIB_DIR': csound_libdir}
|
||||
elif csound_option.enabled()
|
||||
error('csound option is enabled, but csound64 library could not be found and CSOUND_LIB_DIR was not set')
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
if get_option('gtk4').allowed()
|
||||
gtk4_features = []
|
||||
gl_winsys = gst_gl_dep.get_variable('gl_winsys').split()
|
||||
gl_platforms = gst_gl_dep.get_variable('gl_platforms').split()
|
||||
if host_system == 'linux'
|
||||
if 'wayland' in gl_winsys
|
||||
gtk4_features += 'wayland'
|
||||
endif
|
||||
if 'x11' in gl_winsys
|
||||
if 'egl' in gl_platforms
|
||||
gtk4_features += 'x11egl'
|
||||
endif
|
||||
if 'glx' in gl_platforms
|
||||
gtk4_features += 'x11glx'
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
plugins += {'gtk4': {
|
||||
'library': 'libgstgtk4',
|
||||
'examples': ['gtksink'],
|
||||
'extra-deps': {'gtk4': '>=4.6'},
|
||||
'features': gtk4_features,
|
||||
}}
|
||||
endif
|
||||
|
||||
# Process plugins list
|
||||
|
||||
default_library = get_option('default_library')
|
||||
|
||||
library_suffixes = []
|
||||
if default_library in ['shared', 'both']
|
||||
library_suffixes += [ext_dynamic]
|
||||
endif
|
||||
if default_library in ['static', 'both']
|
||||
library_suffixes += [ext_static]
|
||||
endif
|
||||
|
||||
# cargo packages (plugins) to build
|
||||
packages = []
|
||||
# cargo features
|
||||
features = []
|
||||
# examples to build
|
||||
examples = []
|
||||
# Add the plugin library files as output
|
||||
output = []
|
||||
|
||||
if get_option('gtk4').allowed()
|
||||
if glib_dep.version().version_compare('>=2.74')
|
||||
features += ['glib/v2_74', 'gio/v2_74']
|
||||
elif glib_dep.version().version_compare('>=2.72')
|
||||
features += ['glib/v2_72', 'gio/v2_72']
|
||||
elif glib_dep.version().version_compare('>=2.70')
|
||||
features += ['glib/v2_70', 'gio/v2_70']
|
||||
elif glib_dep.version().version_compare('>=2.68')
|
||||
features += ['glib/v2_68', 'gio/v2_68']
|
||||
elif glib_dep.version().version_compare('>=2.66')
|
||||
features += ['glib/v2_66', 'gio/v2_66']
|
||||
elif glib_dep.version().version_compare('>=2.64')
|
||||
features += ['glib/v2_64', 'gio/v2_64']
|
||||
elif glib_dep.version().version_compare('>=2.62')
|
||||
features += ['glib/v2_62', 'gio/v2_62']
|
||||
elif glib_dep.version().version_compare('>=2.60')
|
||||
features += ['glib/v2_60', 'gio/v2_60']
|
||||
elif glib_dep.version().version_compare('>=2.58')
|
||||
features += ['glib/v2_58', 'gio/v2_58']
|
||||
endif
|
||||
endif
|
||||
|
||||
if gst_dep.version().version_compare('>=1.21')
|
||||
components = [
|
||||
'', '-app', '-audio', '-base', '-check',
|
||||
'-rtp', '-sdp', '-utils', '-video', '-webrtc',
|
||||
]
|
||||
if get_option('tracers').allowed()
|
||||
components += '-plugin-tracers'
|
||||
endif
|
||||
if get_option('threadshare').allowed()
|
||||
components += '-net'
|
||||
endif
|
||||
if get_option('mp4').allowed() or get_option('fmp4').allowed()
|
||||
components += '-pbutils'
|
||||
endif
|
||||
foreach c: components
|
||||
features += f'gst@c@/v1_22'
|
||||
endforeach
|
||||
extra_env_str = ','.join(extra_env_list)
|
||||
if get_option('webrtc').allowed()
|
||||
features += 'gst-plugin-webrtc/gst1_22'
|
||||
endif
|
||||
endif
|
||||
|
||||
if get_option('rav1e').allowed() and find_program('nasm', required: false).found()
|
||||
features += 'gst-plugin-rav1e/asm'
|
||||
endif
|
||||
|
||||
foreach plugin_name, details: plugins
|
||||
plugin_opt = get_option(plugin_name)
|
||||
if plugin_opt.allowed()
|
||||
plugin_deps_found = true
|
||||
foreach dep_name, dep_ver: details.get('extra-deps', {})
|
||||
if dep_ver != ''
|
||||
dep = dependency(dep_name, version: dep_ver, required: plugin_opt)
|
||||
else
|
||||
dep = dependency(dep_name, required: plugin_opt)
|
||||
endif
|
||||
deps_cache += {dep_name: dep}
|
||||
if not dep.found()
|
||||
plugin_deps_found = false
|
||||
endif
|
||||
endforeach
|
||||
if plugin_deps_found
|
||||
packages += f'gst-plugin-@plugin_name@'
|
||||
features += details.get('features', [])
|
||||
examples += details.get('examples', [])
|
||||
lib = details.get('library')
|
||||
if default_library in ['shared', 'both']
|
||||
output += [lib + '.' + ext_dynamic]
|
||||
endif
|
||||
if default_library in ['static', 'both']
|
||||
output += [lib + '.' + ext_static]
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endforeach
|
||||
|
||||
|
||||
plugins_install_dir = get_option('libdir') / 'gstreamer-1.0'
|
||||
pkgconfig_install_dir = get_option('libdir') / 'pkgconfig'
|
||||
|
||||
extra_args = []
|
||||
if get_option('doc').disabled()
|
||||
disable_doc = ['--disable-doc']
|
||||
else
|
||||
disable_doc = []
|
||||
extra_args = ['--disable-doc']
|
||||
endif
|
||||
|
||||
# 'pkgconfig' is the entry in the machine file, if specified
|
||||
pkg_config = find_program('pkgconfig', required: false)
|
||||
if pkg_config.found()
|
||||
extra_env += {'PKG_CONFIG': pkg_config.full_path()}
|
||||
endif
|
||||
|
||||
pkg_config_path = get_option('pkg_config_path')
|
||||
if pkg_config_path.length() > 0
|
||||
pathsep = ':'
|
||||
if host_system == 'windows'
|
||||
pathsep = ';'
|
||||
endif
|
||||
extra_env += {'PKG_CONFIG_PATH': pathsep.join(pkg_config_path)}
|
||||
endif
|
||||
|
||||
rs_plugins = custom_target('gst-plugins-rs',
|
||||
|
@ -221,19 +403,20 @@ rs_plugins = custom_target('gst-plugins-rs',
|
|||
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,
|
||||
include,
|
||||
extra_env_str,
|
||||
get_option('prefix'),
|
||||
get_option('libdir'),
|
||||
'--packages', packages,
|
||||
'--features', features,
|
||||
'--depfile', '@DEPFILE@',
|
||||
'--exts', extensions,
|
||||
] + disable_doc)
|
||||
'--lib-suffixes', library_suffixes,
|
||||
] + extra_args)
|
||||
|
||||
plugins = rs_plugins.to_list()
|
||||
|
||||
|
@ -245,6 +428,17 @@ foreach plugin : plugins
|
|||
# skip the 'lib' prefix and extension from plugin path
|
||||
plugin_name = fs.stem(plugin.full_path()).substring(3)
|
||||
|
||||
option_name = plugin_name.substring(3)
|
||||
if option_name.startswith('rs')
|
||||
option_name = option_name.substring(2)
|
||||
endif
|
||||
if option_name == 'flv'
|
||||
option_name = 'flavors'
|
||||
endif
|
||||
if not get_option(option_name).allowed()
|
||||
continue
|
||||
endif
|
||||
|
||||
# Extract plugin dependencies from their Cargo.toml file
|
||||
plugin_deps = []
|
||||
p = run_command('dependencies.py', meson.current_source_dir(), plugin_name,
|
||||
|
@ -252,9 +446,9 @@ foreach plugin : plugins
|
|||
check: true)
|
||||
foreach dep_name : p.stdout().strip().split(',')
|
||||
dep_name_version = dep_name.split('|')
|
||||
dep_name = dep_name_version.get(0)
|
||||
dep_name = dep_name_version.get(0).strip()
|
||||
if dep_name_version.length() > 1
|
||||
extras = {'version': dep_name_version.get(1)}
|
||||
extras = {'version': dep_name_version.get(1).strip()}
|
||||
else
|
||||
extras = {}
|
||||
endif
|
||||
|
@ -274,11 +468,10 @@ foreach plugin : plugins
|
|||
)
|
||||
meson.override_dependency(plugin_name, dep)
|
||||
|
||||
if static_build and plugin_name in ['gstcsound', 'gstthreadshare']
|
||||
if default_library == 'static' and plugin_name in ['gstcsound', 'gstthreadshare', 'gstgtk4']
|
||||
warning('Static plugin @0@ is known to fail. It will not be included in libgstreamer-full.'.format(plugin_name))
|
||||
else
|
||||
gst_plugins += dep
|
||||
endif
|
||||
|
||||
pc_files += [plugin_name + '.pc']
|
||||
if plugin_name.startswith('gst')
|
||||
|
@ -286,6 +479,7 @@ foreach plugin : plugins
|
|||
else
|
||||
plugin_names += [plugin_name]
|
||||
endif
|
||||
endif
|
||||
endforeach
|
||||
|
||||
subdir('docs')
|
||||
|
@ -302,7 +496,7 @@ custom_target('gst-plugins-rs-pc-files',
|
|||
depends: rs_plugins,
|
||||
command: [python, '-c', '""'])
|
||||
|
||||
|
||||
if get_option('webrtc').allowed()
|
||||
custom_target('gst-webrtc-signalling-server',
|
||||
build_by_default: true,
|
||||
output: 'gst-webrtc-signalling-server',
|
||||
|
@ -310,32 +504,56 @@ custom_target('gst-webrtc-signalling-server',
|
|||
install: true,
|
||||
install_dir: get_option('bindir'),
|
||||
depfile: 'gst-webrtc-signalling-server.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'),
|
||||
'--depfile', '@DEPFILE@',
|
||||
'--exts', ext_exe,
|
||||
'--bin', 'gst-webrtc-signalling-server'
|
||||
'--bin', 'gst-webrtc-signalling-server',
|
||||
'--exe-suffix', exe_suffix,
|
||||
])
|
||||
endif
|
||||
|
||||
if get_option('examples').allowed() and examples.length() > 0
|
||||
custom_target('gst-plugins-rs-examples',
|
||||
build_by_default: true,
|
||||
output: examples,
|
||||
console: true,
|
||||
install: false,
|
||||
depfile: 'gst-plugins-rs-examples.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'),
|
||||
'--depfile', '@DEPFILE@',
|
||||
'--packages', packages,
|
||||
'--features', features,
|
||||
'--examples', examples,
|
||||
'--exe-suffix', exe_suffix,
|
||||
])
|
||||
endif
|
||||
|
||||
test('tests',
|
||||
cargo_wrapper,
|
||||
env: extra_env,
|
||||
args: ['test',
|
||||
meson.current_build_dir(),
|
||||
meson.current_source_dir(),
|
||||
meson.global_build_root(),
|
||||
target,
|
||||
include,
|
||||
extra_env_str,
|
||||
get_option('prefix'),
|
||||
get_option('libdir')],
|
||||
get_option('libdir'),
|
||||
'--packages', packages],
|
||||
timeout: 600)
|
||||
|
||||
summary({
|
||||
|
|
|
@ -1,12 +1,64 @@
|
|||
option('videofx', type : 'feature', value : 'auto', description : 'Build videofx plugin')
|
||||
# Same order as members in Cargo.toml
|
||||
|
||||
# audio
|
||||
option('audiofx', type: 'feature', value: 'auto', description: 'Build audiofx plugin')
|
||||
option('claxon', type: 'feature', value: 'auto', description: 'Build claxon plugin')
|
||||
option('csound', type: 'feature', value: 'auto', description: 'Build csound plugin')
|
||||
option('lewton', type: 'feature', value: 'auto', description: 'Build lewton plugin')
|
||||
option('spotify', type: 'feature', value: 'auto', description: 'Build spotify plugin')
|
||||
|
||||
# generic
|
||||
option('file', type: 'feature', value: 'auto', description: 'Build file plugin')
|
||||
option('sodium', type: 'feature', value: 'auto', description: 'Build sodium plugin')
|
||||
option('sodium-source', type: 'combo',
|
||||
choices: ['system', 'built-in'], value: 'built-in',
|
||||
description: 'Whether to use libsodium from the system or the built-in version from the sodiumoxide crate')
|
||||
option('threadshare', type: 'feature', value: 'auto', description: 'Build threadshare plugin')
|
||||
|
||||
# mux
|
||||
option('flavors', type: 'feature', value: 'auto', description: 'Build flavors plugin')
|
||||
option('fmp4', type: 'feature', value: 'auto', description: 'Build fmp4 plugin')
|
||||
option('mp4', type: 'feature', value: 'auto', description: 'Build mp4 plugin')
|
||||
|
||||
# net
|
||||
option('aws', type: 'feature', value: 'auto', description: 'Build aws plugin')
|
||||
option('hlssink3', type: 'feature', value: 'auto', description: 'Build hlssink3 plugin')
|
||||
option('ndi', type: 'feature', value: 'auto', description: 'Build ndi plugin')
|
||||
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('webrtchttp', type: 'feature', value: 'auto', description: 'Build webrtchttp plugin')
|
||||
|
||||
# text
|
||||
option('textahead', type: 'feature', value: 'auto', description: 'Build textahead plugin')
|
||||
option('json', type: 'feature', value: 'auto', description: 'Build json plugin')
|
||||
option('regex', type: 'feature', value: 'auto', description: 'Build regex plugin')
|
||||
option('textwrap', type: 'feature', value: 'auto', description: 'Build textwrap plugin')
|
||||
|
||||
# utils
|
||||
option('fallbackswitch', type: 'feature', value: 'auto', description: 'Build fallbackswitch plugin')
|
||||
option('livesync', type: 'feature', value: 'auto', description: 'Build livesync plugin')
|
||||
option('togglerecord', type: 'feature', value: 'auto', description: 'Build togglerecord plugin')
|
||||
option('tracers', type: 'feature', value: 'auto', description: 'Build tracers plugin')
|
||||
option('uriplaylistbin', type: 'feature', value: 'auto', description: 'Build uriplaylistbin plugin')
|
||||
|
||||
# video
|
||||
option('cdg', type: 'feature', value: 'auto', description: 'Build cdg plugin')
|
||||
option('closedcaption', type: 'feature', value: 'auto', description: 'Build closedcaption plugin')
|
||||
option('dav1d', type: 'feature', value: 'auto', description: 'Build dav1d plugin')
|
||||
option('sodium', type : 'combo',
|
||||
choices : ['system', 'built-in', 'disabled'], value : 'built-in',
|
||||
description : 'Weither to use libsodium from the system or the built-in version from the sodiumoxide crate')
|
||||
option('csound', type : 'feature', value : 'auto', description : 'Build csound plugin')
|
||||
option('ffv1', type: 'feature', value: 'auto', description: 'Build ffv1 plugin')
|
||||
option('gif', type: 'feature', value: 'auto', description: 'Build gif plugin')
|
||||
option('gtk4', type: 'feature', value: 'auto', description: 'Build GTK4 plugin')
|
||||
option('hsv', type: 'feature', value: 'auto', description: 'Build hsv plugin')
|
||||
option('png', type: 'feature', value: 'auto', description: 'Build png plugin')
|
||||
option('rav1e', type: 'feature', value: 'auto', description: 'Build rav1e plugin')
|
||||
option('videofx', type: 'feature', value: 'auto', description: 'Build videofx plugin')
|
||||
option('webp', type: 'feature', value: 'auto', description: 'Build webp plugin')
|
||||
|
||||
# Common options
|
||||
option('doc', type: 'feature', value: 'auto', yield: true,
|
||||
description: 'Enable documentation.')
|
||||
description: 'Enable documentation')
|
||||
option('examples', type: 'feature', value: 'disabled', yield: true,
|
||||
description: 'Build examples')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-flavors"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
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.63"
|
|||
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.19", version = "0.19.1" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
num-rational = { version = "0.4", default-features = false, features = [] }
|
||||
nom = "7"
|
||||
flavors = { git = "https://github.com/rust-av/flavors" }
|
||||
|
@ -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 = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -463,7 +463,7 @@ impl FlvDemux {
|
|||
match *state {
|
||||
State::Stopped => unreachable!(),
|
||||
State::NeedHeader => {
|
||||
let header = match self.find_header(&mut *adapter) {
|
||||
let header = match self.find_header(&mut adapter) {
|
||||
Ok(header) => header,
|
||||
Err(_) => {
|
||||
gst::trace!(CAT, imp: self, "Need more data");
|
||||
|
@ -503,7 +503,7 @@ impl FlvDemux {
|
|||
*skip_left -= skip as u32;
|
||||
}
|
||||
State::Streaming(ref mut sstate) => {
|
||||
let res = sstate.handle_tag(self, &mut *adapter);
|
||||
let res = sstate.handle_tag(self, &mut adapter);
|
||||
|
||||
match res {
|
||||
Ok(None) => {
|
||||
|
@ -533,7 +533,7 @@ impl FlvDemux {
|
|||
while adapter.available() >= 9 {
|
||||
let data = adapter.map(9).unwrap();
|
||||
|
||||
if let Ok((_, header)) = flavors::header(&*data) {
|
||||
if let Ok((_, header)) = flavors::header(&data) {
|
||||
gst::debug!(CAT, imp: self, "Found FLV header: {:?}", header);
|
||||
drop(data);
|
||||
adapter.flush(9);
|
||||
|
@ -745,7 +745,7 @@ impl StreamingState {
|
|||
|
||||
let data = adapter.map(tag_header.data_size as usize).unwrap();
|
||||
|
||||
match flavors::script_data(&*data) {
|
||||
match flavors::script_data(&data) {
|
||||
Ok((_, ref script_data)) if script_data.name == "onMetaData" => {
|
||||
gst::trace!(CAT, imp: imp, "Got script tag: {:?}", script_data);
|
||||
|
||||
|
@ -823,7 +823,9 @@ impl StreamingState {
|
|||
}
|
||||
}
|
||||
|
||||
if (!self.expect_video || self.video != None) && self.audio != None && !self.got_all_streams
|
||||
if (!self.expect_video || self.video.is_some())
|
||||
&& self.audio.is_some()
|
||||
&& !self.got_all_streams
|
||||
{
|
||||
gst::debug!(CAT, imp: imp, "Have all expected streams now");
|
||||
self.got_all_streams = true;
|
||||
|
@ -853,7 +855,7 @@ impl StreamingState {
|
|||
|
||||
let data = adapter.map(1).unwrap();
|
||||
|
||||
match flavors::aac_audio_packet_header(&*data) {
|
||||
match flavors::aac_audio_packet_header(&data) {
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Invalid AAC audio packet header: {:?}", err);
|
||||
drop(data);
|
||||
|
@ -894,7 +896,7 @@ impl StreamingState {
|
|||
assert!(adapter.available() >= tag_header.data_size as usize);
|
||||
|
||||
let data = adapter.map(1).unwrap();
|
||||
let data_header = match flavors::audio_data_header(&*data) {
|
||||
let data_header = match flavors::audio_data_header(&data) {
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Invalid audio data header: {:?}", err);
|
||||
drop(data);
|
||||
|
@ -925,7 +927,7 @@ impl StreamingState {
|
|||
return Ok(events);
|
||||
}
|
||||
|
||||
if self.audio == None {
|
||||
if self.audio.is_none() {
|
||||
adapter.flush((tag_header.data_size - offset) as usize);
|
||||
return Ok(events);
|
||||
}
|
||||
|
@ -983,7 +985,9 @@ impl StreamingState {
|
|||
}
|
||||
}
|
||||
|
||||
if (!self.expect_audio || self.audio != None) && self.video != None && !self.got_all_streams
|
||||
if (!self.expect_audio || self.audio.is_some())
|
||||
&& self.video.is_some()
|
||||
&& !self.got_all_streams
|
||||
{
|
||||
gst::debug!(CAT, imp: imp, "Have all expected streams now");
|
||||
self.got_all_streams = true;
|
||||
|
@ -1012,7 +1016,7 @@ impl StreamingState {
|
|||
}
|
||||
|
||||
let data = adapter.map(4).unwrap();
|
||||
match flavors::avc_video_packet_header(&*data) {
|
||||
match flavors::avc_video_packet_header(&data) {
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Invalid AVC video packet header: {:?}", err);
|
||||
drop(data);
|
||||
|
@ -1065,7 +1069,7 @@ impl StreamingState {
|
|||
assert!(adapter.available() >= tag_header.data_size as usize);
|
||||
|
||||
let data = adapter.map(1).unwrap();
|
||||
let data_header = match flavors::video_data_header(&*data) {
|
||||
let data_header = match flavors::video_data_header(&data) {
|
||||
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||
gst::error!(CAT, imp: imp, "Invalid video data header: {:?}", err);
|
||||
drop(data);
|
||||
|
@ -1101,7 +1105,7 @@ impl StreamingState {
|
|||
return Ok(events);
|
||||
}
|
||||
|
||||
if self.video == None {
|
||||
if self.video.is_none() {
|
||||
adapter.flush((tag_header.data_size - offset) as usize);
|
||||
return Ok(events);
|
||||
}
|
||||
|
@ -1419,7 +1423,7 @@ impl VideoFormat {
|
|||
flavors::CodecId::H264 => self.avc_sequence_header.as_ref().map(|header| {
|
||||
gst::Caps::builder("video/x-h264")
|
||||
.field("stream-format", "avc")
|
||||
.field("codec_data", &header)
|
||||
.field("codec_data", header)
|
||||
.build()
|
||||
}),
|
||||
flavors::CodecId::H263 => Some(gst::Caps::builder("video/x-h263").build()),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-fmp4"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||
license = "MPL-2.0"
|
||||
description = "GStreamer Fragmented MP4 Plugin"
|
||||
|
@ -10,12 +10,12 @@ rust-version = "1.63"
|
|||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
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 = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1", features = ["v1_18"] }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
once_cell = "1.0"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
[lib]
|
||||
name = "gstfmp4"
|
||||
|
@ -23,20 +23,20 @@ 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.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_20"] }
|
||||
m3u8-rs = "5.0"
|
||||
chrono = "0.4"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { path="../../version-helper" }
|
||||
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
default = ["v1_18"]
|
||||
default = []
|
||||
static = []
|
||||
capi = []
|
||||
v1_18 = ["gst-video/v1_18"]
|
||||
doc = []
|
||||
|
||||
[package.metadata.capi]
|
||||
min_version = "0.8.0"
|
||||
|
|
|
@ -225,7 +225,7 @@ fn main() -> Result<(), Error> {
|
|||
"###,
|
||||
duration = duration, segment_timeline = segment_timeline);
|
||||
|
||||
std::fs::write(path, &manifest).expect("failed to write manifest");
|
||||
std::fs::write(path, manifest).expect("failed to write manifest");
|
||||
})
|
||||
.build(),
|
||||
);
|
||||
|
|
|
@ -82,10 +82,8 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
|||
&& level <= ("3", "1")
|
||||
&& fps <= gst::Fraction::new(60, 1)
|
||||
{
|
||||
#[cfg(feature = "v1_18")]
|
||||
{
|
||||
if let Some(colorimetry) = colorimetry
|
||||
.and_then(|c| c.parse::<gst_video::VideoColorimetry>().ok())
|
||||
if let Some(colorimetry) =
|
||||
colorimetry.and_then(|c| c.parse::<gst_video::VideoColorimetry>().ok())
|
||||
{
|
||||
if matches!(
|
||||
colorimetry.primaries(),
|
||||
|
@ -107,21 +105,13 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
|||
// Assume it's OK
|
||||
compatible_brands.push(b"cfsd");
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "v1_18"))]
|
||||
{
|
||||
// Assume it's OK
|
||||
compatible_brands.push(b"cfsd");
|
||||
}
|
||||
} else if width <= 1920
|
||||
&& height <= 1080
|
||||
&& level <= ("4", "0")
|
||||
&& fps <= gst::Fraction::new(60, 1)
|
||||
{
|
||||
#[cfg(feature = "v1_18")]
|
||||
{
|
||||
if let Some(colorimetry) = colorimetry
|
||||
.and_then(|c| c.parse::<gst_video::VideoColorimetry>().ok())
|
||||
if let Some(colorimetry) =
|
||||
colorimetry.and_then(|c| c.parse::<gst_video::VideoColorimetry>().ok())
|
||||
{
|
||||
if matches!(
|
||||
colorimetry.primaries(),
|
||||
|
@ -139,12 +129,6 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
|||
// Assume it's OK
|
||||
compatible_brands.push(b"cfhd");
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "v1_18"))]
|
||||
{
|
||||
// Assume it's OK
|
||||
compatible_brands.push(b"cfhd");
|
||||
}
|
||||
} else if width <= 1920
|
||||
&& height <= 1080
|
||||
&& level <= ("4", "2")
|
||||
|
@ -271,7 +255,6 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
|||
&& level <= ("5", "1")
|
||||
&& fps <= gst::Fraction::new(60, 1)
|
||||
{
|
||||
#[cfg(feature = "v1_18")]
|
||||
if let Some(colorimetry) =
|
||||
colorimetry.and_then(|c| c.parse::<gst_video::VideoColorimetry>().ok())
|
||||
{
|
||||
|
@ -318,11 +301,6 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
|||
compatible_brands.push(b"cud1");
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "v1_18"))]
|
||||
{
|
||||
// Assume it's OK
|
||||
compatible_brands.push(b"cud1");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -355,7 +333,8 @@ fn brands_from_variant_and_caps<'a>(
|
|||
pub(super) fn create_fmp4_header(cfg: super::HeaderConfiguration) -> Result<gst::Buffer, Error> {
|
||||
let mut v = vec![];
|
||||
|
||||
let (brand, compatible_brands) = brands_from_variant_and_caps(cfg.variant, cfg.streams.iter());
|
||||
let (brand, compatible_brands) =
|
||||
brands_from_variant_and_caps(cfg.variant, cfg.streams.iter().map(|s| &s.caps));
|
||||
|
||||
write_box(&mut v, b"ftyp", |v| {
|
||||
// major brand
|
||||
|
@ -420,17 +399,17 @@ fn write_moov(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
|||
write_full_box(v, b"mvhd", FULL_BOX_VERSION_1, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_mvhd(v, cfg, creation_time)
|
||||
})?;
|
||||
for (idx, caps) in cfg.streams.iter().enumerate() {
|
||||
for (idx, stream) in cfg.streams.iter().enumerate() {
|
||||
write_box(v, b"trak", |v| {
|
||||
let mut references = vec![];
|
||||
|
||||
// Reference the video track for ONVIF metadata tracks
|
||||
if cfg.variant == super::Variant::ONVIF
|
||||
&& caps.structure(0).unwrap().name() == "application/x-onvif-metadata"
|
||||
&& stream.caps.structure(0).unwrap().name() == "application/x-onvif-metadata"
|
||||
{
|
||||
// Find the first video track
|
||||
for (idx, caps) in cfg.streams.iter().enumerate() {
|
||||
let s = caps.structure(0).unwrap();
|
||||
for (idx, other_stream) in cfg.streams.iter().enumerate() {
|
||||
let s = other_stream.caps.structure(0).unwrap();
|
||||
|
||||
if matches!(s.name(), "video/x-h264" | "video/x-h265" | "image/jpeg") {
|
||||
references.push(TrackReference {
|
||||
|
@ -442,7 +421,7 @@ fn write_moov(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
|||
}
|
||||
}
|
||||
|
||||
write_trak(v, cfg, idx, caps, creation_time, &references)
|
||||
write_trak(v, cfg, idx, stream, creation_time, &references)
|
||||
})?;
|
||||
}
|
||||
write_box(v, b"mvex", |v| write_mvex(v, cfg))?;
|
||||
|
@ -470,9 +449,13 @@ fn caps_to_timescale(caps: &gst::CapsRef) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
if fps.denom() == 1001 {
|
||||
fps.numer() as u32
|
||||
} else {
|
||||
(fps.numer() as u32)
|
||||
.mul_div_round(100, fps.denom() as u32)
|
||||
.unwrap_or(10_000)
|
||||
}
|
||||
} else if let Ok(rate) = s.get::<i32>("rate") {
|
||||
rate as u32
|
||||
} else {
|
||||
|
@ -480,6 +463,31 @@ fn caps_to_timescale(caps: &gst::CapsRef) -> u32 {
|
|||
}
|
||||
}
|
||||
|
||||
fn header_stream_to_timescale(stream: &super::HeaderStream) -> u32 {
|
||||
if stream.trak_timescale > 0 {
|
||||
stream.trak_timescale
|
||||
} else {
|
||||
caps_to_timescale(&stream.caps)
|
||||
}
|
||||
}
|
||||
|
||||
fn header_configuration_to_timescale(cfg: &super::HeaderConfiguration) -> u32 {
|
||||
if cfg.movie_timescale > 0 {
|
||||
cfg.movie_timescale
|
||||
} else {
|
||||
// Use the reference track timescale
|
||||
header_stream_to_timescale(&cfg.streams[0])
|
||||
}
|
||||
}
|
||||
|
||||
fn fragment_header_stream_to_timescale(stream: &super::FragmentHeaderStream) -> u32 {
|
||||
if stream.trak_timescale > 0 {
|
||||
stream.trak_timescale
|
||||
} else {
|
||||
caps_to_timescale(&stream.caps)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_mvhd(
|
||||
v: &mut Vec<u8>,
|
||||
cfg: &super::HeaderConfiguration,
|
||||
|
@ -489,8 +497,8 @@ fn write_mvhd(
|
|||
v.extend(creation_time.to_be_bytes());
|
||||
// Modification time
|
||||
v.extend(creation_time.to_be_bytes());
|
||||
// Timescale: uses the reference track timescale
|
||||
v.extend(caps_to_timescale(&cfg.streams[0]).to_be_bytes());
|
||||
// Timescale
|
||||
v.extend(header_configuration_to_timescale(cfg).to_be_bytes());
|
||||
// Duration
|
||||
v.extend(0u64.to_be_bytes());
|
||||
|
||||
|
@ -540,7 +548,7 @@ fn write_trak(
|
|||
v: &mut Vec<u8>,
|
||||
cfg: &super::HeaderConfiguration,
|
||||
idx: usize,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
creation_time: u64,
|
||||
references: &[TrackReference],
|
||||
) -> Result<(), Error> {
|
||||
|
@ -549,13 +557,13 @@ fn write_trak(
|
|||
b"tkhd",
|
||||
FULL_BOX_VERSION_1,
|
||||
TKHD_FLAGS_TRACK_ENABLED | TKHD_FLAGS_TRACK_IN_MOVIE | TKHD_FLAGS_TRACK_IN_PREVIEW,
|
||||
|v| write_tkhd(v, cfg, idx, caps, creation_time),
|
||||
|v| write_tkhd(v, cfg, idx, stream, creation_time),
|
||||
)?;
|
||||
|
||||
// TODO: write edts if necessary: for audio tracks to remove initialization samples
|
||||
// TODO: write edts optionally for negative DTS instead of offsetting the DTS
|
||||
|
||||
write_box(v, b"mdia", |v| write_mdia(v, cfg, caps, creation_time))?;
|
||||
write_box(v, b"mdia", |v| write_mdia(v, cfg, stream, creation_time))?;
|
||||
|
||||
if !references.is_empty() {
|
||||
write_box(v, b"tref", |v| write_tref(v, cfg, references))?;
|
||||
|
@ -568,7 +576,7 @@ fn write_tkhd(
|
|||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
idx: usize,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
creation_time: u64,
|
||||
) -> Result<(), Error> {
|
||||
// Creation time
|
||||
|
@ -591,9 +599,9 @@ fn write_tkhd(
|
|||
v.extend(0u16.to_be_bytes());
|
||||
|
||||
// Volume
|
||||
let s = caps.structure(0).unwrap();
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
match s.name() {
|
||||
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
v.extend((1u16 << 8).to_be_bytes())
|
||||
}
|
||||
_ => v.extend(0u16.to_be_bytes()),
|
||||
|
@ -621,7 +629,7 @@ fn write_tkhd(
|
|||
|
||||
// Width/height
|
||||
match s.name() {
|
||||
"video/x-h264" | "video/x-h265" | "image/jpeg" => {
|
||||
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
||||
let width = s.get::<i32>("width").context("video caps without width")? as u32;
|
||||
let height = s
|
||||
.get::<i32>("height")
|
||||
|
@ -650,19 +658,19 @@ fn write_tkhd(
|
|||
fn write_mdia(
|
||||
v: &mut Vec<u8>,
|
||||
cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
creation_time: u64,
|
||||
) -> Result<(), Error> {
|
||||
write_full_box(v, b"mdhd", FULL_BOX_VERSION_1, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_mdhd(v, cfg, caps, creation_time)
|
||||
write_mdhd(v, cfg, stream, creation_time)
|
||||
})?;
|
||||
write_full_box(v, b"hdlr", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_hdlr(v, cfg, caps)
|
||||
write_hdlr(v, cfg, stream)
|
||||
})?;
|
||||
|
||||
// TODO: write elng if needed
|
||||
|
||||
write_box(v, b"minf", |v| write_minf(v, cfg, caps))?;
|
||||
write_box(v, b"minf", |v| write_minf(v, cfg, stream))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -673,7 +681,7 @@ fn write_tref(
|
|||
references: &[TrackReference],
|
||||
) -> Result<(), Error> {
|
||||
for reference in references {
|
||||
write_box(v, &reference.reference_type, |v| {
|
||||
write_box(v, reference.reference_type, |v| {
|
||||
for track_id in &reference.track_ids {
|
||||
v.extend(track_id.to_be_bytes());
|
||||
}
|
||||
|
@ -699,7 +707,7 @@ fn language_code(lang: impl std::borrow::Borrow<[u8; 3]>) -> u16 {
|
|||
fn write_mdhd(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
creation_time: u64,
|
||||
) -> Result<(), Error> {
|
||||
// Creation time
|
||||
|
@ -707,7 +715,7 @@ fn write_mdhd(
|
|||
// Modification time
|
||||
v.extend(creation_time.to_be_bytes());
|
||||
// Timescale
|
||||
v.extend(caps_to_timescale(caps).to_be_bytes());
|
||||
v.extend(header_stream_to_timescale(stream).to_be_bytes());
|
||||
// Duration
|
||||
v.extend(0u64.to_be_bytes());
|
||||
|
||||
|
@ -724,15 +732,17 @@ fn write_mdhd(
|
|||
fn write_hdlr(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
) -> Result<(), Error> {
|
||||
// Pre-defined
|
||||
v.extend([0u8; 4]);
|
||||
|
||||
let s = caps.structure(0).unwrap();
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
let (handler_type, name) = match s.name() {
|
||||
"video/x-h264" | "video/x-h265" | "image/jpeg" => (b"vide", b"VideoHandler\0".as_slice()),
|
||||
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
||||
(b"vide", b"VideoHandler\0".as_slice())
|
||||
}
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
(b"soun", b"SoundHandler\0".as_slice())
|
||||
}
|
||||
"application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()),
|
||||
|
@ -754,16 +764,16 @@ fn write_hdlr(
|
|||
fn write_minf(
|
||||
v: &mut Vec<u8>,
|
||||
cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
) -> Result<(), Error> {
|
||||
let s = caps.structure(0).unwrap();
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
|
||||
match s.name() {
|
||||
"video/x-h264" | "video/x-h265" | "image/jpeg" => {
|
||||
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
||||
// Flags are always 1 for unspecified reasons
|
||||
write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, cfg))?
|
||||
}
|
||||
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
write_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_smhd(v, cfg)
|
||||
})?
|
||||
|
@ -778,7 +788,7 @@ fn write_minf(
|
|||
|
||||
write_box(v, b"dinf", |v| write_dinf(v, cfg))?;
|
||||
|
||||
write_box(v, b"stbl", |v| write_stbl(v, cfg, caps))?;
|
||||
write_box(v, b"stbl", |v| write_stbl(v, cfg, stream))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -831,10 +841,10 @@ fn write_dref(v: &mut Vec<u8>, _cfg: &super::HeaderConfiguration) -> Result<(),
|
|||
fn write_stbl(
|
||||
v: &mut Vec<u8>,
|
||||
cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
) -> Result<(), Error> {
|
||||
write_full_box(v, b"stsd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_stsd(v, cfg, caps)
|
||||
write_stsd(v, cfg, stream)
|
||||
})?;
|
||||
write_full_box(v, b"stts", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_stts(v, cfg)
|
||||
|
@ -851,15 +861,11 @@ fn write_stbl(
|
|||
})?;
|
||||
|
||||
// For video write a sync sample box as indication that not all samples are sync samples
|
||||
let s = caps.structure(0).unwrap();
|
||||
match s.name() {
|
||||
"video/x-h264" | "video/x-h265" => {
|
||||
if !stream.delta_frames.intra_only() {
|
||||
write_full_box(v, b"stss", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_stss(v, cfg)
|
||||
})?
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -867,18 +873,20 @@ fn write_stbl(
|
|||
fn write_stsd(
|
||||
v: &mut Vec<u8>,
|
||||
cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
) -> Result<(), Error> {
|
||||
// Entry count
|
||||
v.extend(1u32.to_be_bytes());
|
||||
|
||||
let s = caps.structure(0).unwrap();
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
match s.name() {
|
||||
"video/x-h264" | "video/x-h265" | "image/jpeg" => write_visual_sample_entry(v, cfg, caps)?,
|
||||
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
write_audio_sample_entry(v, cfg, caps)?
|
||||
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
||||
write_visual_sample_entry(v, cfg, stream)?
|
||||
}
|
||||
"application/x-onvif-metadata" => write_xml_meta_data_sample_entry(v, cfg, caps)?,
|
||||
"audio/mpeg" | "audio/x-opus" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
write_audio_sample_entry(v, cfg, stream)?
|
||||
}
|
||||
"application/x-onvif-metadata" => write_xml_meta_data_sample_entry(v, cfg, stream)?,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
|
@ -904,9 +912,9 @@ fn write_sample_entry_box<T, F: FnOnce(&mut Vec<u8>) -> Result<T, Error>>(
|
|||
fn write_visual_sample_entry(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
) -> Result<(), Error> {
|
||||
let s = caps.structure(0).unwrap();
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
let fourcc = match s.name() {
|
||||
"video/x-h264" => {
|
||||
let stream_format = s.get::<&str>("stream-format").context("no stream-format")?;
|
||||
|
@ -925,6 +933,7 @@ fn write_visual_sample_entry(
|
|||
}
|
||||
}
|
||||
"image/jpeg" => b"jpeg",
|
||||
"video/x-vp9" => b"vp09",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
|
@ -993,6 +1002,69 @@ fn write_visual_sample_entry(
|
|||
Ok(())
|
||||
})?;
|
||||
}
|
||||
"video/x-vp9" => {
|
||||
let profile: u8 = match s.get::<&str>("profile").expect("no vp9 profile") {
|
||||
"0" => Some(0),
|
||||
"1" => Some(1),
|
||||
"2" => Some(2),
|
||||
"3" => Some(3),
|
||||
_ => None,
|
||||
}
|
||||
.context("unsupported vp9 profile")?;
|
||||
let colorimetry = gst_video::VideoColorimetry::from_str(
|
||||
s.get::<&str>("colorimetry").expect("no colorimetry"),
|
||||
)
|
||||
.context("failed to parse colorimetry")?;
|
||||
let video_full_range =
|
||||
colorimetry.range() == gst_video::VideoColorRange::Range0_255;
|
||||
let chroma_format: u8 =
|
||||
match s.get::<&str>("chroma-format").expect("no chroma-format") {
|
||||
"4:2:0" =>
|
||||
// chroma-site is optional
|
||||
{
|
||||
match s
|
||||
.get::<&str>("chroma-site")
|
||||
.ok()
|
||||
.and_then(|cs| gst_video::VideoChromaSite::from_str(cs).ok())
|
||||
{
|
||||
Some(gst_video::VideoChromaSite::V_COSITED) => Some(0),
|
||||
// COSITED
|
||||
_ => Some(1),
|
||||
}
|
||||
}
|
||||
"4:2:2" => Some(2),
|
||||
"4:4:4" => Some(3),
|
||||
_ => None,
|
||||
}
|
||||
.context("unsupported chroma-format")?;
|
||||
let bit_depth: u8 = {
|
||||
let bit_depth_luma = s.get::<u32>("bit-depth-luma").expect("no bit-depth-luma");
|
||||
let bit_depth_chroma = s
|
||||
.get::<u32>("bit-depth-chroma")
|
||||
.expect("no bit-depth-chroma");
|
||||
if bit_depth_luma != bit_depth_chroma {
|
||||
return Err(anyhow!("bit-depth-luma and bit-depth-chroma have different values which is an unsupported configuration"));
|
||||
}
|
||||
bit_depth_luma as u8
|
||||
};
|
||||
write_full_box(v, b"vpcC", 1, 0, move |v| {
|
||||
v.push(profile);
|
||||
// XXX: hardcoded level 1
|
||||
v.push(10);
|
||||
let mut byte: u8 = 0;
|
||||
byte |= (bit_depth & 0xF) << 4;
|
||||
byte |= (chroma_format & 0x7) << 1;
|
||||
byte |= video_full_range as u8;
|
||||
v.push(byte);
|
||||
v.push(colorimetry.primaries().to_iso() as u8);
|
||||
v.push(colorimetry.transfer().to_iso() as u8);
|
||||
v.push(colorimetry.matrix().to_iso() as u8);
|
||||
// 16-bit length field for codec initialization, unused
|
||||
v.push(0);
|
||||
v.push(0);
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
"image/jpeg" => {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
@ -1015,50 +1087,11 @@ fn write_visual_sample_entry(
|
|||
write_box(v, b"colr", move |v| {
|
||||
v.extend(b"nclx");
|
||||
let (primaries, transfer, matrix) = {
|
||||
#[cfg(feature = "v1_18")]
|
||||
{
|
||||
(
|
||||
(colorimetry.primaries().to_iso() as u16),
|
||||
(colorimetry.transfer().to_iso() as u16),
|
||||
(colorimetry.matrix().to_iso() as u16),
|
||||
)
|
||||
}
|
||||
#[cfg(not(feature = "v1_18"))]
|
||||
{
|
||||
let primaries = match colorimetry.primaries() {
|
||||
gst_video::VideoColorPrimaries::Bt709 => 1u16,
|
||||
gst_video::VideoColorPrimaries::Bt470m => 4u16,
|
||||
gst_video::VideoColorPrimaries::Bt470bg => 5u16,
|
||||
gst_video::VideoColorPrimaries::Smpte170m => 6u16,
|
||||
gst_video::VideoColorPrimaries::Smpte240m => 7u16,
|
||||
gst_video::VideoColorPrimaries::Film => 8u16,
|
||||
gst_video::VideoColorPrimaries::Bt2020 => 9u16,
|
||||
_ => 2,
|
||||
};
|
||||
let transfer = match colorimetry.transfer() {
|
||||
gst_video::VideoTransferFunction::Bt709 => 1u16,
|
||||
gst_video::VideoTransferFunction::Gamma22 => 4u16,
|
||||
gst_video::VideoTransferFunction::Gamma28 => 5u16,
|
||||
gst_video::VideoTransferFunction::Smpte240m => 7u16,
|
||||
gst_video::VideoTransferFunction::Gamma10 => 8u16,
|
||||
gst_video::VideoTransferFunction::Log100 => 9u16,
|
||||
gst_video::VideoTransferFunction::Log316 => 10u16,
|
||||
gst_video::VideoTransferFunction::Srgb => 13u16,
|
||||
gst_video::VideoTransferFunction::Bt202012 => 15u16,
|
||||
_ => 2,
|
||||
};
|
||||
let matrix = match colorimetry.matrix() {
|
||||
gst_video::VideoColorMatrix::Rgb => 0u16,
|
||||
gst_video::VideoColorMatrix::Bt709 => 1u16,
|
||||
gst_video::VideoColorMatrix::Fcc => 4u16,
|
||||
gst_video::VideoColorMatrix::Bt601 => 6u16,
|
||||
gst_video::VideoColorMatrix::Smpte240m => 7u16,
|
||||
gst_video::VideoColorMatrix::Bt2020 => 9u16,
|
||||
_ => 2,
|
||||
};
|
||||
|
||||
(primaries, transfer, matrix)
|
||||
}
|
||||
};
|
||||
|
||||
let full_range = match colorimetry.range() {
|
||||
|
@ -1076,17 +1109,15 @@ fn write_visual_sample_entry(
|
|||
})?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1_18")]
|
||||
{
|
||||
if let Ok(cll) = gst_video::VideoContentLightLevel::from_caps(caps) {
|
||||
if let Ok(cll) = gst_video::VideoContentLightLevel::from_caps(&stream.caps) {
|
||||
write_box(v, b"clli", move |v| {
|
||||
v.extend((cll.max_content_light_level() as u16).to_be_bytes());
|
||||
v.extend((cll.max_frame_average_light_level() as u16).to_be_bytes());
|
||||
v.extend((cll.max_content_light_level()).to_be_bytes());
|
||||
v.extend((cll.max_frame_average_light_level()).to_be_bytes());
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
|
||||
if let Ok(mastering) = gst_video::VideoMasteringDisplayInfo::from_caps(caps) {
|
||||
if let Ok(mastering) = gst_video::VideoMasteringDisplayInfo::from_caps(&stream.caps) {
|
||||
write_box(v, b"mdcv", move |v| {
|
||||
for primary in mastering.display_primaries() {
|
||||
v.extend(primary.x.to_be_bytes());
|
||||
|
@ -1099,7 +1130,6 @@ fn write_visual_sample_entry(
|
|||
Ok(())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
// Write fiel box for codecs that require it
|
||||
if ["image/jpeg"].contains(&s.name()) {
|
||||
|
@ -1143,11 +1173,12 @@ fn write_visual_sample_entry(
|
|||
fn write_audio_sample_entry(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
) -> Result<(), Error> {
|
||||
let s = caps.structure(0).unwrap();
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
let fourcc = match s.name() {
|
||||
"audio/mpeg" => b"mp4a",
|
||||
"audio/x-opus" => b"Opus",
|
||||
"audio/x-alaw" => b"alaw",
|
||||
"audio/x-mulaw" => b"ulaw",
|
||||
"audio/x-adpcm" => {
|
||||
|
@ -1205,6 +1236,9 @@ fn write_audio_sample_entry(
|
|||
}
|
||||
write_esds_aac(v, &map)?;
|
||||
}
|
||||
"audio/x-opus" => {
|
||||
write_dops(v, &stream.caps)?;
|
||||
}
|
||||
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
@ -1340,12 +1374,71 @@ fn write_esds_aac(v: &mut Vec<u8>, codec_data: &[u8]) -> Result<(), Error> {
|
|||
)
|
||||
}
|
||||
|
||||
fn write_dops(v: &mut Vec<u8>, caps: &gst::Caps) -> Result<(), Error> {
|
||||
let rate;
|
||||
let channels;
|
||||
let channel_mapping_family;
|
||||
let stream_count;
|
||||
let coupled_count;
|
||||
let pre_skip;
|
||||
let output_gain;
|
||||
let mut channel_mapping = [0; 256];
|
||||
|
||||
// TODO: Use audio clipping meta to calculate pre_skip
|
||||
|
||||
if let Some(header) = caps
|
||||
.structure(0)
|
||||
.unwrap()
|
||||
.get::<gst::ArrayRef>("streamheader")
|
||||
.ok()
|
||||
.and_then(|a| a.get(0).and_then(|v| v.get::<gst::Buffer>().ok()))
|
||||
{
|
||||
(
|
||||
rate,
|
||||
channels,
|
||||
channel_mapping_family,
|
||||
stream_count,
|
||||
coupled_count,
|
||||
pre_skip,
|
||||
output_gain,
|
||||
) = gst_pbutils::codec_utils_opus_parse_header(&header, Some(&mut channel_mapping))
|
||||
.unwrap();
|
||||
} else {
|
||||
(
|
||||
rate,
|
||||
channels,
|
||||
channel_mapping_family,
|
||||
stream_count,
|
||||
coupled_count,
|
||||
) = gst_pbutils::codec_utils_opus_parse_caps(caps, Some(&mut channel_mapping)).unwrap();
|
||||
output_gain = 0;
|
||||
pre_skip = 0;
|
||||
}
|
||||
|
||||
write_box(v, b"dOps", move |v| {
|
||||
// Version number
|
||||
v.push(0);
|
||||
v.push(channels);
|
||||
v.extend(pre_skip.to_le_bytes());
|
||||
v.extend(rate.to_le_bytes());
|
||||
v.extend(output_gain.to_le_bytes());
|
||||
v.push(channel_mapping_family);
|
||||
if channel_mapping_family > 0 {
|
||||
v.push(stream_count);
|
||||
v.push(coupled_count);
|
||||
v.extend(&channel_mapping[..channels as usize]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn write_xml_meta_data_sample_entry(
|
||||
v: &mut Vec<u8>,
|
||||
_cfg: &super::HeaderConfiguration,
|
||||
caps: &gst::CapsRef,
|
||||
stream: &super::HeaderStream,
|
||||
) -> Result<(), Error> {
|
||||
let s = caps.structure(0).unwrap();
|
||||
let s = stream.caps.structure(0).unwrap();
|
||||
let namespace = match s.name() {
|
||||
"application/x-onvif-metadata" => b"http://www.onvif.org/ver10/schema",
|
||||
_ => unreachable!(),
|
||||
|
@ -1424,7 +1517,7 @@ fn write_mvex(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
|||
}
|
||||
}
|
||||
|
||||
for (idx, _caps) in cfg.streams.iter().enumerate() {
|
||||
for (idx, _stream) in cfg.streams.iter().enumerate() {
|
||||
write_full_box(v, b"trex", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_trex(v, cfg, idx)
|
||||
})?;
|
||||
|
@ -1435,7 +1528,7 @@ fn write_mvex(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), E
|
|||
|
||||
fn write_mehd(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), Error> {
|
||||
// Use the reference track timescale
|
||||
let timescale = caps_to_timescale(&cfg.streams[0]);
|
||||
let timescale = header_configuration_to_timescale(cfg);
|
||||
|
||||
let duration = cfg
|
||||
.duration
|
||||
|
@ -1478,7 +1571,7 @@ pub(super) fn create_fmp4_fragment_header(
|
|||
let mut v = vec![];
|
||||
|
||||
let (brand, compatible_brands) =
|
||||
brands_from_variant_and_caps(cfg.variant, cfg.streams.iter().map(|s| &s.0));
|
||||
brands_from_variant_and_caps(cfg.variant, cfg.streams.iter().map(|s| &s.caps));
|
||||
|
||||
write_box(&mut v, b"styp", |v| {
|
||||
// major brand
|
||||
|
@ -1529,15 +1622,14 @@ fn write_moof(
|
|||
})?;
|
||||
|
||||
let mut data_offset_offsets = vec![];
|
||||
for (idx, (caps, timing_info)) in cfg.streams.iter().enumerate() {
|
||||
for (idx, stream) in cfg.streams.iter().enumerate() {
|
||||
// Skip tracks without any buffers for this fragment.
|
||||
let timing_info = match timing_info {
|
||||
None => continue,
|
||||
Some(ref timing_info) => timing_info,
|
||||
};
|
||||
if stream.start_time.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
write_box(v, b"traf", |v| {
|
||||
write_traf(v, cfg, &mut data_offset_offsets, idx, caps, timing_info)
|
||||
write_traf(v, cfg, &mut data_offset_offsets, idx, stream)
|
||||
})?;
|
||||
}
|
||||
|
||||
|
@ -1551,11 +1643,9 @@ fn write_mfhd(v: &mut Vec<u8>, cfg: &super::FragmentHeaderConfiguration) -> Resu
|
|||
}
|
||||
|
||||
#[allow(clippy::identity_op)]
|
||||
fn sample_flags_from_buffer(
|
||||
timing_info: &super::FragmentTimingInfo,
|
||||
buffer: &gst::BufferRef,
|
||||
) -> u32 {
|
||||
if timing_info.intra_only {
|
||||
#[allow(clippy::bool_to_int_with_if)]
|
||||
fn sample_flags_from_buffer(stream: &super::FragmentHeaderStream, buffer: &gst::BufferRef) -> u32 {
|
||||
if stream.delta_frames.intra_only() {
|
||||
(0b00u32 << (16 + 10)) | // leading: unknown
|
||||
(0b10u32 << (16 + 8)) | // depends: no
|
||||
(0b10u32 << (16 + 6)) | // depended: no
|
||||
|
@ -1606,7 +1696,7 @@ const SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT: u32 = 0x8_00;
|
|||
fn analyze_buffers(
|
||||
cfg: &super::FragmentHeaderConfiguration,
|
||||
idx: usize,
|
||||
timing_info: &super::FragmentTimingInfo,
|
||||
stream: &super::FragmentHeaderStream,
|
||||
timescale: u32,
|
||||
) -> Result<
|
||||
(
|
||||
|
@ -1665,7 +1755,7 @@ fn analyze_buffers(
|
|||
tr_flags |= SAMPLE_DURATION_PRESENT;
|
||||
}
|
||||
|
||||
let f = sample_flags_from_buffer(timing_info, buffer);
|
||||
let f = sample_flags_from_buffer(stream, buffer);
|
||||
if first_buffer_flags.is_none() {
|
||||
first_buffer_flags = Some(f);
|
||||
} else {
|
||||
|
@ -1681,7 +1771,7 @@ fn analyze_buffers(
|
|||
}
|
||||
|
||||
if let Some(composition_time_offset) = *composition_time_offset {
|
||||
assert!(!timing_info.intra_only);
|
||||
assert!(stream.delta_frames.requires_dts());
|
||||
if composition_time_offset != 0 {
|
||||
tr_flags |= SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT;
|
||||
}
|
||||
|
@ -1732,10 +1822,9 @@ fn write_traf(
|
|||
cfg: &super::FragmentHeaderConfiguration,
|
||||
data_offset_offsets: &mut Vec<usize>,
|
||||
idx: usize,
|
||||
caps: &gst::CapsRef,
|
||||
timing_info: &super::FragmentTimingInfo,
|
||||
stream: &super::FragmentHeaderStream,
|
||||
) -> Result<(), Error> {
|
||||
let timescale = caps_to_timescale(caps);
|
||||
let timescale = fragment_header_stream_to_timescale(stream);
|
||||
|
||||
// Analyze all buffers to know what values can be put into the tfhd for all samples and what
|
||||
// has to be stored for every single sample
|
||||
|
@ -1746,7 +1835,7 @@ fn write_traf(
|
|||
default_duration,
|
||||
default_flags,
|
||||
negative_composition_time_offsets,
|
||||
) = analyze_buffers(cfg, idx, timing_info, timescale)?;
|
||||
) = analyze_buffers(cfg, idx, stream, timescale)?;
|
||||
|
||||
assert!((tf_flags & DEFAULT_SAMPLE_SIZE_PRESENT == 0) ^ default_size.is_some());
|
||||
assert!((tf_flags & DEFAULT_SAMPLE_DURATION_PRESENT == 0) ^ default_duration.is_some());
|
||||
|
@ -1756,7 +1845,7 @@ fn write_traf(
|
|||
write_tfhd(v, cfg, idx, default_size, default_duration, default_flags)
|
||||
})?;
|
||||
write_full_box(v, b"tfdt", FULL_BOX_VERSION_1, FULL_BOX_FLAGS_NONE, |v| {
|
||||
write_tfdt(v, cfg, idx, timing_info, timescale)
|
||||
write_tfdt(v, cfg, idx, stream, timescale)
|
||||
})?;
|
||||
|
||||
let mut current_data_offset = 0;
|
||||
|
@ -1786,7 +1875,7 @@ fn write_traf(
|
|||
current_data_offset,
|
||||
tr_flags,
|
||||
timescale,
|
||||
timing_info,
|
||||
stream,
|
||||
run,
|
||||
)
|
||||
},
|
||||
|
@ -1836,11 +1925,12 @@ fn write_tfdt(
|
|||
v: &mut Vec<u8>,
|
||||
_cfg: &super::FragmentHeaderConfiguration,
|
||||
_idx: usize,
|
||||
timing_info: &super::FragmentTimingInfo,
|
||||
stream: &super::FragmentHeaderStream,
|
||||
timescale: u32,
|
||||
) -> Result<(), Error> {
|
||||
let base_time = timing_info
|
||||
let base_time = stream
|
||||
.start_time
|
||||
.unwrap()
|
||||
.mul_div_floor(timescale as u64, gst::ClockTime::SECOND.nseconds())
|
||||
.context("base time overflow")?;
|
||||
|
||||
|
@ -1856,7 +1946,7 @@ fn write_trun(
|
|||
current_data_offset: u32,
|
||||
tr_flags: u32,
|
||||
timescale: u32,
|
||||
timing_info: &super::FragmentTimingInfo,
|
||||
stream: &super::FragmentHeaderStream,
|
||||
buffers: &[Buffer],
|
||||
) -> Result<usize, Error> {
|
||||
// Sample count
|
||||
|
@ -1867,7 +1957,7 @@ fn write_trun(
|
|||
v.extend(current_data_offset.to_be_bytes());
|
||||
|
||||
if (tr_flags & FIRST_SAMPLE_FLAGS_PRESENT) != 0 {
|
||||
v.extend(sample_flags_from_buffer(timing_info, &buffers[0].buffer).to_be_bytes());
|
||||
v.extend(sample_flags_from_buffer(stream, &buffers[0].buffer).to_be_bytes());
|
||||
}
|
||||
|
||||
for Buffer {
|
||||
|
@ -1899,7 +1989,7 @@ fn write_trun(
|
|||
assert!((tr_flags & FIRST_SAMPLE_FLAGS_PRESENT) == 0);
|
||||
|
||||
// Sample flags
|
||||
v.extend(sample_flags_from_buffer(timing_info, buffer).to_be_bytes());
|
||||
v.extend(sample_flags_from_buffer(stream, buffer).to_be_bytes());
|
||||
}
|
||||
|
||||
if (tr_flags & SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT) != 0 {
|
||||
|
@ -1977,7 +2067,7 @@ pub(crate) fn create_mfra(
|
|||
}
|
||||
|
||||
// Copy from std while this is still nightly-only
|
||||
use std::fmt;
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
/// An iterator over slice in (non-overlapping) chunks separated by a predicate.
|
||||
///
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,10 @@ use gst::prelude::*;
|
|||
mod boxes;
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct FMP4MuxPad(ObjectSubclass<imp::FMP4MuxPad>) @extends gst_base::AggregatorPad, gst::Pad, gst::Object;
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct FMP4Mux(ObjectSubclass<imp::FMP4Mux>) @extends gst_base::Aggregator, gst::Element, gst::Object;
|
||||
}
|
||||
|
@ -33,8 +37,12 @@ glib::wrapper! {
|
|||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
#[cfg(feature = "doc")]
|
||||
{
|
||||
FMP4Mux::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
FMP4MuxPad::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
HeaderUpdateMode::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
}
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"isofmp4mux",
|
||||
|
@ -64,33 +72,84 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HeaderConfiguration<'a> {
|
||||
pub(crate) struct HeaderConfiguration {
|
||||
variant: Variant,
|
||||
update: bool,
|
||||
|
||||
/// Pre-defined movie timescale if not 0.
|
||||
movie_timescale: u32,
|
||||
|
||||
/// First caps must be the video/reference stream. Must be in the order the tracks are going to
|
||||
/// be used later for the fragments too.
|
||||
streams: &'a [gst::Caps],
|
||||
streams: Vec<HeaderStream>,
|
||||
|
||||
write_mehd: bool,
|
||||
duration: Option<gst::ClockTime>,
|
||||
|
||||
/// Start UTC time in ONVIF mode.
|
||||
/// Since Jan 1 1601 in 100ns units.
|
||||
start_utc_time: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HeaderStream {
|
||||
/// Caps of this stream
|
||||
caps: gst::Caps,
|
||||
|
||||
/// Set if this is an intra-only stream
|
||||
delta_frames: DeltaFrames,
|
||||
|
||||
/// Pre-defined trak timescale if not 0.
|
||||
trak_timescale: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FragmentHeaderConfiguration<'a> {
|
||||
variant: Variant,
|
||||
|
||||
/// Sequence number for this fragment.
|
||||
sequence_number: u32,
|
||||
streams: &'a [(gst::Caps, Option<FragmentTimingInfo>)],
|
||||
|
||||
streams: &'a [FragmentHeaderStream],
|
||||
buffers: &'a [Buffer],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct FragmentTimingInfo {
|
||||
/// Start time of this fragment
|
||||
start_time: gst::ClockTime,
|
||||
pub(crate) struct FragmentHeaderStream {
|
||||
/// Caps of this stream
|
||||
caps: gst::Caps,
|
||||
|
||||
/// Set if this is an intra-only stream
|
||||
intra_only: bool,
|
||||
delta_frames: DeltaFrames,
|
||||
|
||||
/// Pre-defined trak timescale if not 0.
|
||||
trak_timescale: u32,
|
||||
|
||||
/// Start time of this fragment
|
||||
///
|
||||
/// `None` if this stream has no buffers in this fragment.
|
||||
start_time: Option<gst::ClockTime>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum DeltaFrames {
|
||||
/// Only single completely decodable frames
|
||||
IntraOnly,
|
||||
/// Frames may depend on past frames
|
||||
PredictiveOnly,
|
||||
/// Frames may depend on past or future frames
|
||||
Bidirectional,
|
||||
}
|
||||
|
||||
impl DeltaFrames {
|
||||
/// Whether dts is required to order buffers differently from presentation order
|
||||
pub(crate) fn requires_dts(&self) -> bool {
|
||||
matches!(self, Self::Bidirectional)
|
||||
}
|
||||
/// Whether this coding structure does not allow delta flags on buffers
|
||||
pub(crate) fn intra_only(&self) -> bool {
|
||||
matches!(self, Self::IntraOnly)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -18,7 +18,7 @@ fn init() {
|
|||
});
|
||||
}
|
||||
|
||||
fn test_buffer_flags_single_stream(cmaf: bool) {
|
||||
fn test_buffer_flags_single_stream(cmaf: bool, set_dts: bool, caps: gst::Caps) {
|
||||
let mut h = if cmaf {
|
||||
gst_check::Harness::new("cmafmux")
|
||||
} else {
|
||||
|
@ -30,16 +30,7 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
|||
.unwrap()
|
||||
.set_property("fragment-duration", 5.seconds());
|
||||
|
||||
h.set_src_caps(
|
||||
gst::Caps::builder("video/x-h264")
|
||||
.field("width", 1920i32)
|
||||
.field("height", 1080i32)
|
||||
.field("framerate", gst::Fraction::new(30, 1))
|
||||
.field("stream-format", "avc")
|
||||
.field("alignment", "au")
|
||||
.field("codec_data", gst::Buffer::with_size(1).unwrap())
|
||||
.build(),
|
||||
);
|
||||
h.set_src_caps(caps);
|
||||
h.play();
|
||||
|
||||
let output_offset = if cmaf {
|
||||
|
@ -54,7 +45,9 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
|||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
buffer.set_pts(i.seconds());
|
||||
if set_dts {
|
||||
buffer.set_dts(i.seconds());
|
||||
}
|
||||
buffer.set_duration(gst::ClockTime::SECOND);
|
||||
if i != 0 && i != 5 {
|
||||
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
||||
|
@ -84,13 +77,18 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
|||
}
|
||||
}
|
||||
|
||||
// Crank the clock: this should bring us to the end of the first fragment
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
|
||||
let header = h.pull().unwrap();
|
||||
assert_eq!(
|
||||
header.flags(),
|
||||
gst::BufferFlags::HEADER | gst::BufferFlags::DISCONT
|
||||
);
|
||||
assert_eq!(header.pts(), Some(gst::ClockTime::ZERO + output_offset));
|
||||
if set_dts {
|
||||
assert_eq!(header.dts(), Some(gst::ClockTime::ZERO + output_offset));
|
||||
}
|
||||
|
||||
let fragment_header = h.pull().unwrap();
|
||||
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
||||
|
@ -98,10 +96,12 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
|||
fragment_header.pts(),
|
||||
Some(gst::ClockTime::ZERO + output_offset)
|
||||
);
|
||||
if set_dts {
|
||||
assert_eq!(
|
||||
fragment_header.dts(),
|
||||
Some(gst::ClockTime::ZERO + output_offset)
|
||||
);
|
||||
}
|
||||
assert_eq!(fragment_header.duration(), Some(5.seconds()));
|
||||
|
||||
for i in 0..5 {
|
||||
|
@ -115,7 +115,9 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
|||
assert_eq!(buffer.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
assert_eq!(buffer.pts(), Some(i.seconds() + output_offset));
|
||||
if set_dts {
|
||||
assert_eq!(buffer.dts(), Some(i.seconds() + output_offset));
|
||||
}
|
||||
assert_eq!(buffer.duration(), Some(gst::ClockTime::SECOND));
|
||||
}
|
||||
|
||||
|
@ -124,7 +126,9 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
|||
let fragment_header = h.pull().unwrap();
|
||||
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
||||
assert_eq!(fragment_header.pts(), Some(5.seconds() + output_offset));
|
||||
if set_dts {
|
||||
assert_eq!(fragment_header.dts(), Some(5.seconds() + output_offset));
|
||||
}
|
||||
assert_eq!(fragment_header.duration(), Some(2.seconds()));
|
||||
|
||||
for i in 5..7 {
|
||||
|
@ -138,7 +142,9 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
|||
assert_eq!(buffer.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||
}
|
||||
assert_eq!(buffer.pts(), Some(i.seconds() + output_offset));
|
||||
if set_dts {
|
||||
assert_eq!(buffer.dts(), Some(i.seconds() + output_offset));
|
||||
}
|
||||
assert_eq!(buffer.duration(), Some(gst::ClockTime::SECOND));
|
||||
}
|
||||
|
||||
|
@ -153,17 +159,53 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_flags_single_stream_cmaf() {
|
||||
fn test_buffer_flags_single_h264_stream_cmaf() {
|
||||
init();
|
||||
|
||||
test_buffer_flags_single_stream(true);
|
||||
let caps = gst::Caps::builder("video/x-h264")
|
||||
.field("width", 1920i32)
|
||||
.field("height", 1080i32)
|
||||
.field("framerate", gst::Fraction::new(30, 1))
|
||||
.field("stream-format", "avc")
|
||||
.field("alignment", "au")
|
||||
.field("codec_data", gst::Buffer::with_size(1).unwrap())
|
||||
.build();
|
||||
|
||||
test_buffer_flags_single_stream(true, true, caps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_flags_single_stream_iso() {
|
||||
fn test_buffer_flags_single_h264_stream_iso() {
|
||||
init();
|
||||
|
||||
test_buffer_flags_single_stream(false);
|
||||
let caps = gst::Caps::builder("video/x-h264")
|
||||
.field("width", 1920i32)
|
||||
.field("height", 1080i32)
|
||||
.field("framerate", gst::Fraction::new(30, 1))
|
||||
.field("stream-format", "avc")
|
||||
.field("alignment", "au")
|
||||
.field("codec_data", gst::Buffer::with_size(1).unwrap())
|
||||
.build();
|
||||
|
||||
test_buffer_flags_single_stream(false, true, caps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_buffer_flags_single_vp9_stream_iso() {
|
||||
init();
|
||||
|
||||
let caps = gst::Caps::builder("video/x-vp9")
|
||||
.field("width", 1920i32)
|
||||
.field("height", 1080i32)
|
||||
.field("framerate", gst::Fraction::new(30, 1))
|
||||
.field("profile", "0")
|
||||
.field("chroma-format", "4:2:0")
|
||||
.field("bit-depth-luma", 8u32)
|
||||
.field("bit-depth-chroma", 8u32)
|
||||
.field("colorimetry", "bt709")
|
||||
.build();
|
||||
|
||||
test_buffer_flags_single_stream(false, false, caps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -273,6 +315,9 @@ fn test_buffer_flags_multi_stream() {
|
|||
}
|
||||
}
|
||||
|
||||
// Crank the clock: this should bring us to the end of the first fragment
|
||||
h1.crank_single_clock_wait().unwrap();
|
||||
|
||||
let header = h1.pull().unwrap();
|
||||
assert_eq!(
|
||||
header.flags(),
|
||||
|
@ -470,8 +515,7 @@ fn test_live_timeout() {
|
|||
}
|
||||
}
|
||||
|
||||
// Advance time and crank the clock: this should bring us to the end of the first fragment
|
||||
h1.set_time(5.seconds()).unwrap();
|
||||
// Crank the clock: this should bring us to the end of the first fragment
|
||||
h1.crank_single_clock_wait().unwrap();
|
||||
|
||||
let header = h1.pull().unwrap();
|
||||
|
@ -689,8 +733,7 @@ fn test_gap_events() {
|
|||
}
|
||||
}
|
||||
|
||||
// Advance time and crank the clock: this should bring us to the end of the first fragment
|
||||
h1.set_time(5.seconds()).unwrap();
|
||||
// Crank the clock: this should bring us to the end of the first fragment
|
||||
h1.crank_single_clock_wait().unwrap();
|
||||
|
||||
let header = h1.pull().unwrap();
|
||||
|
@ -980,6 +1023,9 @@ fn test_single_stream_long_gops() {
|
|||
}
|
||||
}
|
||||
|
||||
// Crank the clock: this should bring us to the end of the first fragment
|
||||
h.crank_single_clock_wait().unwrap();
|
||||
|
||||
let header = h.pull().unwrap();
|
||||
assert_eq!(
|
||||
header.flags(),
|
||||
|
|
49
mux/mp4/Cargo.toml
Normal file
49
mux/mp4/Cargo.toml
Normal file
|
@ -0,0 +1,49 @@
|
|||
[package]
|
||||
name = "gst-plugin-mp4"
|
||||
version = "0.9.8"
|
||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||
license = "MPL-2.0"
|
||||
description = "GStreamer Rust MP4 Plugin"
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
edition = "2021"
|
||||
rust-version = "1.63"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1", features = ["v1_18"] }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
gst-pbutils = { package = "gstreamer-pbutils", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
once_cell = "1.0"
|
||||
|
||||
[lib]
|
||||
name = "gstmp4"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
url = "2"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
static = []
|
||||
capi = []
|
||||
doc = []
|
||||
|
||||
[package.metadata.capi]
|
||||
min_version = "0.8.0"
|
||||
|
||||
[package.metadata.capi.header]
|
||||
enabled = false
|
||||
|
||||
[package.metadata.capi.library]
|
||||
install_subdir = "gstreamer-1.0"
|
||||
versioning = false
|
||||
|
||||
[package.metadata.capi.pkg_config]
|
||||
requires_private = "gstreamer-1.0, gstreamer-base-1.0, gstreamer-audio-1.0, gstreamer-video-1.0, gobject-2.0, glib-2.0, gmodule-2.0"
|
1
mux/mp4/LICENSE
Symbolic link
1
mux/mp4/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../LICENSE-MPL-2.0
|
3
mux/mp4/build.rs
Normal file
3
mux/mp4/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
gst_plugin_version_helper::info()
|
||||
}
|
34
mux/mp4/src/lib.rs
Normal file
34
mux/mp4/src/lib.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
// Copyright (C) 2022 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
#![allow(clippy::non_send_fields_in_send_ty, unused_doc_comments)]
|
||||
|
||||
/**
|
||||
* plugin-mp4:
|
||||
*
|
||||
* Since: plugins-rs-0.10.0
|
||||
*/
|
||||
use gst::glib;
|
||||
|
||||
mod mp4mux;
|
||||
|
||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
mp4mux::register(plugin)
|
||||
}
|
||||
|
||||
gst::plugin_define!(
|
||||
mp4,
|
||||
env!("CARGO_PKG_DESCRIPTION"),
|
||||
plugin_init,
|
||||
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
||||
// FIXME: MPL-2.0 is only allowed since 1.18.3 (as unknown) and 1.20 (as known)
|
||||
"MPL",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_REPOSITORY"),
|
||||
env!("BUILD_REL_DATE")
|
||||
);
|
1704
mux/mp4/src/mp4mux/boxes.rs
Normal file
1704
mux/mp4/src/mp4mux/boxes.rs
Normal file
File diff suppressed because it is too large
Load diff
1702
mux/mp4/src/mp4mux/imp.rs
Normal file
1702
mux/mp4/src/mp4mux/imp.rs
Normal file
File diff suppressed because it is too large
Load diff
145
mux/mp4/src/mp4mux/mod.rs
Normal file
145
mux/mp4/src/mp4mux/mod.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
// Copyright (C) 2022 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
||||
mod boxes;
|
||||
mod imp;
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct MP4MuxPad(ObjectSubclass<imp::MP4MuxPad>) @extends gst_base::AggregatorPad, gst::Pad, gst::Object;
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct MP4Mux(ObjectSubclass<imp::MP4Mux>) @extends gst_base::Aggregator, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct ISOMP4Mux(ObjectSubclass<imp::ISOMP4Mux>) @extends MP4Mux, gst_base::Aggregator, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub(crate) struct ONVIFMP4Mux(ObjectSubclass<imp::ONVIFMP4Mux>) @extends MP4Mux, gst_base::Aggregator, gst::Element, gst::Object;
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
#[cfg(feature = "doc")]
|
||||
{
|
||||
MP4Mux::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
MP4MuxPad::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||
}
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"isomp4mux",
|
||||
gst::Rank::Marginal,
|
||||
ISOMP4Mux::static_type(),
|
||||
)?;
|
||||
gst::Element::register(
|
||||
Some(plugin),
|
||||
"onvifmp4mux",
|
||||
gst::Rank::Marginal,
|
||||
ONVIFMP4Mux::static_type(),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum DeltaFrames {
|
||||
/// Only single completely decodable frames
|
||||
IntraOnly,
|
||||
/// Frames may depend on past frames
|
||||
PredictiveOnly,
|
||||
/// Frames may depend on past or future frames
|
||||
Bidirectional,
|
||||
}
|
||||
|
||||
impl DeltaFrames {
|
||||
/// Whether dts is required to order samples differently from presentation order
|
||||
pub(crate) fn requires_dts(&self) -> bool {
|
||||
matches!(self, Self::Bidirectional)
|
||||
}
|
||||
/// Whether this coding structure does not allow delta flags on samples
|
||||
pub(crate) fn intra_only(&self) -> bool {
|
||||
matches!(self, Self::IntraOnly)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Sample {
|
||||
/// Sync point
|
||||
sync_point: bool,
|
||||
|
||||
/// Sample duration
|
||||
duration: gst::ClockTime,
|
||||
|
||||
/// Composition time offset
|
||||
///
|
||||
/// This is `None` for streams that have no concept of DTS.
|
||||
composition_time_offset: Option<i64>,
|
||||
|
||||
/// Size
|
||||
size: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Chunk {
|
||||
/// Chunk start offset
|
||||
offset: u64,
|
||||
|
||||
/// Samples of this stream that are part of this chunk
|
||||
samples: Vec<Sample>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Stream {
|
||||
/// Caps of this stream
|
||||
caps: gst::Caps,
|
||||
|
||||
/// If this stream has delta frames, and if so if it can have B frames.
|
||||
delta_frames: DeltaFrames,
|
||||
|
||||
/// Pre-defined trak timescale if not 0.
|
||||
trak_timescale: u32,
|
||||
|
||||
/// Start DTS
|
||||
///
|
||||
/// If this is negative then an edit list entry is needed to
|
||||
/// make all sample times positive.
|
||||
///
|
||||
/// This is `None` for streams that have no concept of DTS.
|
||||
start_dts: Option<gst::Signed<gst::ClockTime>>,
|
||||
|
||||
/// Earliest PTS
|
||||
///
|
||||
/// If this is >0 then an edit list entry is needed to shift
|
||||
earliest_pts: gst::ClockTime,
|
||||
|
||||
/// End PTS
|
||||
end_pts: gst::ClockTime,
|
||||
|
||||
/// All the chunks stored for this stream
|
||||
chunks: Vec<Chunk>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Header {
|
||||
#[allow(dead_code)]
|
||||
variant: Variant,
|
||||
/// Pre-defined movie timescale if not 0.
|
||||
movie_timescale: u32,
|
||||
streams: Vec<Stream>,
|
||||
}
|
||||
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum Variant {
|
||||
ISO,
|
||||
ONVIF,
|
||||
}
|
128
mux/mp4/tests/tests.rs
Normal file
128
mux/mp4/tests/tests.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
// Copyright (C) 2022 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public License, v2.0.
|
||||
// If a copy of the MPL was not distributed with this file, You can obtain one at
|
||||
// <https://mozilla.org/MPL/2.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
//
|
||||
|
||||
use gst::prelude::*;
|
||||
use gst_pbutils::prelude::*;
|
||||
|
||||
fn init() {
|
||||
use std::sync::Once;
|
||||
static INIT: Once = Once::new();
|
||||
|
||||
INIT.call_once(|| {
|
||||
gst::init().unwrap();
|
||||
gstmp4::plugin_register_static().unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
init();
|
||||
|
||||
struct Pipeline(gst::Pipeline);
|
||||
impl std::ops::Deref for Pipeline {
|
||||
type Target = gst::Pipeline;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl Drop for Pipeline {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.0.set_state(gst::State::Null);
|
||||
}
|
||||
}
|
||||
|
||||
let pipeline = match gst::parse_launch(
|
||||
"videotestsrc num-buffers=99 ! x264enc ! mux. \
|
||||
audiotestsrc num-buffers=140 ! fdkaacenc ! mux. \
|
||||
isomp4mux name=mux ! filesink name=sink \
|
||||
",
|
||||
) {
|
||||
Ok(pipeline) => Pipeline(pipeline.downcast::<gst::Pipeline>().unwrap()),
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let mut location = dir.path().to_owned();
|
||||
location.push("test.mp4");
|
||||
|
||||
let sink = pipeline.by_name("sink").unwrap();
|
||||
sink.set_property("location", location.to_str().expect("Non-UTF8 filename"));
|
||||
|
||||
pipeline
|
||||
.set_state(gst::State::Playing)
|
||||
.expect("Unable to set the pipeline to the `Playing` state");
|
||||
|
||||
for msg in pipeline.bus().unwrap().iter_timed(gst::ClockTime::NONE) {
|
||||
use gst::MessageView;
|
||||
|
||||
match msg.view() {
|
||||
MessageView::Eos(..) => break,
|
||||
MessageView::Error(err) => {
|
||||
panic!(
|
||||
"Error from {:?}: {} ({:?})",
|
||||
err.src().map(|s| s.path_string()),
|
||||
err.error(),
|
||||
err.debug()
|
||||
);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
pipeline
|
||||
.set_state(gst::State::Null)
|
||||
.expect("Unable to set the pipeline to the `Null` state");
|
||||
|
||||
drop(pipeline);
|
||||
|
||||
let discoverer = gst_pbutils::Discoverer::new(gst::ClockTime::from_seconds(5))
|
||||
.expect("Failed to create discoverer");
|
||||
let info = discoverer
|
||||
.discover_uri(
|
||||
url::Url::from_file_path(&location)
|
||||
.expect("Failed to convert filename to URL")
|
||||
.as_str(),
|
||||
)
|
||||
.expect("Failed to discover MP4 file");
|
||||
|
||||
assert_eq!(info.duration(), Some(gst::ClockTime::from_mseconds(3_300)));
|
||||
|
||||
let audio_streams = info.audio_streams();
|
||||
assert_eq!(audio_streams.len(), 1);
|
||||
let audio_stream = &audio_streams[0];
|
||||
assert_eq!(audio_stream.channels(), 1);
|
||||
assert_eq!(audio_stream.sample_rate(), 44_100);
|
||||
let caps = audio_stream.caps().unwrap();
|
||||
assert!(
|
||||
caps.can_intersect(
|
||||
&gst::Caps::builder("audio/mpeg")
|
||||
.any_features()
|
||||
.field("mpegversion", 4i32)
|
||||
.build()
|
||||
),
|
||||
"Unexpected audio caps {:?}",
|
||||
caps
|
||||
);
|
||||
|
||||
let video_streams = info.video_streams();
|
||||
assert_eq!(video_streams.len(), 1);
|
||||
let video_stream = &video_streams[0];
|
||||
assert_eq!(video_stream.width(), 320);
|
||||
assert_eq!(video_stream.height(), 240);
|
||||
assert_eq!(video_stream.framerate(), gst::Fraction::new(30, 1));
|
||||
assert_eq!(video_stream.par(), gst::Fraction::new(1, 1));
|
||||
assert!(!video_stream.is_interlaced());
|
||||
let caps = video_stream.caps().unwrap();
|
||||
assert!(
|
||||
caps.can_intersect(&gst::Caps::builder("video/x-h264").any_features().build()),
|
||||
"Unexpected video caps {:?}",
|
||||
caps
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-aws"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
authors = ["Arun Raghavan <arun@arunraghavan.net>",
|
||||
"Jordan Petridis <jordan@centricular.com>",
|
||||
"Mathieu Duponchelle <mathieu@centricular.com>"]
|
||||
|
@ -13,22 +13,23 @@ rust-version = "1.63"
|
|||
[dependencies]
|
||||
bytes = "1.0"
|
||||
futures = "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-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_16"] }
|
||||
aws-config = "0.49.0"
|
||||
aws-sdk-s3 = "0.19.0"
|
||||
aws-sdk-transcribe = "0.19.0"
|
||||
aws-types = "0.49.0"
|
||||
aws-sig-auth = "0.49.0"
|
||||
aws-smithy-http = { version = "0.49.0", features = [ "rt-tokio" ] }
|
||||
aws-smithy-types = "0.49.0"
|
||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_16"] }
|
||||
aws-config = "0.53.0"
|
||||
aws-sdk-s3 = "0.23.0"
|
||||
aws-sdk-transcribe = "0.23.0"
|
||||
aws-types = "0.53.0"
|
||||
aws-credential-types = "0.53.0"
|
||||
aws-sig-auth = "0.53.0"
|
||||
aws-smithy-http = { version = "0.53.0", features = [ "rt-tokio" ] }
|
||||
aws-smithy-types = "0.53.0"
|
||||
http = "0.2.7"
|
||||
chrono = "0.4"
|
||||
url = "2"
|
||||
percent-encoding = "2"
|
||||
tokio = { version = "1.0", features = [ "full" ] }
|
||||
async-tungstenite = { version = "0.18", features = ["tokio", "tokio-runtime", "tokio-native-tls"] }
|
||||
async-tungstenite = { version = "0.19", features = ["tokio", "tokio-runtime", "tokio-native-tls"] }
|
||||
nom = "7"
|
||||
crc = "3"
|
||||
byteorder = "1.3.4"
|
||||
|
@ -39,14 +40,14 @@ serde_json = "1"
|
|||
atomic_refcell = "0.1"
|
||||
base32 = "0.4"
|
||||
backoff = { version = "0.4", features = [ "futures", "tokio" ] }
|
||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core.git", package = "gio" }
|
||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16", package = "gio" }
|
||||
|
||||
[dev-dependencies]
|
||||
chrono = { version = "0.4", features = [ "alloc" ] }
|
||||
env_logger = "0.9"
|
||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
|
||||
env_logger = "0.10"
|
||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||
rand = "0.8"
|
||||
test-with = { version = "0.8", default-features = false }
|
||||
test-with = { version = "0.9", default-features = false }
|
||||
|
||||
[lib]
|
||||
name = "gstaws"
|
||||
|
@ -54,7 +55,7 @@ crate-type = ["cdylib", "rlib"]
|
|||
path = "src/lib.rs"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { path="../../version-helper" }
|
||||
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# gst-plugin-s3
|
||||
# gst-plugin-aws
|
||||
|
||||
This is a [GStreamer](https://gstreamer.freedesktop.org/) plugin to interact
|
||||
with [Amazon Web Services](https://aws.amazon.com/). We currently have elements
|
||||
|
|
|
@ -170,8 +170,8 @@ impl TranscribeParse {
|
|||
}
|
||||
};
|
||||
|
||||
let start_pts = ((start_time as f64 * 1_000_000_000.0) as u64).nseconds();
|
||||
let end_pts = ((end_time as f64 * 1_000_000_000.0) as u64).nseconds();
|
||||
let start_pts = ((start_time * 1_000_000_000.0) as u64).nseconds();
|
||||
let end_pts = ((end_time * 1_000_000_000.0) as u64).nseconds();
|
||||
let duration = end_pts.saturating_sub(start_pts);
|
||||
|
||||
if start_pts > last_pts {
|
||||
|
|
|
@ -14,11 +14,11 @@ use gst::{element_imp_error, error_msg, loggable_error};
|
|||
use std::default::Default;
|
||||
|
||||
use aws_config::default_provider::credentials::DefaultCredentialsChain;
|
||||
use aws_credential_types::{provider::ProvideCredentials, Credentials};
|
||||
use aws_sig_auth::signer::{self, HttpSignatureType, OperationSigningConfig, RequestConfig};
|
||||
use aws_smithy_http::body::SdkBody;
|
||||
use aws_types::credentials::ProvideCredentials;
|
||||
use aws_types::region::{Region, SigningRegion};
|
||||
use aws_types::{Credentials, SigningService};
|
||||
use aws_types::SigningService;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use chrono::prelude::*;
|
||||
|
|
|
@ -22,10 +22,8 @@ use gst::{element_imp_error, glib, prelude::*, subclass::prelude::*};
|
|||
use aws_sdk_s3::config;
|
||||
use aws_sdk_s3::model::ObjectCannedAcl;
|
||||
use aws_sdk_s3::types::ByteStream;
|
||||
use aws_sdk_s3::Endpoint;
|
||||
use aws_sdk_s3::{Client, Credentials, Region, RetryConfig};
|
||||
use aws_sdk_s3::{config::retry::RetryConfig, Client, Credentials, Region};
|
||||
use aws_types::sdk_config::SdkConfig;
|
||||
use http::Uri;
|
||||
|
||||
use crate::s3utils;
|
||||
|
||||
|
@ -85,6 +83,7 @@ impl Default for Settings {
|
|||
|
||||
pub struct S3HlsSink {
|
||||
settings: Mutex<Settings>,
|
||||
state: Mutex<State>,
|
||||
hlssink: gst::Element,
|
||||
canceller: Mutex<Option<future::AbortHandle>>,
|
||||
}
|
||||
|
@ -132,6 +131,18 @@ enum S3RequestControl {
|
|||
Pause,
|
||||
}
|
||||
|
||||
enum State {
|
||||
Stopped,
|
||||
Started(Started),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Started {
|
||||
num_uploads_started: usize,
|
||||
num_uploads_completed: usize,
|
||||
num_bytes_uploaded: usize,
|
||||
}
|
||||
|
||||
impl S3Upload {
|
||||
fn new(
|
||||
s3_client: Client,
|
||||
|
@ -258,7 +269,8 @@ impl S3HlsSink {
|
|||
let put_object_req_future = put_object_req.send();
|
||||
let result = s3utils::wait(&self.canceller, put_object_req_future);
|
||||
|
||||
if let Err(err) = result {
|
||||
match result {
|
||||
Err(err) => {
|
||||
gst::error!(
|
||||
CAT,
|
||||
imp: self,
|
||||
|
@ -273,6 +285,19 @@ impl S3HlsSink {
|
|||
["Put object request failed"]
|
||||
);
|
||||
break;
|
||||
}
|
||||
Ok(_) => {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
match *state {
|
||||
State::Started(ref mut state) => {
|
||||
state.num_bytes_uploaded += s3_data_len;
|
||||
state.num_uploads_completed += 1;
|
||||
}
|
||||
State::Stopped => {
|
||||
unreachable!("State not started yet")
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(S3Request::Delete(data)) => {
|
||||
|
@ -349,29 +374,13 @@ impl S3HlsSink {
|
|||
}
|
||||
|
||||
let sdk_config = settings.config.as_ref().expect("SDK config must be set");
|
||||
let endpoint_uri = match &settings.endpoint_uri {
|
||||
Some(endpoint) => match endpoint.parse::<Uri>() {
|
||||
Ok(uri) => Some(uri),
|
||||
Err(e) => {
|
||||
element_imp_error!(
|
||||
self,
|
||||
gst::ResourceError::Settings,
|
||||
["Invalid S3 endpoint uri. Error: {}", e]
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let config_builder = config::Builder::from(sdk_config)
|
||||
.region(settings.s3_region.clone())
|
||||
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
||||
|
||||
let config = if let Some(uri) = endpoint_uri {
|
||||
config_builder
|
||||
.endpoint_resolver(Endpoint::mutable(uri))
|
||||
.build()
|
||||
let config = if let Some(ref uri) = settings.endpoint_uri {
|
||||
config_builder.endpoint_url(uri).build()
|
||||
} else {
|
||||
config_builder.build()
|
||||
};
|
||||
|
@ -400,6 +409,24 @@ impl S3HlsSink {
|
|||
};
|
||||
};
|
||||
}
|
||||
|
||||
fn create_stats(&self) -> gst::Structure {
|
||||
let state = self.state.lock().unwrap();
|
||||
|
||||
match &*state {
|
||||
State::Started(state) => gst::Structure::builder("stats")
|
||||
.field("num-uploads-started", state.num_uploads_started as u32)
|
||||
.field("num-uploads-completed", state.num_uploads_completed as u32)
|
||||
.field("num-bytes-uploaded", state.num_bytes_uploaded as u32)
|
||||
.build(),
|
||||
|
||||
State::Stopped => gst::Structure::builder("stats")
|
||||
.field("num-uploads-started", 0)
|
||||
.field("num-uploads-completed", 0)
|
||||
.field("num-bytes-uploaded", 0)
|
||||
.build(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -423,6 +450,7 @@ impl ObjectSubclass for S3HlsSink {
|
|||
|
||||
Self {
|
||||
settings: Mutex::new(Settings::default()),
|
||||
state: Mutex::new(State::Stopped),
|
||||
hlssink,
|
||||
canceller: Mutex::new(None),
|
||||
}
|
||||
|
@ -488,6 +516,13 @@ impl ObjectImpl for S3HlsSink {
|
|||
.minimum(1)
|
||||
.default_value(DEFAULT_TIMEOUT_IN_MSECS)
|
||||
.build(),
|
||||
glib::ParamSpecBoxed::new(
|
||||
"stats",
|
||||
"Various statistics",
|
||||
"Various statistics",
|
||||
gst::Structure::static_type(),
|
||||
glib::ParamFlags::READABLE,
|
||||
),
|
||||
glib::ParamSpecString::builder("endpoint-uri")
|
||||
.nick("S3 endpoint URI")
|
||||
.blurb("The S3 endpoint URI to use")
|
||||
|
@ -568,6 +603,7 @@ impl ObjectImpl for S3HlsSink {
|
|||
"acl" => settings.s3_acl.as_str().to_value(),
|
||||
"retry-attempts" => settings.retry_attempts.to_value(),
|
||||
"request-timeout" => (settings.request_timeout.as_millis() as u64).to_value(),
|
||||
"stats" => self.create_stats().to_value(),
|
||||
"endpoint-uri" => settings.endpoint_uri.to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
@ -610,6 +646,11 @@ impl ObjectImpl for S3HlsSink {
|
|||
|
||||
let s3client = self_.s3client_from_settings();
|
||||
let settings = self_.settings.lock().unwrap();
|
||||
let mut state = self_.state.lock().unwrap();
|
||||
match *state {
|
||||
State::Started(ref mut state) => state.num_uploads_started += 1,
|
||||
State::Stopped => unreachable!("State not started yet"),
|
||||
};
|
||||
|
||||
let s3_location = args[1].get::<&str>().unwrap();
|
||||
let upload = S3Upload::new(
|
||||
|
@ -639,6 +680,11 @@ impl ObjectImpl for S3HlsSink {
|
|||
|
||||
let s3client = self_.s3client_from_settings();
|
||||
let settings = self_.settings.lock().unwrap();
|
||||
let mut state = self_.state.lock().unwrap();
|
||||
match *state {
|
||||
State::Started(ref mut state) => state.num_uploads_started += 1,
|
||||
State::Stopped => unreachable!("State not started yet"),
|
||||
};
|
||||
|
||||
let s3_location = args[1].get::<&str>().unwrap();
|
||||
let upload = S3Upload::new(
|
||||
|
@ -764,8 +810,24 @@ impl ElementImpl for S3HlsSink {
|
|||
* in turn will require the settings lock.
|
||||
*/
|
||||
let settings = self.settings.lock().unwrap();
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
match transition {
|
||||
gst::StateChange::ReadyToPaused => *state = State::Started(Started::default()),
|
||||
gst::StateChange::PausedToPlaying => {
|
||||
let s3_txc = settings.s3_txc.clone();
|
||||
if let Some(tx) = s3_txc {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
"Sending continue request to S3 request thread."
|
||||
);
|
||||
if tx.send(S3RequestControl::Continue).is_err() {
|
||||
gst::error!(CAT, imp: self, "Could not send continue request.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gst::StateChange::PlayingToPaused => {
|
||||
let s3_txc = settings.s3_txc.clone();
|
||||
if let Some(tx) = s3_txc {
|
||||
|
@ -781,19 +843,7 @@ impl ElementImpl for S3HlsSink {
|
|||
}
|
||||
}
|
||||
}
|
||||
gst::StateChange::PausedToPlaying => {
|
||||
let s3_txc = settings.s3_txc.clone();
|
||||
if let Some(tx) = s3_txc {
|
||||
gst::debug!(
|
||||
CAT,
|
||||
imp: self,
|
||||
"Sending continue request to S3 request thread."
|
||||
);
|
||||
if tx.send(S3RequestControl::Continue).is_err() {
|
||||
gst::error!(CAT, imp: self, "Could not send continue request.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gst::StateChange::ReadyToNull => {
|
||||
drop(settings);
|
||||
/*
|
||||
|
@ -801,6 +851,8 @@ impl ElementImpl for S3HlsSink {
|
|||
* pending requests.
|
||||
*/
|
||||
self.stop();
|
||||
|
||||
*state = State::Stopped
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
@ -17,9 +17,7 @@ use aws_sdk_s3::client::fluent_builders::{
|
|||
use aws_sdk_s3::config;
|
||||
use aws_sdk_s3::model::{CompletedMultipartUpload, CompletedPart};
|
||||
use aws_sdk_s3::types::ByteStream;
|
||||
use aws_sdk_s3::Endpoint;
|
||||
use aws_sdk_s3::{Client, Credentials, Region, RetryConfig};
|
||||
use http::Uri;
|
||||
use aws_sdk_s3::{config::retry::RetryConfig, Client, Credentials, Region};
|
||||
|
||||
use futures::future;
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -88,6 +86,7 @@ impl Started {
|
|||
|
||||
enum State {
|
||||
Stopped,
|
||||
Completed,
|
||||
Started(Started),
|
||||
}
|
||||
|
||||
|
@ -185,23 +184,7 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
|||
});
|
||||
|
||||
impl S3Sink {
|
||||
fn flush_current_buffer(&self) -> Result<(), Option<gst::ErrorMessage>> {
|
||||
let upload_part_req: UploadPart = self.create_upload_part_request()?;
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let state = match *state {
|
||||
State::Started(ref mut started_state) => started_state,
|
||||
State::Stopped => {
|
||||
unreachable!("Element should be started");
|
||||
}
|
||||
};
|
||||
|
||||
let part_number = state.part_number;
|
||||
|
||||
let upload_part_req_future = upload_part_req.send();
|
||||
let output =
|
||||
s3utils::wait(&self.canceller, upload_part_req_future).map_err(|err| match err {
|
||||
WaitError::FutureError(err) => {
|
||||
fn flush_multipart_upload(&self, state: &mut Started) {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
match settings.multipart_upload_on_error {
|
||||
OnError::Abort => {
|
||||
|
@ -252,6 +235,29 @@ impl S3Sink {
|
|||
}
|
||||
OnError::DoNothing => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_current_buffer(&self) -> Result<(), Option<gst::ErrorMessage>> {
|
||||
let upload_part_req: UploadPart = self.create_upload_part_request()?;
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
let state = match *state {
|
||||
State::Started(ref mut started_state) => started_state,
|
||||
State::Completed => {
|
||||
unreachable!("Upload should not be completed yet");
|
||||
}
|
||||
State::Stopped => {
|
||||
unreachable!("Element should be started");
|
||||
}
|
||||
};
|
||||
|
||||
let part_number = state.part_number;
|
||||
|
||||
let upload_part_req_future = upload_part_req.send();
|
||||
let output =
|
||||
s3utils::wait(&self.canceller, upload_part_req_future).map_err(|err| match err {
|
||||
WaitError::FutureError(err) => {
|
||||
self.flush_multipart_upload(state);
|
||||
Some(gst::error_msg!(
|
||||
gst::ResourceError::OpenWrite,
|
||||
["Failed to upload part: {}", err]
|
||||
|
@ -277,6 +283,9 @@ impl S3Sink {
|
|||
let mut state = self.state.lock().unwrap();
|
||||
let state = match *state {
|
||||
State::Started(ref mut started_state) => started_state,
|
||||
State::Completed => {
|
||||
unreachable!("Upload should not be completed yet");
|
||||
}
|
||||
State::Stopped => {
|
||||
unreachable!("Element should be started");
|
||||
}
|
||||
|
@ -437,12 +446,21 @@ impl S3Sink {
|
|||
let mut state = self.state.lock().unwrap();
|
||||
let started_state = match *state {
|
||||
State::Started(ref mut started_state) => started_state,
|
||||
State::Completed => {
|
||||
unreachable!("Upload should not be completed yet");
|
||||
}
|
||||
State::Stopped => {
|
||||
unreachable!("Element should be started");
|
||||
}
|
||||
};
|
||||
|
||||
self.complete_multipart_upload_request(started_state)
|
||||
let res = self.complete_multipart_upload_request(started_state);
|
||||
|
||||
if res.is_ok() {
|
||||
*state = State::Completed;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||
|
@ -497,26 +515,11 @@ impl S3Sink {
|
|||
}
|
||||
})?;
|
||||
|
||||
let endpoint_uri = match &settings.endpoint_uri {
|
||||
Some(endpoint) => match endpoint.parse::<Uri>() {
|
||||
Ok(uri) => Some(uri),
|
||||
Err(e) => {
|
||||
return Err(gst::error_msg!(
|
||||
gst::ResourceError::Settings,
|
||||
["Invalid S3 endpoint uri. Error: {}", e]
|
||||
));
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let config_builder = config::Builder::from(&sdk_config)
|
||||
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
||||
|
||||
let config = if let Some(uri) = endpoint_uri {
|
||||
config_builder
|
||||
.endpoint_resolver(Endpoint::mutable(uri))
|
||||
.build()
|
||||
let config = if let Some(ref uri) = settings.endpoint_uri {
|
||||
config_builder.endpoint_url(uri).build()
|
||||
} else {
|
||||
config_builder.build()
|
||||
};
|
||||
|
@ -562,6 +565,9 @@ impl S3Sink {
|
|||
let mut state = self.state.lock().unwrap();
|
||||
let started_state = match *state {
|
||||
State::Started(ref mut started_state) => started_state,
|
||||
State::Completed => {
|
||||
unreachable!("Upload should not be completed yet");
|
||||
}
|
||||
State::Stopped => {
|
||||
unreachable!("Element should be started already");
|
||||
}
|
||||
|
@ -953,6 +959,17 @@ impl BaseSinkImpl for S3Sink {
|
|||
|
||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
if let State::Started(ref mut state) = *state {
|
||||
gst::warning!(CAT, imp: self, "Stopped without EOS");
|
||||
|
||||
// We're stopping without an EOS -- treat this as an error and deal with the open
|
||||
// multipart upload accordingly _if_ we managed to upload any parts
|
||||
if !state.completed_parts.is_empty() {
|
||||
self.flush_multipart_upload(state);
|
||||
}
|
||||
}
|
||||
|
||||
*state = State::Stopped;
|
||||
gst::info!(CAT, imp: self, "Stopped");
|
||||
|
||||
|
@ -965,6 +982,15 @@ impl BaseSinkImpl for S3Sink {
|
|||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
if let State::Completed = *self.state.lock().unwrap() {
|
||||
gst::element_imp_error!(
|
||||
self,
|
||||
gst::CoreError::Failed,
|
||||
["Trying to render after upload complete"]
|
||||
);
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
|
||||
gst::trace!(CAT, imp: self, "Rendering {:?}", buffer);
|
||||
let map = buffer.map_readable().map_err(|_| {
|
||||
gst::element_imp_error!(self, gst::CoreError::Failed, ["Failed to map buffer"]);
|
||||
|
|
|
@ -13,9 +13,7 @@ use std::sync::Mutex;
|
|||
use std::time::Duration;
|
||||
|
||||
use aws_sdk_s3::config;
|
||||
use aws_sdk_s3::Endpoint;
|
||||
use aws_sdk_s3::{Client, Credentials, RetryConfig};
|
||||
use http::Uri;
|
||||
use aws_sdk_s3::{config::retry::RetryConfig, Client, Credentials};
|
||||
|
||||
use gst::glib;
|
||||
use gst::prelude::*;
|
||||
|
@ -130,26 +128,11 @@ impl S3Src {
|
|||
}
|
||||
})?;
|
||||
|
||||
let endpoint_uri = match &settings.endpoint_uri {
|
||||
Some(endpoint) => match endpoint.parse::<Uri>() {
|
||||
Ok(uri) => Some(uri),
|
||||
Err(e) => {
|
||||
return Err(gst::error_msg!(
|
||||
gst::ResourceError::Settings,
|
||||
["Invalid S3 endpoint uri. Error: {}", e]
|
||||
));
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
|
||||
let config_builder = config::Builder::from(&sdk_config)
|
||||
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
||||
|
||||
let config = if let Some(uri) = endpoint_uri {
|
||||
config_builder
|
||||
.endpoint_resolver(Endpoint::mutable(uri))
|
||||
.build()
|
||||
let config = if let Some(ref uri) = settings.endpoint_uri {
|
||||
config_builder.endpoint_url(uri).build()
|
||||
} else {
|
||||
config_builder.build()
|
||||
};
|
||||
|
|
|
@ -102,7 +102,7 @@ pub fn parse_s3_url(url_str: &str) -> Result<GstS3Url, String> {
|
|||
Some(_) => return Err("Bad query, only 'version' is supported".to_owned()),
|
||||
};
|
||||
|
||||
if q.next() != None {
|
||||
if q.next().is_some() {
|
||||
return Err("Extra query terms, only 'version' is supported".to_owned());
|
||||
}
|
||||
|
||||
|
|
|
@ -7,11 +7,10 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use aws_config::meta::region::RegionProviderChain;
|
||||
use aws_sdk_s3::{Credentials, Region};
|
||||
use aws_sdk_s3::{config::timeout::TimeoutConfig, Credentials, Region};
|
||||
use aws_types::sdk_config::SdkConfig;
|
||||
|
||||
use aws_smithy_http::byte_stream::{ByteStream, Error};
|
||||
use aws_smithy_types::{timeout, tristate::TriState};
|
||||
use aws_smithy_http::byte_stream::{error::Error, ByteStream};
|
||||
|
||||
use bytes::{buf::BufMut, Bytes, BytesMut};
|
||||
use futures::stream::TryStreamExt;
|
||||
|
@ -96,19 +95,16 @@ pub fn wait_stream(
|
|||
}
|
||||
|
||||
// See setting-timeouts example in aws-sdk-rust.
|
||||
pub fn timeout_config(request_timeout: Duration) -> timeout::Config {
|
||||
timeout::Config::new().with_api_timeouts(
|
||||
timeout::Api::new()
|
||||
// This timeout acts at the "HTTP request" level and sets a separate timeout for each
|
||||
// HTTP request made as part of a "service request."
|
||||
.with_call_attempt_timeout(TriState::Set(request_timeout)),
|
||||
)
|
||||
pub fn timeout_config(request_timeout: Duration) -> TimeoutConfig {
|
||||
TimeoutConfig::builder()
|
||||
.operation_attempt_timeout(request_timeout)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn wait_config(
|
||||
canceller: &Mutex<Option<future::AbortHandle>>,
|
||||
region: Region,
|
||||
timeout_config: timeout::Config,
|
||||
timeout_config: TimeoutConfig,
|
||||
credentials: Option<Credentials>,
|
||||
) -> Result<SdkConfig, WaitError<Error>> {
|
||||
let region_provider = RegionProviderChain::first_try(region)
|
||||
|
|
|
@ -22,6 +22,8 @@ fn init() {
|
|||
});
|
||||
}
|
||||
|
||||
// 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]
|
||||
|
|
|
@ -2,27 +2,27 @@
|
|||
name = "gst-plugin-hlssink3"
|
||||
description = "GStreamer HLS (HTTP Live Streaming) Plugin"
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
authors = ["Rafael Caricio <rafael@caricio.com>"]
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.63"
|
||||
|
||||
[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.19", version = "0.19.1" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
glib = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16.2" }
|
||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16" }
|
||||
once_cell = "1.7.2"
|
||||
m3u8-rs = "5.0"
|
||||
regex = "1"
|
||||
|
||||
[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.19", version = "0.19" }
|
||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { path = "../../version-helper" }
|
||||
gst-plugin-version-helper = { version = "0.7", path = "../../version-helper" }
|
||||
|
||||
[lib]
|
||||
name = "gsthlssink3"
|
||||
|
|
|
@ -93,7 +93,7 @@ impl Playlist {
|
|||
}
|
||||
|
||||
self.playlist_index += 1;
|
||||
self.inner.media_sequence = self.playlist_index as u64 - self.inner.segments.len() as u64;
|
||||
self.inner.media_sequence = self.playlist_index - self.inner.segments.len() as u64;
|
||||
}
|
||||
|
||||
/// Sets the playlist to started state.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-ndi"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
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,11 +9,11 @@ edition = "2021"
|
|||
rust-version = "1.63"
|
||||
|
||||
[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.16", version = "0.16.2"}
|
||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
byte-slice-cast = "1"
|
||||
once_cell = "1.0"
|
||||
byteorder = "1.0"
|
||||
|
@ -21,7 +21,7 @@ atomic_refcell = "0.1"
|
|||
libloading = "0.7"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = { path = "../../version-helper" }
|
||||
gst-plugin-version-helper = { version = "0.7", path = "../../version-helper" }
|
||||
|
||||
[features]
|
||||
default = ["interlaced-fields", "sink"]
|
||||
|
|
|
@ -215,16 +215,12 @@ impl GstObjectImpl for Device {}
|
|||
impl DeviceImpl for Device {
|
||||
fn create_element(&self, name: Option<&str>) -> Result<gst::Element, gst::LoggableError> {
|
||||
let source_info = self.source.get().unwrap();
|
||||
let element = glib::Object::with_type(
|
||||
crate::ndisrc::NdiSrc::static_type(),
|
||||
&[
|
||||
("name", &name),
|
||||
("ndi-name", &source_info.ndi_name()),
|
||||
("url-address", &source_info.url_address()),
|
||||
],
|
||||
)
|
||||
.dynamic_cast::<gst::Element>()
|
||||
.unwrap();
|
||||
let element = glib::Object::builder::<crate::ndisrc::NdiSrc>()
|
||||
.property("name", name)
|
||||
.property("ndi-name", source_info.ndi_name())
|
||||
.property("url-address", source_info.url_address())
|
||||
.build()
|
||||
.upcast::<gst::Element>();
|
||||
|
||||
Ok(element)
|
||||
}
|
||||
|
@ -242,16 +238,16 @@ impl super::Device {
|
|||
|
||||
// Put the url-address into the extra properties
|
||||
let extra_properties = gst::Structure::builder("properties")
|
||||
.field("ndi-name", &source.ndi_name())
|
||||
.field("url-address", &source.url_address())
|
||||
.field("ndi-name", source.ndi_name())
|
||||
.field("url-address", source.url_address())
|
||||
.build();
|
||||
|
||||
let device = glib::Object::new::<super::Device>(&[
|
||||
("caps", &caps),
|
||||
("display-name", &display_name),
|
||||
("device-class", &device_class),
|
||||
("properties", &extra_properties),
|
||||
]);
|
||||
let device = glib::Object::builder::<super::Device>()
|
||||
.property("caps", caps)
|
||||
.property("display-name", display_name)
|
||||
.property("device-class", device_class)
|
||||
.property("properties", extra_properties)
|
||||
.build();
|
||||
|
||||
let imp = device.imp();
|
||||
imp.source.set(source.to_owned()).unwrap();
|
||||
|
|
|
@ -121,23 +121,23 @@ impl ElementImpl for NdiSink {
|
|||
gst::Structure::builder("video/x-raw")
|
||||
.field(
|
||||
"format",
|
||||
&gst::List::new(&[
|
||||
&gst_video::VideoFormat::Uyvy.to_str(),
|
||||
&gst_video::VideoFormat::I420.to_str(),
|
||||
&gst_video::VideoFormat::Nv12.to_str(),
|
||||
&gst_video::VideoFormat::Nv21.to_str(),
|
||||
&gst_video::VideoFormat::Yv12.to_str(),
|
||||
&gst_video::VideoFormat::Bgra.to_str(),
|
||||
&gst_video::VideoFormat::Bgrx.to_str(),
|
||||
&gst_video::VideoFormat::Rgba.to_str(),
|
||||
&gst_video::VideoFormat::Rgbx.to_str(),
|
||||
gst::List::new([
|
||||
gst_video::VideoFormat::Uyvy.to_str(),
|
||||
gst_video::VideoFormat::I420.to_str(),
|
||||
gst_video::VideoFormat::Nv12.to_str(),
|
||||
gst_video::VideoFormat::Nv21.to_str(),
|
||||
gst_video::VideoFormat::Yv12.to_str(),
|
||||
gst_video::VideoFormat::Bgra.to_str(),
|
||||
gst_video::VideoFormat::Bgrx.to_str(),
|
||||
gst_video::VideoFormat::Rgba.to_str(),
|
||||
gst_video::VideoFormat::Rgbx.to_str(),
|
||||
]),
|
||||
)
|
||||
.field("width", &gst::IntRange::<i32>::new(1, std::i32::MAX))
|
||||
.field("height", &gst::IntRange::<i32>::new(1, std::i32::MAX))
|
||||
.field("width", gst::IntRange::<i32>::new(1, std::i32::MAX))
|
||||
.field("height", gst::IntRange::<i32>::new(1, std::i32::MAX))
|
||||
.field(
|
||||
"framerate",
|
||||
&gst::FractionRange::new(
|
||||
gst::FractionRange::new(
|
||||
gst::Fraction::new(0, 1),
|
||||
gst::Fraction::new(std::i32::MAX, 1),
|
||||
),
|
||||
|
@ -146,10 +146,10 @@ impl ElementImpl for NdiSink {
|
|||
)
|
||||
.structure(
|
||||
gst::Structure::builder("audio/x-raw")
|
||||
.field("format", &gst_audio::AUDIO_FORMAT_F32.to_str())
|
||||
.field("rate", &gst::IntRange::<i32>::new(1, i32::MAX))
|
||||
.field("channels", &gst::IntRange::<i32>::new(1, i32::MAX))
|
||||
.field("layout", &"interleaved")
|
||||
.field("format", gst_audio::AUDIO_FORMAT_F32.to_str())
|
||||
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
|
||||
.field("channels", gst::IntRange::<i32>::new(1, i32::MAX))
|
||||
.field("layout", "interleaved")
|
||||
.build(),
|
||||
)
|
||||
.build();
|
||||
|
|
|
@ -109,6 +109,12 @@ impl ObjectSubclass for NdiSrc {
|
|||
impl ObjectImpl for NdiSrc {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
let receiver = glib::ParamSpecString::builder("receiver-ndi-name")
|
||||
.nick("Receiver NDI Name")
|
||||
.blurb("NDI stream name of this receiver");
|
||||
#[cfg(feature = "doc")]
|
||||
let receiver = receiver.doc_show_default();
|
||||
|
||||
vec![
|
||||
glib::ParamSpecString::builder("ndi-name")
|
||||
.nick("NDI Name")
|
||||
|
@ -118,11 +124,7 @@ impl ObjectImpl for NdiSrc {
|
|||
.nick("URL/Address")
|
||||
.blurb("URL/address and port of the sender, e.g. 127.0.0.1:5961")
|
||||
.build(),
|
||||
glib::ParamSpecString::builder("receiver-ndi-name")
|
||||
.nick("Receiver NDI Name")
|
||||
.blurb("NDI stream name of this receiver")
|
||||
.doc_show_default()
|
||||
.build(),
|
||||
receiver.build(),
|
||||
glib::ParamSpecUInt::builder("connect-timeout")
|
||||
.nick("Connect Timeout")
|
||||
.blurb("Connection timeout in ms")
|
||||
|
|
|
@ -915,14 +915,14 @@ impl Receiver {
|
|||
real_time_now,
|
||||
);
|
||||
|
||||
let res_timestamp = self.0.observations_timestamp[if is_audio { 0 } else { 1 }].process(
|
||||
let res_timestamp = self.0.observations_timestamp[usize::from(!is_audio)].process(
|
||||
element,
|
||||
timestamp,
|
||||
receive_time,
|
||||
duration,
|
||||
);
|
||||
|
||||
let res_timecode = self.0.observations_timecode[if is_audio { 0 } else { 1 }].process(
|
||||
let res_timecode = self.0.observations_timecode[usize::from(!is_audio)].process(
|
||||
element,
|
||||
Some(timecode),
|
||||
receive_time,
|
||||
|
@ -1299,14 +1299,14 @@ impl Receiver {
|
|||
|
||||
gst::ReferenceTimestampMeta::add(
|
||||
buffer,
|
||||
&*crate::TIMECODE_CAPS,
|
||||
&crate::TIMECODE_CAPS,
|
||||
(video_frame.timecode() as u64 * 100).nseconds(),
|
||||
gst::ClockTime::NONE,
|
||||
);
|
||||
if video_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined {
|
||||
gst::ReferenceTimestampMeta::add(
|
||||
buffer,
|
||||
&*crate::TIMESTAMP_CAPS,
|
||||
&crate::TIMESTAMP_CAPS,
|
||||
(video_frame.timestamp() as u64 * 100).nseconds(),
|
||||
gst::ClockTime::NONE,
|
||||
);
|
||||
|
@ -1587,11 +1587,19 @@ impl Receiver {
|
|||
let fourcc = audio_frame.fourcc();
|
||||
|
||||
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],
|
||||
);
|
||||
|
||||
let builder = gst_audio::AudioInfo::builder(
|
||||
gst_audio::AUDIO_FORMAT_F32,
|
||||
audio_frame.sample_rate() as u32,
|
||||
audio_frame.no_channels() as u32,
|
||||
);
|
||||
channels,
|
||||
)
|
||||
.positions(&positions[..channels as usize]);
|
||||
|
||||
let info = builder.build().map_err(|_| {
|
||||
gst::element_error!(
|
||||
|
@ -1670,14 +1678,14 @@ impl Receiver {
|
|||
|
||||
gst::ReferenceTimestampMeta::add(
|
||||
buffer,
|
||||
&*crate::TIMECODE_CAPS,
|
||||
&crate::TIMECODE_CAPS,
|
||||
(audio_frame.timecode() as u64 * 100).nseconds(),
|
||||
gst::ClockTime::NONE,
|
||||
);
|
||||
if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined {
|
||||
gst::ReferenceTimestampMeta::add(
|
||||
buffer,
|
||||
&*crate::TIMESTAMP_CAPS,
|
||||
&crate::TIMESTAMP_CAPS,
|
||||
(audio_frame.timestamp() as u64 * 100).nseconds(),
|
||||
gst::ClockTime::NONE,
|
||||
);
|
||||
|
|
|
@ -21,6 +21,8 @@ const LIBRARY_NAME: &str = "Processing.NDI.Lib.x86.dll";
|
|||
const LIBRARY_NAME: &str = "libndi.so.5";
|
||||
#[cfg(target_os = "macos")]
|
||||
const LIBRARY_NAME: &str = "libndi.dylib";
|
||||
#[cfg(all(unix, not(any(target_os = "linux", target_os = "macos"))))]
|
||||
const LIBRARY_NAME: &str = "libndi.so";
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
struct FFI {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-onvif"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
license = "MPL-2.0"
|
||||
|
@ -9,16 +9,16 @@ edition = "2021"
|
|||
rust-version = "1.63"
|
||||
|
||||
[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.19", version = "0.19.1", features = ["v1_20"] }
|
||||
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_20"] }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_20"] }
|
||||
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_20"] }
|
||||
once_cell = "1.0"
|
||||
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" }
|
||||
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16", features=["use_glib"] }
|
||||
pango = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16" }
|
||||
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16" }
|
||||
xmltree = "0.10"
|
||||
|
||||
[lib]
|
||||
|
@ -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 = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
|
@ -187,8 +187,8 @@ impl OnvifMetadataOverlay {
|
|||
gst_video::VideoFormat::Bgra,
|
||||
#[cfg(target_endian = "big")]
|
||||
gst_video::VideoFormat::Argb,
|
||||
total_width as u32,
|
||||
total_height as u32,
|
||||
total_width,
|
||||
total_height,
|
||||
)
|
||||
.ok()?;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "gst-plugin-raptorq"
|
||||
version = "0.9.0-alpha.1"
|
||||
version = "0.9.8"
|
||||
authors = ["Tomasz Andrzejak <andreiltd@gmail.com>"]
|
||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||
description = "GStreamer RaptorQ FEC Plugin"
|
||||
|
@ -9,14 +9,14 @@ edition = "2021"
|
|||
rust-version = "1.63"
|
||||
|
||||
[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.19", version = "0.19.1" }
|
||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||
once_cell = "1.0"
|
||||
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.19", version = "0.19", features = ["v1_18"] }
|
||||
rand = "0.8"
|
||||
|
||||
[lib]
|
||||
|
@ -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 = { version = "0.7", path="../../version-helper" }
|
||||
|
||||
[features]
|
||||
static = []
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue