mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-09-30 07:42:08 +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
|
interruptible: true
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
|
- "trigger"
|
||||||
- "prep"
|
- "prep"
|
||||||
- "lint"
|
- "lint"
|
||||||
- "test"
|
- "test"
|
||||||
- "extras"
|
- "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:
|
.debian:11:
|
||||||
variables:
|
variables:
|
||||||
|
@ -49,30 +72,23 @@ stages:
|
||||||
before_script:
|
before_script:
|
||||||
- source ./ci/env.sh
|
- source ./ci/env.sh
|
||||||
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
- mkdir .cargo && echo -e "[net]\ngit-fetch-with-cli = true" > .cargo/config
|
||||||
# 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:
|
.debian:11-stable:
|
||||||
extends: .debian:11
|
extends: .debian:11
|
||||||
variables:
|
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"
|
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:$GST_RS_STABLE-$GST_RS_IMG_TAG"
|
||||||
|
|
||||||
.debian:11-msrv:
|
.debian:11-msrv:
|
||||||
extends: .debian:11
|
extends: .debian:11
|
||||||
variables:
|
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"
|
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:$GST_RS_MSRV-$GST_RS_IMG_TAG"
|
||||||
|
|
||||||
.debian:11-nightly:
|
.debian:11-nightly:
|
||||||
extends: .debian:11
|
extends: .debian:11
|
||||||
variables:
|
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"
|
FDO_BASE_IMAGE: "registry.freedesktop.org/gstreamer/gstreamer-rs/debian/bullseye-slim:nightly-$GST_RS_IMG_TAG"
|
||||||
|
|
||||||
.build-debian-container:
|
.build-debian-container:
|
||||||
|
@ -86,6 +102,8 @@ stages:
|
||||||
apt clean &&
|
apt clean &&
|
||||||
bash ./ci/install-rust-ext.sh &&
|
bash ./ci/install-rust-ext.sh &&
|
||||||
pip install tomli
|
pip install tomli
|
||||||
|
needs:
|
||||||
|
- "trigger"
|
||||||
rules:
|
rules:
|
||||||
- if: '$UPDATE_IMG == null'
|
- if: '$UPDATE_IMG == null'
|
||||||
|
|
||||||
|
@ -230,7 +248,7 @@ meson shared:
|
||||||
meson static:
|
meson static:
|
||||||
extends: .meson
|
extends: .meson
|
||||||
script:
|
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
|
- ninja -C build install
|
||||||
- ./ci/generate-static-test.py test-static-link-all
|
- ./ci/generate-static-test.py test-static-link-all
|
||||||
- cd test-static-link-all
|
- cd test-static-link-all
|
||||||
|
@ -246,6 +264,7 @@ meson static:
|
||||||
# Check that the gstreamer documentation keeps working
|
# Check that the gstreamer documentation keeps working
|
||||||
documentation:
|
documentation:
|
||||||
image: $GSTREAMER_DOC_IMAGE
|
image: $GSTREAMER_DOC_IMAGE
|
||||||
|
stage: 'integration'
|
||||||
variables:
|
variables:
|
||||||
MESON_ARGS: >
|
MESON_ARGS: >
|
||||||
-Ddoc=enabled
|
-Ddoc=enabled
|
||||||
|
@ -258,7 +277,7 @@ documentation:
|
||||||
-Dsharp=disabled
|
-Dsharp=disabled
|
||||||
-Dgst-examples=disabled
|
-Dgst-examples=disabled
|
||||||
-Drs=enabled
|
-Drs=enabled
|
||||||
-Dgst-plugins-rs:sodium=system
|
-Dgst-plugins-rs:sodium-source=system
|
||||||
-Dgst-docs:fatal_warnings=true
|
-Dgst-docs:fatal_warnings=true
|
||||||
-Dorc=disabled
|
-Dorc=disabled
|
||||||
script:
|
script:
|
||||||
|
@ -279,54 +298,65 @@ documentation:
|
||||||
paths:
|
paths:
|
||||||
- documentation/
|
- documentation/
|
||||||
needs: []
|
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
|
# build gst-plugins-rs as a gst-build subproject
|
||||||
gst-build:
|
# Disabled because of https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/262
|
||||||
extends: .meson
|
#gst-build:
|
||||||
rules:
|
# extends: .meson
|
||||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
# rules:
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
# - if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||||
when: 'manual'
|
# - if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||||
allow_failure: true
|
# when: 'manual'
|
||||||
variables:
|
# allow_failure: true
|
||||||
MESON_ARGS: >
|
# variables:
|
||||||
-Domx=disabled
|
# MESON_ARGS: >
|
||||||
-Dpython=disabled
|
# -Domx=disabled
|
||||||
-Dlibav=disabled
|
# -Dpython=disabled
|
||||||
-Dlibnice=disabled
|
# -Dlibav=disabled
|
||||||
-Dugly=disabled
|
# -Dlibnice=disabled
|
||||||
-Dbad=disabled
|
# -Dugly=disabled
|
||||||
-Ddevtools=disabled
|
# -Dbad=disabled
|
||||||
-Dges=disabled
|
# -Ddevtools=disabled
|
||||||
-Drtsp_server=disabled
|
# -Dges=disabled
|
||||||
-Dvaapi=disabled
|
# -Drtsp_server=disabled
|
||||||
-Dsharp=disabled
|
# -Dvaapi=disabled
|
||||||
-Dgst-examples=disabled
|
# -Dsharp=disabled
|
||||||
-Drs=enabled
|
# -Dgst-examples=disabled
|
||||||
-Dgst-plugins-rs:sodium=system
|
# -Drs=enabled
|
||||||
script:
|
# -Dgst-plugins-rs:sodium-source=system
|
||||||
- P=$(pwd)
|
# script:
|
||||||
- cd ..
|
# - P=$(pwd)
|
||||||
- rm -rf gstreamer
|
# - cd ..
|
||||||
- git clone --depth 1 https://gitlab.freedesktop.org/gstreamer/gstreamer.git --branch main
|
# - rm -rf gstreamer
|
||||||
- cd gstreamer
|
# - git clone --depth 1 https://gitlab.freedesktop.org/gstreamer/gstreamer.git --branch main
|
||||||
- ln -s $P subprojects/gst-plugins-rs
|
# - cd gstreamer
|
||||||
- meson build $MESON_ARGS
|
# - ln -s $P subprojects/gst-plugins-rs
|
||||||
- ninja -C build
|
# - meson build $MESON_ARGS
|
||||||
# Check static Rust plugins can be linked into gst-full
|
# - ninja -C build
|
||||||
- meson build-gst-full --default-library=static $MESON_ARGS
|
# # Check static Rust plugins can be linked into gst-full
|
||||||
- ninja -C build-gst-full
|
# - meson build-gst-full --default-library=static $MESON_ARGS
|
||||||
- meson devenv -C build-gst-full ./gst-inspect-1.0 rsaudiofx
|
# - ninja -C build-gst-full
|
||||||
artifacts:
|
# - meson devenv -C build-gst-full ./gst-inspect-1.0 rsaudiofx
|
||||||
expire_in: '7 days'
|
# artifacts:
|
||||||
when: always
|
# expire_in: '7 days'
|
||||||
paths:
|
# when: always
|
||||||
- 'build/meson-logs/'
|
# paths:
|
||||||
- 'build-gst-full/meson-logs/'
|
# - 'build/meson-logs/'
|
||||||
|
# - 'build-gst-full/meson-logs/'
|
||||||
|
|
||||||
.msvc2019 build:
|
.msvc2019 build:
|
||||||
stage: 'test'
|
stage: 'test'
|
||||||
needs: []
|
needs:
|
||||||
|
- 'trigger'
|
||||||
tags:
|
tags:
|
||||||
- 'docker'
|
- 'docker'
|
||||||
- 'windows'
|
- '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/flavors",
|
||||||
"mux/fmp4",
|
"mux/fmp4",
|
||||||
|
"mux/mp4",
|
||||||
|
|
||||||
"net/aws",
|
"net/aws",
|
||||||
"net/hlssink3",
|
"net/hlssink3",
|
||||||
|
@ -35,6 +36,7 @@ members = [
|
||||||
"text/wrap",
|
"text/wrap",
|
||||||
|
|
||||||
"utils/fallbackswitch",
|
"utils/fallbackswitch",
|
||||||
|
"utils/livesync",
|
||||||
"utils/togglerecord",
|
"utils/togglerecord",
|
||||||
"utils/tracers",
|
"utils/tracers",
|
||||||
"utils/uriplaylistbin",
|
"utils/uriplaylistbin",
|
||||||
|
@ -63,6 +65,7 @@ default-members = [
|
||||||
"generic/threadshare",
|
"generic/threadshare",
|
||||||
|
|
||||||
"mux/fmp4",
|
"mux/fmp4",
|
||||||
|
"mux/mp4",
|
||||||
|
|
||||||
"net/aws",
|
"net/aws",
|
||||||
"net/hlssink3",
|
"net/hlssink3",
|
||||||
|
@ -82,6 +85,7 @@ default-members = [
|
||||||
"text/wrap",
|
"text/wrap",
|
||||||
|
|
||||||
"utils/fallbackswitch",
|
"utils/fallbackswitch",
|
||||||
|
"utils/livesync",
|
||||||
"utils/togglerecord",
|
"utils/togglerecord",
|
||||||
"utils/tracers",
|
"utils/tracers",
|
||||||
"utils/uriplaylistbin",
|
"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
|
configuring a fallback audio/video if there are problems with the main
|
||||||
source.
|
source.
|
||||||
|
|
||||||
|
- `livesync`: Element to maintain a continuous live stream from a
|
||||||
|
potentially unstable source.
|
||||||
|
|
||||||
- `togglerecord`: Element to enable starting and stopping multiple streams together.
|
- `togglerecord`: Element to enable starting and stopping multiple streams together.
|
||||||
|
|
||||||
- `tracers`: Plugin with multiple tracers:
|
- `tracers`: Plugin with multiple tracers:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-audiofx"
|
name = "gst-plugin-audiofx"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
@ -9,9 +9,9 @@ edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", 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", 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", 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"
|
anyhow = "1"
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
@ -29,11 +29,11 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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"] }
|
||||||
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -90,10 +90,10 @@ fn run() -> Result<(), Error> {
|
||||||
let new_z = -x * f32::sin(ROTATION) + z * f32::cos(ROTATION);
|
let new_z = -x * f32::sin(ROTATION) + z * f32::cos(ROTATION);
|
||||||
|
|
||||||
let objs = [gst::Structure::builder("application/spatial-object")
|
let objs = [gst::Structure::builder("application/spatial-object")
|
||||||
.field("x", &new_x)
|
.field("x", new_x)
|
||||||
.field("y", &y)
|
.field("y", y)
|
||||||
.field("z", &new_z)
|
.field("z", new_z)
|
||||||
.field("distance-gain", &gain)
|
.field("distance-gain", gain)
|
||||||
.build()];
|
.build()];
|
||||||
|
|
||||||
hrtf.set_property("spatial-objects", gst::Array::new(objs));
|
hrtf.set_property("spatial-objects", gst::Array::new(objs));
|
||||||
|
|
|
@ -15,8 +15,8 @@ pub struct RingBuffer {
|
||||||
|
|
||||||
impl RingBuffer {
|
impl RingBuffer {
|
||||||
pub fn new(size: usize) -> Self {
|
pub fn new(size: usize) -> Self {
|
||||||
let mut buffer = Vec::with_capacity(size as usize);
|
let mut buffer = Vec::with_capacity(size);
|
||||||
buffer.extend(iter::repeat(0.0).take(size as usize));
|
buffer.extend(iter::repeat(0.0).take(size));
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
buffer: buffer.into_boxed_slice(),
|
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
|
// the position where we have to start writing the next 100ms in the next
|
||||||
// iteration.
|
// iteration.
|
||||||
|
|
||||||
let mut outbuf = gst::Buffer::with_size(
|
let mut outbuf =
|
||||||
self.current_samples_per_frame as usize * self.info.bpf() as usize,
|
gst::Buffer::with_size(self.current_samples_per_frame * self.info.bpf() as usize)
|
||||||
)
|
|
||||||
.map_err(|_| gst::FlowError::Error)?;
|
.map_err(|_| gst::FlowError::Error)?;
|
||||||
{
|
{
|
||||||
let outbuf = outbuf.get_mut().unwrap();
|
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
|
// adjustment. frame_type should only ever be set to Final at the end if we ended up in
|
||||||
// Inner state before.
|
// Inner state before.
|
||||||
if self.frame_type == FrameType::First
|
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)?;
|
self.process_first_frame_is_last(imp)?;
|
||||||
}
|
}
|
||||||
|
@ -1560,7 +1559,7 @@ impl AudioLoudNorm {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to reset the state now
|
// 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);
|
state.adapter.push(buffer);
|
||||||
|
@ -1602,7 +1601,7 @@ impl AudioLoudNorm {
|
||||||
Err(_) => return false,
|
Err(_) => return false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
*state = Some(State::new(&*self.settings.lock().unwrap(), info));
|
*state = Some(State::new(&self.settings.lock().unwrap(), info));
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
if let Some(outbuf) = outbuf {
|
if let Some(outbuf) = outbuf {
|
||||||
|
@ -1623,7 +1622,7 @@ impl AudioLoudNorm {
|
||||||
Err(gst::FlowError::Eos) => None,
|
Err(gst::FlowError::Eos) => None,
|
||||||
Err(_) => return false,
|
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);
|
drop(state);
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::subclass::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;
|
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 {
|
struct ChannelDenoiser {
|
||||||
denoiser: Box<DenoiseState<'static>>,
|
denoiser: Box<DenoiseState<'static>>,
|
||||||
frame_chunk: Box<[f32; FRAME_SIZE]>,
|
frame_chunk: Box<[f32; FRAME_SIZE]>,
|
||||||
|
@ -47,6 +63,7 @@ struct State {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AudioRNNoise {
|
pub struct AudioRNNoise {
|
||||||
|
settings: Mutex<Settings>,
|
||||||
state: AtomicRefCell<Option<State>>,
|
state: AtomicRefCell<Option<State>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,43 +99,6 @@ impl State {
|
||||||
fn needs_more_data(&self) -> bool {
|
fn needs_more_data(&self) -> bool {
|
||||||
self.adapter.available() < (FRAME_SIZE * self.in_info.bpf() as usize)
|
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 {
|
impl AudioRNNoise {
|
||||||
|
@ -131,6 +111,7 @@ impl AudioRNNoise {
|
||||||
return Ok(gst::FlowSuccess::Ok);
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let settings = *self.settings.lock().unwrap();
|
||||||
let mut buffer = gst::Buffer::with_size(available).map_err(|e| {
|
let mut buffer = gst::Buffer::with_size(available).map_err(|e| {
|
||||||
gst::error!(CAT, imp: self, "Failed to allocate buffer at EOS {:?}", e);
|
gst::error!(CAT, imp: self, "Failed to allocate buffer at EOS {:?}", e);
|
||||||
gst::FlowError::Flushing
|
gst::FlowError::Flushing
|
||||||
|
@ -151,7 +132,7 @@ impl AudioRNNoise {
|
||||||
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
let mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
||||||
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
|
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)
|
self.obj().src_pad().push(buffer)
|
||||||
|
@ -164,6 +145,7 @@ impl AudioRNNoise {
|
||||||
let duration = state.buffer_duration(output_size as _);
|
let duration = state.buffer_duration(output_size as _);
|
||||||
let pts = state.current_pts();
|
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)?;
|
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 mut out_map = buffer.map_writable().map_err(|_| gst::FlowError::Error)?;
|
||||||
let out_data = out_map.as_mut_slice_of::<f32>().unwrap();
|
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))
|
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]
|
#[glib::object_subclass]
|
||||||
|
@ -195,7 +230,42 @@ impl ObjectSubclass for AudioRNNoise {
|
||||||
type ParentType = gst_base::BaseTransform;
|
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 {}
|
impl GstObjectImpl for AudioRNNoise {}
|
||||||
|
|
||||||
|
|
|
@ -138,7 +138,7 @@ impl ObjectImpl for EbuR128Level {
|
||||||
.build()]
|
.build()]
|
||||||
});
|
});
|
||||||
|
|
||||||
&*SIGNALS
|
&SIGNALS
|
||||||
}
|
}
|
||||||
|
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
@ -482,7 +482,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
|
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::M) {
|
if state.ebur128.mode().contains(ebur128::Mode::M) {
|
||||||
match state.ebur128.loudness_momentary() {
|
match state.ebur128.loudness_momentary() {
|
||||||
Ok(loudness) => s.set("momentary-loudness", &loudness),
|
Ok(loudness) => s.set("momentary-loudness", loudness),
|
||||||
Err(err) => gst::error!(
|
Err(err) => gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -494,7 +494,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
|
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::S) {
|
if state.ebur128.mode().contains(ebur128::Mode::S) {
|
||||||
match state.ebur128.loudness_shortterm() {
|
match state.ebur128.loudness_shortterm() {
|
||||||
Ok(loudness) => s.set("shortterm-loudness", &loudness),
|
Ok(loudness) => s.set("shortterm-loudness", loudness),
|
||||||
Err(err) => gst::error!(
|
Err(err) => gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -506,7 +506,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
|
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::I) {
|
if state.ebur128.mode().contains(ebur128::Mode::I) {
|
||||||
match state.ebur128.loudness_global() {
|
match state.ebur128.loudness_global() {
|
||||||
Ok(loudness) => s.set("global-loudness", &loudness),
|
Ok(loudness) => s.set("global-loudness", loudness),
|
||||||
Err(err) => gst::error!(
|
Err(err) => gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -516,7 +516,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
}
|
}
|
||||||
|
|
||||||
match state.ebur128.relative_threshold() {
|
match state.ebur128.relative_threshold() {
|
||||||
Ok(threshold) => s.set("relative-threshold", &threshold),
|
Ok(threshold) => s.set("relative-threshold", threshold),
|
||||||
Err(err) => gst::error!(
|
Err(err) => gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -528,7 +528,7 @@ impl BaseTransformImpl for EbuR128Level {
|
||||||
|
|
||||||
if state.ebur128.mode().contains(ebur128::Mode::LRA) {
|
if state.ebur128.mode().contains(ebur128::Mode::LRA) {
|
||||||
match state.ebur128.loudness_range() {
|
match state.ebur128.loudness_range() {
|
||||||
Ok(range) => s.set("loudness-range", &range),
|
Ok(range) => s.set("loudness-range", range),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
gst::error!(CAT, imp: self, "Failed to get loudness range: {}", 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 (prev_offset, _) = state.adapter.prev_offset();
|
||||||
let offset = prev_offset.checked_add(distance_samples).unwrap_or(0);
|
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);
|
let duration = samples_to_time(duration_samples as u64);
|
||||||
|
|
||||||
(pts, offset, duration)
|
(pts, offset, duration)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-claxon"
|
name = "gst-plugin-claxon"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Ruben Gonzalez <rgonzalez@fluendo.com>"]
|
authors = ["Ruben Gonzalez <rgonzalez@fluendo.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
@ -9,15 +9,15 @@ edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||||
gst-audio = { package = "gstreamer-audio", 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" }
|
||||||
claxon = { version = "0.4" }
|
claxon = { version = "0.4" }
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
atomic_refcell = "0.1"
|
atomic_refcell = "0.1"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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]
|
[lib]
|
||||||
name = "gstclaxon"
|
name = "gstclaxon"
|
||||||
|
@ -25,7 +25,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-csound"
|
name = "gst-plugin-csound"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Natanael Mojica <neithanmo@gmail.com>"]
|
authors = ["Natanael Mojica <neithanmo@gmail.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
@ -9,15 +9,15 @@ rust-version = "1.63"
|
||||||
description = "GStreamer Audio Filter plugin based on Csound"
|
description = "GStreamer Audio Filter plugin based on Csound"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
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" }
|
gst-audio = { package = "gstreamer-audio", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
csound = "0.1.8"
|
csound = "0.1.8"
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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]
|
[lib]
|
||||||
name = "gstcsound"
|
name = "gstcsound"
|
||||||
|
@ -29,7 +29,7 @@ name = "csound-effect"
|
||||||
path = "examples/effect_example.rs"
|
path = "examples/effect_example.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path = "../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path = "../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
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 audio_sink = gst::parse_bin_from_description(AUDIO_SINK, true)?.upcast();
|
||||||
|
|
||||||
let csoundfilter = gst::ElementFactory::make("csoundfilter")
|
let csoundfilter = gst::ElementFactory::make("csoundfilter")
|
||||||
.property("csd-text", &CSD)
|
.property("csd-text", CSD)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -517,15 +517,15 @@ impl BaseTransformImpl for CsoundFilter {
|
||||||
let ichannels = csound.input_channels() as i32;
|
let ichannels = csound.input_channels() as i32;
|
||||||
let ochannels = csound.output_channels() as i32;
|
let ochannels = csound.output_channels() as i32;
|
||||||
for s in new_caps.make_mut().iter_mut() {
|
for s in new_caps.make_mut().iter_mut() {
|
||||||
s.set("format", &gst_audio::AUDIO_FORMAT_F64.to_str());
|
s.set("format", gst_audio::AUDIO_FORMAT_F64.to_str());
|
||||||
s.set("rate", &sr);
|
s.set("rate", sr);
|
||||||
|
|
||||||
// replace the channel property with our values,
|
// replace the channel property with our values,
|
||||||
// if they are not supported, the negotiation will fail.
|
// if they are not supported, the negotiation will fail.
|
||||||
if direction == gst::PadDirection::Src {
|
if direction == gst::PadDirection::Src {
|
||||||
s.set("channels", &ichannels);
|
s.set("channels", ichannels);
|
||||||
} else {
|
} else {
|
||||||
s.set("channels", &ochannels);
|
s.set("channels", ochannels);
|
||||||
}
|
}
|
||||||
// Csound does not have a concept of channel-mask
|
// Csound does not have a concept of channel-mask
|
||||||
s.remove_field("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 {
|
fn build_harness(src_caps: gst::Caps, sink_caps: gst::Caps, csd: &str) -> gst_check::Harness {
|
||||||
let filter = gst::ElementFactory::make("csoundfilter")
|
let filter = gst::ElementFactory::make("csoundfilter")
|
||||||
.property("csd-text", &csd)
|
.property("csd-text", csd)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -260,10 +260,7 @@ fn csound_filter_underflow() {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(num_buffers, UNDERFLOW_NUM_BUFFERS / 2);
|
assert_eq!(num_buffers, UNDERFLOW_NUM_BUFFERS / 2);
|
||||||
assert_eq!(
|
assert_eq!(num_samples, UNDERFLOW_NUM_SAMPLES * UNDERFLOW_NUM_BUFFERS);
|
||||||
num_samples as usize,
|
|
||||||
UNDERFLOW_NUM_SAMPLES * UNDERFLOW_NUM_BUFFERS
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verifies that the caps negotiation is properly done, by pushing buffers whose caps
|
// Verifies that the caps negotiation is properly done, by pushing buffers whose caps
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-lewton"
|
name = "gst-plugin-lewton"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
@ -9,15 +9,15 @@ edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||||
gst-audio = { package = "gstreamer-audio", 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" }
|
||||||
lewton = { version = "0.10", default-features = false }
|
lewton = { version = "0.10", default-features = false }
|
||||||
byte-slice-cast = "1.0"
|
byte-slice-cast = "1.0"
|
||||||
atomic_refcell = "0.1"
|
atomic_refcell = "0.1"
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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]
|
[lib]
|
||||||
name = "gstlewton"
|
name = "gstlewton"
|
||||||
|
@ -25,7 +25,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -405,7 +405,7 @@ impl LewtonDec {
|
||||||
let outbuf = if let Some(ref reorder_map) = state.reorder_map {
|
let outbuf = if let Some(ref reorder_map) = state.reorder_map {
|
||||||
let mut outbuf = self
|
let mut outbuf = self
|
||||||
.obj()
|
.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
|
// And copy the decoded data into our output buffer while reordering the channels to the
|
||||||
// GStreamer channel order
|
// GStreamer channel order
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-spotify"
|
name = "gst-plugin-spotify"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Guillaume Desmottes <guillaume@desmottes.be>"]
|
authors = ["Guillaume Desmottes <guillaume@desmottes.be>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
@ -9,8 +9,8 @@ edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
librespot = { version = "0.4", default-features = false }
|
librespot = { version = "0.4", default-features = false }
|
||||||
tokio = "1.0"
|
tokio = "1.0"
|
||||||
|
@ -24,7 +24,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -6,9 +6,10 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use std::sync::{mpsc, Arc, Mutex};
|
use std::sync::{mpsc, Arc, Mutex, MutexGuard};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
|
use futures::future::{AbortHandle, Abortable, Aborted};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::{runtime, task::JoinHandle};
|
use tokio::{runtime, task::JoinHandle};
|
||||||
|
|
||||||
|
@ -74,10 +75,16 @@ struct Settings {
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SpotifyAudioSrc {
|
pub struct SpotifyAudioSrc {
|
||||||
|
setup_thread: Mutex<Option<SetupThread>>,
|
||||||
state: Arc<Mutex<Option<State>>>,
|
state: Arc<Mutex<Option<State>>>,
|
||||||
settings: Mutex<Settings>,
|
settings: Mutex<Settings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SetupThread {
|
||||||
|
thread_handle: std::thread::JoinHandle<Result<anyhow::Result<()>, Aborted>>,
|
||||||
|
abort_handle: AbortHandle,
|
||||||
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for SpotifyAudioSrc {
|
impl ObjectSubclass for SpotifyAudioSrc {
|
||||||
const NAME: &'static str = "GstSpotifyAudioSrc";
|
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);
|
let setup_thread = self.setup_thread.lock().unwrap();
|
||||||
gst::error!(CAT, imp: self, "failed to start: {}", details);
|
if setup_thread.is_some() {
|
||||||
gst::element_imp_error!(self, gst::ResourceError::Settings, [&details]);
|
// already starting
|
||||||
return Err(gst::error_msg!(gst::ResourceError::Settings, [&details]));
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.start_setup(setup_thread);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
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() {
|
if let Some(state) = self.state.lock().unwrap().take() {
|
||||||
gst::debug!(CAT, imp: self, "stopping");
|
gst::debug!(CAT, imp: self, "stopping");
|
||||||
state.player.stop();
|
state.player.stop();
|
||||||
|
@ -258,6 +270,12 @@ impl BaseSrcImpl for SpotifyAudioSrc {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unlock(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
|
self.cancel_setup();
|
||||||
|
|
||||||
|
self.parent_unlock()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PushSrcImpl for SpotifyAudioSrc {
|
impl PushSrcImpl for SpotifyAudioSrc {
|
||||||
|
@ -265,6 +283,41 @@ impl PushSrcImpl for SpotifyAudioSrc {
|
||||||
&self,
|
&self,
|
||||||
_buffer: Option<&mut gst::BufferRef>,
|
_buffer: Option<&mut gst::BufferRef>,
|
||||||
) -> Result<CreateSuccess, gst::FlowError> {
|
) -> 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 = self.state.lock().unwrap();
|
||||||
let state = state.as_ref().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 {
|
struct BufferSink {
|
||||||
sender: mpsc::SyncSender<Message>,
|
sender: mpsc::SyncSender<Message>,
|
||||||
}
|
}
|
||||||
|
@ -456,3 +403,144 @@ impl URIHandlerImpl for SpotifyAudioSrc {
|
||||||
Ok(())
|
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('src_dir', type=P)
|
||||||
PARSER.add_argument('root_dir', type=P)
|
PARSER.add_argument('root_dir', type=P)
|
||||||
PARSER.add_argument('target', choices=['release', 'debug'])
|
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('prefix', type=P)
|
||||||
PARSER.add_argument('libdir', type=P)
|
PARSER.add_argument('libdir', type=P)
|
||||||
PARSER.add_argument('--version', default=None)
|
PARSER.add_argument('--version', default=None)
|
||||||
PARSER.add_argument('--bin', default=None, type=P)
|
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('--depfile')
|
||||||
PARSER.add_argument('--disable-doc', action="store_true", default=False)
|
PARSER.add_argument('--disable-doc', action="store_true", default=False)
|
||||||
|
|
||||||
|
@ -33,7 +35,19 @@ def generate_depfile_for(fpath):
|
||||||
with open(f"{file_stem}.d", 'r') as depfile:
|
with open(f"{file_stem}.d", 'r') as depfile:
|
||||||
for l in depfile.readlines():
|
for l in depfile.readlines():
|
||||||
if l.startswith(str(file_stem)):
|
if l.startswith(str(file_stem)):
|
||||||
output, srcs = l.split(":", maxsplit=2)
|
# 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 = []
|
all_deps = []
|
||||||
for src in srcs.split(" "):
|
for src in srcs.split(" "):
|
||||||
|
@ -53,33 +67,30 @@ def generate_depfile_for(fpath):
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
opts = PARSER.parse_args()
|
opts = PARSER.parse_args()
|
||||||
|
logdir = opts.root_dir / 'meson-logs'
|
||||||
logfile = open(opts.root_dir / 'meson-logs' /
|
logfile_path = logdir / f'{opts.src_dir.name}-cargo-wrapper.log'
|
||||||
f'{opts.src_dir.name}-cargo-wrapper.log', 'w')
|
logfile = open(logfile_path, mode='w', buffering=1)
|
||||||
|
|
||||||
print(opts, file=logfile)
|
print(opts, file=logfile)
|
||||||
cargo_target_dir = opts.build_dir / 'target'
|
cargo_target_dir = opts.build_dir / 'target'
|
||||||
|
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env['CARGO_TARGET_DIR'] = str(cargo_target_dir)
|
if 'PKG_CONFIG_PATH' in env:
|
||||||
|
pkg_config_path = env['PKG_CONFIG_PATH'].split(os.pathsep)
|
||||||
pkg_config_path = env.get('PKG_CONFIG_PATH', '').split(':')
|
else:
|
||||||
|
pkg_config_path = []
|
||||||
pkg_config_path.append(str(opts.root_dir / 'meson-uninstalled'))
|
pkg_config_path.append(str(opts.root_dir / 'meson-uninstalled'))
|
||||||
env['PKG_CONFIG_PATH'] = ':'.join(pkg_config_path)
|
env['PKG_CONFIG_PATH'] = os.pathsep.join(pkg_config_path)
|
||||||
|
|
||||||
if opts.extra_env:
|
|
||||||
for e in opts.extra_env.split(','):
|
|
||||||
k, v = e.split(':')
|
|
||||||
env[k] = v
|
|
||||||
|
|
||||||
|
features = opts.features
|
||||||
if opts.command == 'build':
|
if opts.command == 'build':
|
||||||
cargo_cmd = ['cargo']
|
cargo_cmd = ['cargo']
|
||||||
if opts.bin:
|
if opts.bin or opts.examples:
|
||||||
cargo_cmd += ['build']
|
cargo_cmd += ['build']
|
||||||
else:
|
else:
|
||||||
cargo_cmd += ['cbuild']
|
cargo_cmd += ['cbuild']
|
||||||
if not opts.disable_doc:
|
if not opts.disable_doc:
|
||||||
cargo_cmd += ['--features', "doc"]
|
features += ['doc']
|
||||||
if opts.target == 'release':
|
if opts.target == 'release':
|
||||||
cargo_cmd.append('--release')
|
cargo_cmd.append('--release')
|
||||||
elif opts.command == 'test':
|
elif opts.command == 'test':
|
||||||
|
@ -89,40 +100,41 @@ if __name__ == "__main__":
|
||||||
print("Unknown command:", opts.command, file=logfile)
|
print("Unknown command:", opts.command, file=logfile)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
cwd = None
|
if features:
|
||||||
if not opts.bin:
|
cargo_cmd += ['--features', ','.join(features)]
|
||||||
cargo_cmd.extend(['--manifest-path', opts.src_dir / 'Cargo.toml'])
|
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',
|
cargo_cmd.extend(['--prefix', opts.prefix, '--libdir',
|
||||||
opts.prefix / opts.libdir])
|
opts.prefix / opts.libdir])
|
||||||
for p in opts.include.split(','):
|
for p in opts.packages:
|
||||||
cargo_cmd.extend(['-p', p])
|
cargo_cmd.extend(['-p', p])
|
||||||
else:
|
for e in opts.examples:
|
||||||
cargo_cmd.extend(['--bin', opts.bin.name])
|
cargo_cmd.extend(['--example', e])
|
||||||
cwd = opts.src_dir
|
|
||||||
|
|
||||||
def run(cargo_cmd, env, cwd=cwd):
|
def run(cargo_cmd, env):
|
||||||
|
print(cargo_cmd, env, file=logfile)
|
||||||
try:
|
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:
|
except subprocess.SubprocessError:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
run(cargo_cmd, env, cwd)
|
run(cargo_cmd, env)
|
||||||
|
|
||||||
if opts.command == 'build':
|
if opts.command == 'build':
|
||||||
target_dir = cargo_target_dir / '**' / opts.target
|
target_dir = cargo_target_dir / '**' / opts.target
|
||||||
if opts.bin:
|
if opts.bin:
|
||||||
if opts.exts[0]:
|
exe = glob.glob(str(target_dir / opts.bin) + opts.exe_suffix, recursive=True)[0]
|
||||||
ext = f'.{opts.exts[0]}'
|
|
||||||
else:
|
|
||||||
ext = ''
|
|
||||||
exe = glob.glob(str(target_dir / opts.bin) + ext, recursive=True)[0]
|
|
||||||
shutil.copy2(exe, opts.build_dir)
|
shutil.copy2(exe, opts.build_dir)
|
||||||
depfile_content = generate_depfile_for(P(exe))
|
depfile_content = generate_depfile_for(P(exe))
|
||||||
else:
|
else:
|
||||||
# Copy so files to build dir
|
# Copy so files to build dir
|
||||||
depfile_content = ""
|
depfile_content = ""
|
||||||
for ext in opts.exts:
|
for suffix in opts.lib_suffixes:
|
||||||
for f in glob.glob(str(target_dir / f'*.{ext}'), recursive=True):
|
for f in glob.glob(str(target_dir / f'*.{suffix}'), recursive=True):
|
||||||
libfile = P(f)
|
libfile = P(f)
|
||||||
|
|
||||||
depfile_content += generate_depfile_for(libfile)
|
depfile_content += generate_depfile_for(libfile)
|
||||||
|
@ -137,6 +149,12 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
print(f"Copying {copied_file}", file=logfile)
|
print(f"Copying {copied_file}", file=logfile)
|
||||||
shutil.copy2(f, opts.build_dir)
|
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:
|
with open(opts.depfile, 'w') as depfile:
|
||||||
depfile.write(depfile_content)
|
depfile.write(depfile_content)
|
||||||
|
|
|
@ -7,7 +7,8 @@ import os
|
||||||
from utils import iterate_plugins
|
from utils import iterate_plugins
|
||||||
|
|
||||||
# the csound version used on ci does not ship a .pc file
|
# 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]
|
outdir = sys.argv[1]
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,4 @@ source ./ci/env.sh
|
||||||
set -e
|
set -e
|
||||||
export CARGO_HOME='/usr/local/cargo'
|
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 (
|
param (
|
||||||
$Features
|
$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 "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 (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Build failed"
|
Write-Host "Build failed"
|
||||||
|
@ -29,7 +36,7 @@ function Run-Tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
$env:G_DEBUG="fatal_warnings"
|
$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 (!$?) {
|
if (!$?) {
|
||||||
Write-Host "Tests failed"
|
Write-Host "Tests failed"
|
||||||
|
|
64
deny.toml
64
deny.toml
|
@ -9,10 +9,13 @@ ignore = [
|
||||||
"RUSTSEC-2021-0059",
|
"RUSTSEC-2021-0059",
|
||||||
"RUSTSEC-2021-0060",
|
"RUSTSEC-2021-0060",
|
||||||
"RUSTSEC-2021-0061",
|
"RUSTSEC-2021-0061",
|
||||||
|
"RUSTSEC-2021-0145",
|
||||||
# https://github.com/chronotope/chrono/issues/499
|
# https://github.com/chronotope/chrono/issues/499
|
||||||
"RUSTSEC-2020-0071",
|
"RUSTSEC-2020-0071",
|
||||||
# sodiumoxide is deprecated
|
# sodiumoxide is deprecated
|
||||||
"RUSTSEC-2021-0137",
|
"RUSTSEC-2021-0137",
|
||||||
|
# https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/256
|
||||||
|
"RUSTSEC-2022-0048",
|
||||||
]
|
]
|
||||||
|
|
||||||
[licenses]
|
[licenses]
|
||||||
|
@ -55,12 +58,6 @@ wildcards = "allow"
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.1"
|
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
|
# ignore duplicated rustc_version dependency because rav1e depends on an old version
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
|
@ -69,31 +66,13 @@ version = "0.3"
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "0.11"
|
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
|
# ignore duplicated crc dependency because ffv1 depends on an old version
|
||||||
# https://github.com/rust-av/ffv1/issues/21
|
# https://github.com/rust-av/ffv1/issues/21
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "1.8"
|
version = "1.8"
|
||||||
|
|
||||||
# ignore duplicated heck dependency because various crates depend on an old version
|
# Ignore various duplicated dependencies because librespot depends on an old versions
|
||||||
[[bans.skip]]
|
|
||||||
name = "heck"
|
|
||||||
version = "0.3"
|
|
||||||
|
|
||||||
# ignore duplicated sha-1/digest/block-buffer dependencies because librespot depends on an old version
|
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.9"
|
version = "0.9"
|
||||||
|
@ -103,6 +82,12 @@ version = "0.9"
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.9"
|
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
|
# ignore duplicated wasi dependency because various crates depends on an old version
|
||||||
[[bans.skip]]
|
[[bans.skip]]
|
||||||
|
@ -114,6 +99,35 @@ version = "0.10"
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.5"
|
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]
|
[sources]
|
||||||
unknown-registry = "deny"
|
unknown-registry = "deny"
|
||||||
unknown-git = "deny"
|
unknown-git = "deny"
|
||||||
|
|
|
@ -9,7 +9,7 @@ if meson.is_cross_build()
|
||||||
subdir_done()
|
subdir_done()
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if static_build
|
if default_library == 'static'
|
||||||
if get_option('doc').enabled()
|
if get_option('doc').enabled()
|
||||||
error('Documentation enabled but not supported when building statically.')
|
error('Documentation enabled but not supported when building statically.')
|
||||||
endif
|
endif
|
||||||
|
|
|
@ -165,6 +165,18 @@
|
||||||
"readable": true,
|
"readable": true,
|
||||||
"type": "gchararray",
|
"type": "gchararray",
|
||||||
"writable": true
|
"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"
|
"rank": "none"
|
||||||
|
@ -1070,7 +1082,7 @@
|
||||||
"writable": true
|
"writable": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rank": "primary + 1"
|
"rank": "primary"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filename": "gstdav1d",
|
"filename": "gstdav1d",
|
||||||
|
@ -1589,7 +1601,8 @@
|
||||||
"sink": {
|
"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 ]\naudio/mpeg:\n mpegversion: 4\n stream-format: raw\n channels: [ 1, 65535 ]\n rate: [ 1, 2147483647 ]\n",
|
||||||
"direction": "sink",
|
"direction": "sink",
|
||||||
"presence": "always"
|
"presence": "always",
|
||||||
|
"type": "GstFMP4MuxPad"
|
||||||
},
|
},
|
||||||
"src": {
|
"src": {
|
||||||
"caps": "video/quicktime:\n variant: cmaf\n",
|
"caps": "video/quicktime:\n variant: cmaf\n",
|
||||||
|
@ -1615,9 +1628,10 @@
|
||||||
"long-name": "DASHMP4Mux",
|
"long-name": "DASHMP4Mux",
|
||||||
"pad-templates": {
|
"pad-templates": {
|
||||||
"sink": {
|
"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",
|
"direction": "sink",
|
||||||
"presence": "always"
|
"presence": "always",
|
||||||
|
"type": "GstFMP4MuxPad"
|
||||||
},
|
},
|
||||||
"src": {
|
"src": {
|
||||||
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
||||||
|
@ -1643,9 +1657,10 @@
|
||||||
"long-name": "ISOFMP4Mux",
|
"long-name": "ISOFMP4Mux",
|
||||||
"pad-templates": {
|
"pad-templates": {
|
||||||
"sink_%%u": {
|
"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",
|
"direction": "sink",
|
||||||
"presence": "request"
|
"presence": "request",
|
||||||
|
"type": "GstFMP4MuxPad"
|
||||||
},
|
},
|
||||||
"src": {
|
"src": {
|
||||||
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
||||||
|
@ -1673,7 +1688,8 @@
|
||||||
"sink_%%u": {
|
"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",
|
"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",
|
"direction": "sink",
|
||||||
"presence": "request"
|
"presence": "request",
|
||||||
|
"type": "GstFMP4MuxPad"
|
||||||
},
|
},
|
||||||
"src": {
|
"src": {
|
||||||
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
"caps": "video/quicktime:\n variant: iso-fragmented\n",
|
||||||
|
@ -1752,6 +1768,20 @@
|
||||||
"type": "guint64",
|
"type": "guint64",
|
||||||
"writable": true
|
"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": {
|
"write-mehd": {
|
||||||
"blurb": "Write movie extends header box with the duration at the end of the stream (needs a header-update-mode enabled)",
|
"blurb": "Write movie extends header box with the duration at the end of the stream (needs a header-update-mode enabled)",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
|
@ -1797,6 +1827,33 @@
|
||||||
"value": "2"
|
"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",
|
"package": "gst-plugin-fmp4",
|
||||||
|
@ -1895,7 +1952,7 @@
|
||||||
"long-name": "GTK 4 Paintable Sink",
|
"long-name": "GTK 4 Paintable Sink",
|
||||||
"pad-templates": {
|
"pad-templates": {
|
||||||
"sink": {
|
"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",
|
"direction": "sink",
|
||||||
"presence": "always"
|
"presence": "always"
|
||||||
}
|
}
|
||||||
|
@ -2452,6 +2509,293 @@
|
||||||
"tracers": {},
|
"tracers": {},
|
||||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
"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": {
|
"ndi": {
|
||||||
"description": "GStreamer NewTek NDI Plugin",
|
"description": "GStreamer NewTek NDI Plugin",
|
||||||
"elements": {
|
"elements": {
|
||||||
|
@ -3628,6 +3972,22 @@
|
||||||
"presence": "always"
|
"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"
|
"rank": "none"
|
||||||
},
|
},
|
||||||
"ebur128level": {
|
"ebur128level": {
|
||||||
|
@ -4762,40 +5122,6 @@
|
||||||
"rsonvif": {
|
"rsonvif": {
|
||||||
"description": "GStreamer Rust ONVIF Plugin",
|
"description": "GStreamer Rust ONVIF Plugin",
|
||||||
"elements": {
|
"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": {
|
"onvifmetadatacombiner": {
|
||||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||||
"description": "ONVIF metadata combiner",
|
"description": "ONVIF metadata combiner",
|
||||||
|
@ -4926,73 +5252,6 @@
|
||||||
},
|
},
|
||||||
"rank": "none"
|
"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": {
|
"rtponvifmetadatadepay": {
|
||||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||||
"description": "ONVIF metadata RTP depayloader",
|
"description": "ONVIF metadata RTP depayloader",
|
||||||
|
@ -5046,33 +5305,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rank": "primary"
|
"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",
|
"filename": "gstrsonvif",
|
||||||
|
@ -6201,6 +6433,32 @@
|
||||||
"type": "guint",
|
"type": "guint",
|
||||||
"writable": true
|
"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": {
|
"separator": {
|
||||||
"blurb": "Text inserted between each text buffers",
|
"blurb": "Text inserted between each text buffers",
|
||||||
"conditionally-available": false,
|
"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": {
|
"ts-input-selector": {
|
||||||
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
"author": "Mathieu Duponchelle <mathieu@centricular.com>",
|
||||||
"description": "Simple input selector element",
|
"description": "Simple input selector element",
|
||||||
|
@ -7581,8 +7939,145 @@
|
||||||
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
"url": "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
},
|
},
|
||||||
"webrtchttp": {
|
"webrtchttp": {
|
||||||
"description": "GStreamer WebRTC Plugin for WebRTC HTTP protocols (WHIP)",
|
"description": "GStreamer WebRTC Plugin for WebRTC HTTP protocols (WHIP/WHEP)",
|
||||||
"elements": {
|
"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": {
|
"whipsink": {
|
||||||
"author": "Taruntej Kanakamalla <taruntej@asymptotic.io>",
|
"author": "Taruntej Kanakamalla <taruntej@asymptotic.io>",
|
||||||
"description": "A bin to stream media using the WebRTC HTTP Ingestion Protocol (WHIP)",
|
"description": "A bin to stream media using the WebRTC HTTP Ingestion Protocol (WHIP)",
|
||||||
|
@ -7619,8 +8114,58 @@
|
||||||
"type": "gchararray",
|
"type": "gchararray",
|
||||||
"writable": true
|
"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": {
|
"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,
|
"conditionally-available": false,
|
||||||
"construct": false,
|
"construct": false,
|
||||||
"construct-only": false,
|
"construct-only": false,
|
||||||
|
@ -7649,7 +8194,23 @@
|
||||||
},
|
},
|
||||||
"filename": "gstwebrtchttp",
|
"filename": "gstwebrtchttp",
|
||||||
"license": "MPL",
|
"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",
|
"package": "gst-plugin-webrtchttp",
|
||||||
"source": "gst-plugin-webrtchttp",
|
"source": "gst-plugin-webrtchttp",
|
||||||
"tracers": {},
|
"tracers": {},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-file"
|
name = "gst-plugin-file"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
@ -10,8 +10,8 @@ rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
url = "2"
|
url = "2"
|
||||||
gst = { package = "gstreamer", 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" }
|
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -20,7 +20,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -74,7 +74,7 @@ impl FileLocation {
|
||||||
.parent()
|
.parent()
|
||||||
.expect("FileSink::set_location `location` with filename but without a parent")
|
.expect("FileSink::set_location `location` with filename but without a parent")
|
||||||
.to_owned();
|
.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
|
// `location` only contains the filename
|
||||||
// need to specify "." for `canonicalize` to resolve the actual path
|
// need to specify "." for `canonicalize` to resolve the actual path
|
||||||
parent_dir = PathBuf::from(".");
|
parent_dir = PathBuf::from(".");
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-sodium"
|
name = "gst-plugin-sodium"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Jordan Petridis <jordan@centricular.com>"]
|
authors = ["Jordan Petridis <jordan@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
description = "GStreamer plugin for libsodium-based file encryption and decryption"
|
description = "GStreamer plugin for libsodium-based file encryption and decryption"
|
||||||
|
@ -9,8 +9,8 @@ edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package="gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst = { package="gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
sodiumoxide = "0.2.1"
|
sodiumoxide = "0.2.1"
|
||||||
once_cell = "1.3.0"
|
once_cell = "1.3.0"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
@ -27,10 +27,14 @@ rand = "0.8"
|
||||||
|
|
||||||
[dev-dependencies.gst-check]
|
[dev-dependencies.gst-check]
|
||||||
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
||||||
|
branch = "0.19"
|
||||||
|
version = "0.19"
|
||||||
package="gstreamer-check"
|
package="gstreamer-check"
|
||||||
|
|
||||||
[dev-dependencies.gst-app]
|
[dev-dependencies.gst-app]
|
||||||
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
|
||||||
|
branch = "0.19"
|
||||||
|
version = "0.19"
|
||||||
package="gstreamer-app"
|
package="gstreamer-app"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -54,7 +58,7 @@ path = "examples/decrypt_example.rs"
|
||||||
required-features = ["serde", "serde_json", "clap"]
|
required-features = ["serde", "serde_json", "clap"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -53,7 +53,7 @@ struct Keys {
|
||||||
|
|
||||||
impl Keys {
|
impl Keys {
|
||||||
fn from_file(file: &Path) -> Result<Self, Box<dyn Error>> {
|
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)
|
serde_json::from_reader(f).map_err(From::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ struct Keys {
|
||||||
|
|
||||||
impl Keys {
|
impl Keys {
|
||||||
fn from_file(file: &Path) -> Result<Self, Box<dyn Error>> {
|
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)
|
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 offset: {}", offset);
|
||||||
gst::debug!(CAT, obj: pad, "Requested size: {}", requested_size);
|
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);
|
gst::debug!(CAT, obj: pad, "Stream Block index: {}", chunk_index);
|
||||||
|
|
||||||
let pull_offset = offset - (chunk_index * block_size as u64);
|
let pull_offset = offset - (chunk_index * block_size as u64);
|
||||||
|
|
|
@ -75,7 +75,7 @@ fn test_pipeline() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let filesrc = gst::ElementFactory::make("filesrc")
|
let filesrc = gst::ElementFactory::make("filesrc")
|
||||||
.property("location", &input_path.to_str().unwrap())
|
.property("location", input_path.to_str().unwrap())
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-threadshare"
|
name = "gst-plugin-threadshare"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
license = "LGPL-2.1-or-later"
|
license = "LGPL-2.1-or-later"
|
||||||
description = "GStreamer Threadshare Plugin"
|
description = "GStreamer Threadshare Plugin"
|
||||||
|
@ -10,14 +10,15 @@ rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-task = "4.3.0"
|
async-task = "4.3.0"
|
||||||
concurrent-queue = "1.2.2"
|
concurrent-queue = "2"
|
||||||
flume = "0.10.13"
|
flume = "0.10.13"
|
||||||
futures = "0.3.21"
|
futures = "0.3.21"
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
gio = { git = "https://github.com/gtk-rs/gtk-rs-core" }
|
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" }
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||||
gst-net = { package = "gstreamer-net", 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-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
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"
|
once_cell = "1"
|
||||||
pin-project-lite = "0.2.0"
|
pin-project-lite = "0.2.0"
|
||||||
polling = "2.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"] }
|
winapi = { version = "0.3.9", features = ["winsock2", "processthreadsapi"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[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" }
|
||||||
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstthreadshare"
|
name = "gstthreadshare"
|
||||||
|
@ -42,7 +43,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "benchmark"
|
name = "ts-benchmark"
|
||||||
path = "examples/benchmark.rs"
|
path = "examples/benchmark.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
@ -58,7 +59,7 @@ name = "ts-standalone"
|
||||||
path = "examples/standalone/main.rs"
|
path = "examples/standalone/main.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
cc = "1.0.38"
|
cc = "1.0.38"
|
||||||
pkg-config = "0.3.15"
|
pkg-config = "0.3.15"
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ fn main() {
|
||||||
};
|
};
|
||||||
let is_rtp = args.len() > 6 && (args[6] == "rtp");
|
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("media", "audio")
|
||||||
.field("payload", 8i32)
|
.field("payload", 8i32)
|
||||||
.field("clock-rate", 8000)
|
.field("clock-rate", 8000)
|
||||||
|
@ -97,7 +97,7 @@ fn main() {
|
||||||
"udpsrc" => {
|
"udpsrc" => {
|
||||||
let source = gst::ElementFactory::make("udpsrc")
|
let source = gst::ElementFactory::make("udpsrc")
|
||||||
.name(format!("source-{}", i).as_str())
|
.name(format!("source-{}", i).as_str())
|
||||||
.property("port", 40000i32 + i as i32)
|
.property("port", 5004i32 + i as i32)
|
||||||
.property("retrieve-sender-address", false)
|
.property("retrieve-sender-address", false)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -108,7 +108,7 @@ fn main() {
|
||||||
let context = build_context();
|
let context = build_context();
|
||||||
let source = gst::ElementFactory::make("ts-udpsrc")
|
let source = gst::ElementFactory::make("ts-udpsrc")
|
||||||
.name(format!("source-{}", i).as_str())
|
.name(format!("source-{}", i).as_str())
|
||||||
.property("port", 40000i32 + i as i32)
|
.property("port", 5004i32 + i as i32)
|
||||||
.property("context", &context)
|
.property("context", &context)
|
||||||
.property("context-wait", wait)
|
.property("context-wait", wait)
|
||||||
.build()
|
.build()
|
||||||
|
@ -158,7 +158,7 @@ fn main() {
|
||||||
let context = build_context();
|
let context = build_context();
|
||||||
let source = gst::ElementFactory::make("ts-tonesrc")
|
let source = gst::ElementFactory::make("ts-tonesrc")
|
||||||
.name(format!("source-{}", i).as_str())
|
.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", &context)
|
||||||
.property("context-wait", wait)
|
.property("context-wait", wait)
|
||||||
.build()
|
.build()
|
||||||
|
@ -184,22 +184,7 @@ fn main() {
|
||||||
pipeline.add_many(elements).unwrap();
|
pipeline.add_many(elements).unwrap();
|
||||||
gst::Element::link_many(elements).unwrap();
|
gst::Element::link_many(elements).unwrap();
|
||||||
} else {
|
} else {
|
||||||
let queue = if let Some(context) = context {
|
let elements = &[&source, &sink];
|
||||||
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];
|
|
||||||
pipeline.add_many(elements).unwrap();
|
pipeline.add_many(elements).unwrap();
|
||||||
gst::Element::link_many(elements).unwrap();
|
gst::Element::link_many(elements).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -268,14 +253,14 @@ fn main() {
|
||||||
let elapsed = init.elapsed();
|
let elapsed = init.elapsed();
|
||||||
gst::info!(
|
gst::info!(
|
||||||
CAT,
|
CAT,
|
||||||
"{:>6.2} / s / stream",
|
"Thrpt: {:>6.2}",
|
||||||
total_count * 1_000.0 / elapsed.as_millis() as f32
|
total_count * 1_000.0 / elapsed.as_millis() as f32
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "tuning")]
|
#[cfg(feature = "tuning")]
|
||||||
gst::info!(
|
gst::info!(
|
||||||
CAT,
|
CAT,
|
||||||
"{:>6.2}% parked",
|
"Parked: {:>6.2}%",
|
||||||
(ctx_0.parked_duration() - parked_init).as_nanos() as f32 * 100.0
|
(ctx_0.parked_duration() - parked_init).as_nanos() as f32 * 100.0
|
||||||
/ elapsed.as_nanos() as f32
|
/ 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 gst::glib;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
mod args;
|
||||||
|
use args::*;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
|
||||||
mod sink;
|
mod sink;
|
||||||
mod src;
|
mod src;
|
||||||
|
|
||||||
|
use std::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
gst::DebugCategory::new(
|
gst::DebugCategory::new(
|
||||||
"ts-standalone-test-main",
|
"ts-standalone-main",
|
||||||
gst::DebugColorFlags::empty(),
|
gst::DebugColorFlags::empty(),
|
||||||
Some("Thread-sharing standalone test main"),
|
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> {
|
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
src::register(plugin)?;
|
src::register(plugin)?;
|
||||||
sink::register(plugin)?;
|
sink::async_mutex::register(plugin)?;
|
||||||
|
sink::sync_mutex::register(plugin)?;
|
||||||
|
sink::task::register(plugin)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -31,91 +42,6 @@ gst::plugin_define!(
|
||||||
env!("BUILD_REL_DATE")
|
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() {
|
fn main() {
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
@ -133,8 +59,8 @@ fn main() {
|
||||||
for i in 0..args.streams {
|
for i in 0..args.streams {
|
||||||
let ctx_name = format!("standalone {}", i % args.groups);
|
let ctx_name = format!("standalone {}", i % args.groups);
|
||||||
|
|
||||||
let src = gst::ElementFactory::make("ts-standalone-test-src")
|
let src = gst::ElementFactory::make(src::ELEMENT_NAME)
|
||||||
.name(format!("src-{}", i).as_str())
|
.name(format!("src-{i}").as_str())
|
||||||
.property("context", &ctx_name)
|
.property("context", &ctx_name)
|
||||||
.property("context-wait", args.wait)
|
.property("context-wait", args.wait)
|
||||||
.property("push-period", args.push_period)
|
.property("push-period", args.push_period)
|
||||||
|
@ -142,16 +68,16 @@ fn main() {
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let sink = gst::ElementFactory::make("ts-standalone-test-sink")
|
let sink = gst::ElementFactory::make(args.sink.element_name())
|
||||||
.name(format!("sink-{}", i).as_str())
|
.name(format!("sink-{i}").as_str())
|
||||||
.property("context", &ctx_name)
|
.property("context", &ctx_name)
|
||||||
.property("context-wait", args.wait)
|
.property("context-wait", args.wait)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
src.set_property("raise-log-level", true);
|
src.set_property("main-elem", true);
|
||||||
sink.set_property("raise-log-level", true);
|
sink.set_property("main-elem", true);
|
||||||
|
|
||||||
if !args.disable_stats_log {
|
if !args.disable_stats_log {
|
||||||
// Don't use the last 5 secs in stats
|
// Don't use the last 5 secs in stats
|
||||||
|
@ -179,30 +105,46 @@ fn main() {
|
||||||
let l = glib::MainLoop::new(None, false);
|
let l = glib::MainLoop::new(None, false);
|
||||||
|
|
||||||
let bus = pipeline.bus().unwrap();
|
let bus = pipeline.bus().unwrap();
|
||||||
|
let terminated_count = Arc::new(AtomicU32::new(0));
|
||||||
let pipeline_clone = pipeline.clone();
|
let pipeline_clone = pipeline.clone();
|
||||||
let l_clone = l.clone();
|
let l_clone = l.clone();
|
||||||
bus.add_watch(move |_, msg| {
|
bus.add_watch(move |_, msg| {
|
||||||
use gst::MessageView;
|
use gst::MessageView;
|
||||||
|
|
||||||
match msg.view() {
|
match msg.view() {
|
||||||
MessageView::Eos(_) => {
|
MessageView::Eos(_) => {
|
||||||
|
// Actually, we don't post EOS (see sinks impl).
|
||||||
gst::info!(CAT, "Received eos");
|
gst::info!(CAT, "Received eos");
|
||||||
l_clone.quit();
|
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!(
|
gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
"Error from {:?}: {} ({:?})",
|
"Error from {:?}: {} ({:?})",
|
||||||
err.src().map(|s| s.path_string()),
|
msg.src().map(|s| s.path_string()),
|
||||||
err.error(),
|
msg.error(),
|
||||||
err.debug()
|
msg.debug()
|
||||||
);
|
);
|
||||||
l_clone.quit();
|
l_clone.quit();
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
|
|
||||||
glib::Continue(true)
|
glib::Continue(false)
|
||||||
|
}
|
||||||
|
_ => glib::Continue(true),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.expect("Failed to add bus watch");
|
.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;
|
pub mod async_mutex;
|
||||||
use gst::prelude::*;
|
pub mod sync_mutex;
|
||||||
|
pub mod task;
|
||||||
|
|
||||||
mod imp;
|
mod settings;
|
||||||
|
pub use settings::Settings;
|
||||||
|
|
||||||
glib::wrapper! {
|
mod stats;
|
||||||
pub struct TestSink(ObjectSubclass<imp::TestSink>) @extends gst::Element, gst::Object;
|
pub use stats::Stats;
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
pub const ASYNC_MUTEX_ELEMENT_NAME: &str = "ts-standalone-async-mutex-sink";
|
||||||
gst::Element::register(
|
pub const SYNC_MUTEX_ELEMENT_NAME: &str = "ts-standalone-sync-mutex-sink";
|
||||||
Some(plugin),
|
pub const TASK_ELEMENT_NAME: &str = "ts-standalone-task-sink";
|
||||||
"ts-standalone-test-sink",
|
|
||||||
gst::Rank::None,
|
use once_cell::sync::Lazy;
|
||||||
TestSink::static_type(),
|
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 std::time::Duration;
|
||||||
|
|
||||||
use gstthreadshare::runtime::prelude::*;
|
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(|| {
|
static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
gst::DebugCategory::new(
|
gst::DebugCategory::new(
|
||||||
"ts-standalone-test-src",
|
super::ELEMENT_NAME,
|
||||||
gst::DebugColorFlags::empty(),
|
gst::DebugColorFlags::empty(),
|
||||||
Some("Thread-sharing standalone test src"),
|
Some("Thread-sharing standalone test src"),
|
||||||
)
|
)
|
||||||
|
@ -39,7 +39,7 @@ struct Settings {
|
||||||
context: String,
|
context: String,
|
||||||
context_wait: Duration,
|
context_wait: Duration,
|
||||||
push_period: gst::ClockTime,
|
push_period: gst::ClockTime,
|
||||||
raise_log_level: bool,
|
is_main_elem: bool,
|
||||||
num_buffers: Option<u32>,
|
num_buffers: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ impl Default for Settings {
|
||||||
context: DEFAULT_CONTEXT.into(),
|
context: DEFAULT_CONTEXT.into(),
|
||||||
context_wait: DEFAULT_CONTEXT_WAIT,
|
context_wait: DEFAULT_CONTEXT_WAIT,
|
||||||
push_period: DEFAULT_PUSH_PERIOD,
|
push_period: DEFAULT_PUSH_PERIOD,
|
||||||
raise_log_level: false,
|
is_main_elem: false,
|
||||||
num_buffers: Some(DEFAULT_NUM_BUFFERS as u32),
|
num_buffers: Some(DEFAULT_NUM_BUFFERS as u32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,19 +63,18 @@ impl PadSrcHandler for TestSrcPadHandler {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct SrcTask {
|
struct SrcTask {
|
||||||
element: super::TestSrc,
|
elem: super::TestSrc,
|
||||||
buffer_pool: gst::BufferPool,
|
buffer_pool: gst::BufferPool,
|
||||||
timer: Option<timer::Interval>,
|
timer: Option<timer::Interval>,
|
||||||
raise_log_level: bool,
|
is_main_elem: bool,
|
||||||
push_period: gst::ClockTime,
|
push_period: gst::ClockTime,
|
||||||
need_initial_events: bool,
|
need_initial_events: bool,
|
||||||
need_segment: bool,
|
|
||||||
num_buffers: Option<u32>,
|
num_buffers: Option<u32>,
|
||||||
buffer_count: u32,
|
buffer_count: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SrcTask {
|
impl SrcTask {
|
||||||
fn new(element: super::TestSrc) -> Self {
|
fn new(elem: super::TestSrc) -> Self {
|
||||||
let buffer_pool = gst::BufferPool::new();
|
let buffer_pool = gst::BufferPool::new();
|
||||||
let mut pool_config = buffer_pool.config();
|
let mut pool_config = buffer_pool.config();
|
||||||
pool_config
|
pool_config
|
||||||
|
@ -84,13 +83,12 @@ impl SrcTask {
|
||||||
buffer_pool.set_config(pool_config).unwrap();
|
buffer_pool.set_config(pool_config).unwrap();
|
||||||
|
|
||||||
SrcTask {
|
SrcTask {
|
||||||
element,
|
elem,
|
||||||
buffer_pool,
|
buffer_pool,
|
||||||
timer: None,
|
timer: None,
|
||||||
raise_log_level: false,
|
is_main_elem: false,
|
||||||
push_period: gst::ClockTime::ZERO,
|
push_period: gst::ClockTime::ZERO,
|
||||||
need_initial_events: true,
|
need_initial_events: true,
|
||||||
need_segment: true,
|
|
||||||
num_buffers: Some(DEFAULT_NUM_BUFFERS as u32),
|
num_buffers: Some(DEFAULT_NUM_BUFFERS as u32),
|
||||||
buffer_count: 0,
|
buffer_count: 0,
|
||||||
}
|
}
|
||||||
|
@ -98,34 +96,48 @@ impl SrcTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskImpl for SrcTask {
|
impl TaskImpl for SrcTask {
|
||||||
type Item = gst::Buffer;
|
type Item = ();
|
||||||
|
|
||||||
fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
fn prepare(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||||
async move {
|
let imp = self.elem.imp();
|
||||||
let src = self.element.imp();
|
let settings = imp.settings.lock().unwrap();
|
||||||
let settings = src.settings.lock().unwrap();
|
self.is_main_elem = settings.is_main_elem;
|
||||||
self.raise_log_level = settings.raise_log_level;
|
|
||||||
|
|
||||||
if self.raise_log_level {
|
log_or_trace!(CAT, self.is_main_elem, imp: imp, "Preparing Task");
|
||||||
gst::log!(CAT, obj: self.element, "Preparing Task");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, obj: self.element, "Preparing Task");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.push_period = settings.push_period;
|
self.push_period = settings.push_period;
|
||||||
self.num_buffers = settings.num_buffers;
|
self.num_buffers = settings.num_buffers;
|
||||||
|
|
||||||
Ok(())
|
future::ok(()).boxed()
|
||||||
}
|
|
||||||
.boxed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
fn start(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||||
async {
|
async move {
|
||||||
if self.raise_log_level {
|
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Starting Task");
|
||||||
gst::log!(CAT, obj: self.element, "Starting Task");
|
|
||||||
} else {
|
if self.need_initial_events {
|
||||||
gst::trace!(CAT, obj: self.element, "Starting Task");
|
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(
|
self.timer = Some(
|
||||||
|
@ -138,178 +150,100 @@ impl TaskImpl for SrcTask {
|
||||||
);
|
);
|
||||||
self.buffer_count = 0;
|
self.buffer_count = 0;
|
||||||
self.buffer_pool.set_active(true).unwrap();
|
self.buffer_pool.set_active(true).unwrap();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
fn stop(&mut self) -> BoxFuture<'_, Result<(), gst::ErrorMessage>> {
|
||||||
async move {
|
log_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Stopping Task");
|
||||||
if self.raise_log_level {
|
|
||||||
gst::log!(CAT, obj: self.element, "Stopping Task");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, obj: self.element, "Stopping Task");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.buffer_pool.set_active(false).unwrap();
|
self.buffer_pool.set_active(false).unwrap();
|
||||||
self.timer = None;
|
self.timer = None;
|
||||||
self.need_initial_events = true;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_next(&mut self) -> BoxFuture<'_, Result<gst::Buffer, gst::FlowError>> {
|
fn handle_item(&mut self, _: ()) -> BoxFuture<'_, Result<(), gst::FlowError>> {
|
||||||
async move {
|
async move {
|
||||||
if self.raise_log_level {
|
let buffer = self
|
||||||
gst::log!(CAT, obj: self.element, "Awaiting timer");
|
.buffer_pool
|
||||||
} 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
|
|
||||||
.acquire_buffer(None)
|
.acquire_buffer(None)
|
||||||
.map(|mut buffer| {
|
.map(|mut buffer| {
|
||||||
{
|
{
|
||||||
let buffer = buffer.get_mut().unwrap();
|
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.set_dts(rtime);
|
||||||
}
|
}
|
||||||
buffer
|
buffer
|
||||||
})
|
})
|
||||||
.map_err(|err| {
|
.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
|
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()
|
.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 {
|
async move {
|
||||||
let res = self.push(buffer).await;
|
match err {
|
||||||
match res {
|
gst::FlowError::Eos => {
|
||||||
Ok(_) => {
|
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Pushing EOS");
|
||||||
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;
|
|
||||||
|
|
||||||
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 {
|
task::Trigger::Stop
|
||||||
gst::debug!(CAT, obj: self.element, "Flushing");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, obj: self.element, "Flushing");
|
|
||||||
}
|
}
|
||||||
|
gst::FlowError::Flushing => {
|
||||||
|
debug_or_trace!(CAT, self.is_main_elem, obj: self.elem, "Flushing");
|
||||||
|
|
||||||
|
task::Trigger::FlushStart
|
||||||
}
|
}
|
||||||
Err(err) => {
|
err => {
|
||||||
gst::error!(CAT, obj: self.element, "Got error {}", err);
|
gst::error!(CAT, obj: self.elem, "Got error {err}");
|
||||||
gst::element_error!(
|
gst::element_error!(
|
||||||
&self.element,
|
&self.elem,
|
||||||
gst::StreamError::Failed,
|
gst::StreamError::Failed,
|
||||||
("Internal data stream error"),
|
("Internal data stream error"),
|
||||||
["streaming stopped, reason {}", err]
|
["streaming stopped, reason {}", err]
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.map(drop)
|
task::Trigger::Error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.boxed()
|
.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)]
|
#[derive(Debug)]
|
||||||
pub struct TestSrc {
|
pub struct TestSrc {
|
||||||
src_pad: PadSrc,
|
src_pad: PadSrc,
|
||||||
|
@ -319,16 +253,11 @@ pub struct TestSrc {
|
||||||
|
|
||||||
impl TestSrc {
|
impl TestSrc {
|
||||||
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
fn prepare(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||||
if raise_log_level {
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Preparing");
|
||||||
gst::debug!(CAT, imp: self, "Preparing");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Preparing");
|
|
||||||
}
|
|
||||||
|
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
let context =
|
let ts_ctx = Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
|
||||||
Context::acquire(&settings.context, settings.context_wait).map_err(|err| {
|
|
||||||
gst::error_msg!(
|
gst::error_msg!(
|
||||||
gst::ResourceError::OpenRead,
|
gst::ResourceError::OpenRead,
|
||||||
["Failed to acquire Context: {}", err]
|
["Failed to acquire Context: {}", err]
|
||||||
|
@ -337,88 +266,44 @@ impl TestSrc {
|
||||||
drop(settings);
|
drop(settings);
|
||||||
|
|
||||||
self.task
|
self.task
|
||||||
.prepare(SrcTask::new(self.obj().clone()), context)
|
.prepare(SrcTask::new(self.instance().clone()), ts_ctx)
|
||||||
.block_on()?;
|
.block_on()?;
|
||||||
|
|
||||||
if raise_log_level {
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Prepared");
|
||||||
gst::debug!(CAT, imp: self, "Prepared");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Prepared");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unprepare(&self) {
|
fn unprepare(&self) {
|
||||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||||
if raise_log_level {
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Unpreparing");
|
||||||
gst::debug!(CAT, imp: self, "Unpreparing");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Unpreparing");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.task.unprepare().block_on().unwrap();
|
self.task.unprepare().block_on().unwrap();
|
||||||
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Unprepared");
|
||||||
if raise_log_level {
|
|
||||||
gst::debug!(CAT, imp: self, "Unprepared");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Unprepared");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||||
if raise_log_level {
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopping");
|
||||||
gst::debug!(CAT, imp: self, "Stopping");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Stopping");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.task.stop().block_on()?;
|
self.task.stop().block_on()?;
|
||||||
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Stopped");
|
||||||
if raise_log_level {
|
|
||||||
gst::debug!(CAT, imp: self, "Stopped");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Stopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
fn start(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||||
if raise_log_level {
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Starting");
|
||||||
gst::debug!(CAT, imp: self, "Starting");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Starting");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.task.start().block_on()?;
|
self.task.start().block_on()?;
|
||||||
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Started");
|
||||||
if raise_log_level {
|
|
||||||
gst::debug!(CAT, imp: self, "Started");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Started");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
fn pause(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
let raise_log_level = self.settings.lock().unwrap().raise_log_level;
|
let is_main_elem = self.settings.lock().unwrap().is_main_elem;
|
||||||
if raise_log_level {
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Pausing");
|
||||||
gst::debug!(CAT, imp: self, "Pausing");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Pausing");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.task.pause().block_on()?;
|
self.task.pause().block_on()?;
|
||||||
|
debug_or_trace!(CAT, is_main_elem, imp: self, "Paused");
|
||||||
if raise_log_level {
|
|
||||||
gst::debug!(CAT, imp: self, "Paused");
|
|
||||||
} else {
|
|
||||||
gst::trace!(CAT, imp: self, "Paused");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -462,9 +347,9 @@ impl ObjectImpl for TestSrc {
|
||||||
.blurb("Push a new buffer every this many ms")
|
.blurb("Push a new buffer every this many ms")
|
||||||
.default_value(DEFAULT_PUSH_PERIOD.mseconds() as u32)
|
.default_value(DEFAULT_PUSH_PERIOD.mseconds() as u32)
|
||||||
.build(),
|
.build(),
|
||||||
glib::ParamSpecBoolean::builder("raise-log-level")
|
glib::ParamSpecBoolean::builder("main-elem")
|
||||||
.nick("Raise log level")
|
.nick("Main Element")
|
||||||
.blurb("Raises the log level so that this element stands out")
|
.blurb("Declare this element as the main one")
|
||||||
.write_only()
|
.write_only()
|
||||||
.build(),
|
.build(),
|
||||||
glib::ParamSpecInt::builder("num-buffers")
|
glib::ParamSpecInt::builder("num-buffers")
|
||||||
|
@ -485,24 +370,21 @@ impl ObjectImpl for TestSrc {
|
||||||
"context" => {
|
"context" => {
|
||||||
settings.context = value
|
settings.context = value
|
||||||
.get::<Option<String>>()
|
.get::<Option<String>>()
|
||||||
.expect("type checked upstream")
|
.unwrap()
|
||||||
.unwrap_or_else(|| DEFAULT_CONTEXT.into());
|
.unwrap_or_else(|| DEFAULT_CONTEXT.into());
|
||||||
}
|
}
|
||||||
"context-wait" => {
|
"context-wait" => {
|
||||||
settings.context_wait = Duration::from_millis(
|
settings.context_wait = Duration::from_millis(value.get::<u32>().unwrap().into());
|
||||||
value.get::<u32>().expect("type checked upstream").into(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
"push-period" => {
|
"push-period" => {
|
||||||
settings.push_period = gst::ClockTime::from_mseconds(
|
let value: u64 = value.get::<u32>().unwrap().into();
|
||||||
value.get::<u32>().expect("type checked upstream").into(),
|
settings.push_period = value.mseconds();
|
||||||
);
|
|
||||||
}
|
}
|
||||||
"raise-log-level" => {
|
"main-elem" => {
|
||||||
settings.raise_log_level = value.get::<bool>().expect("type checked upstream");
|
settings.is_main_elem = value.get::<bool>().unwrap();
|
||||||
}
|
}
|
||||||
"num-buffers" => {
|
"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 };
|
settings.num_buffers = if value > 0 { Some(value as u32) } else { None };
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
|
@ -515,7 +397,7 @@ impl ObjectImpl for TestSrc {
|
||||||
"context" => settings.context.to_value(),
|
"context" => settings.context.to_value(),
|
||||||
"context-wait" => (settings.context_wait.as_millis() as u32).to_value(),
|
"context-wait" => (settings.context_wait.as_millis() as u32).to_value(),
|
||||||
"push-period" => (settings.push_period.mseconds() 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" => settings
|
||||||
.num_buffers
|
.num_buffers
|
||||||
.and_then(|val| val.try_into().ok())
|
.and_then(|val| val.try_into().ok())
|
||||||
|
@ -571,7 +453,7 @@ impl ElementImpl for TestSrc {
|
||||||
&self,
|
&self,
|
||||||
transition: gst::StateChange,
|
transition: gst::StateChange,
|
||||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||||
gst::trace!(CAT, imp: self, "Changing state {:?}", transition);
|
gst::trace!(CAT, imp: self, "Changing state {transition:?}");
|
||||||
|
|
||||||
match transition {
|
match transition {
|
||||||
gst::StateChange::NullToReady => {
|
gst::StateChange::NullToReady => {
|
||||||
|
|
|
@ -3,6 +3,8 @@ use gst::prelude::*;
|
||||||
|
|
||||||
mod imp;
|
mod imp;
|
||||||
|
|
||||||
|
pub const ELEMENT_NAME: &str = "ts-standalone-src";
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
pub struct TestSrc(ObjectSubclass<imp::TestSrc>) @extends gst::Element, gst::Object;
|
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> {
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"ts-standalone-test-src",
|
"ts-standalone-src",
|
||||||
gst::Rank::None,
|
gst::Rank::None,
|
||||||
TestSrc::static_type(),
|
TestSrc::static_type(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,19 +17,45 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
// 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;
|
||||||
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
||||||
use std::{env, thread, time};
|
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() {
|
fn main() {
|
||||||
|
gst::init().unwrap();
|
||||||
|
gstthreadshare::plugin_register_static().unwrap();
|
||||||
|
|
||||||
let args = env::args().collect::<Vec<_>>();
|
let args = env::args().collect::<Vec<_>>();
|
||||||
assert!(args.len() > 1);
|
assert!(args.len() > 1);
|
||||||
let n_streams: u16 = args[1].parse().unwrap();
|
let n_streams: u16 = args[1].parse().unwrap();
|
||||||
|
|
||||||
if args.len() > 2 && args[2] == "rtp" {
|
let num_buffers: Option<i32> = if args.len() > 3 {
|
||||||
send_rtp_buffers(n_streams);
|
args[3].parse().ok()
|
||||||
} else {
|
} 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 socket = net::UdpSocket::bind("0.0.0.0:0").unwrap();
|
||||||
|
|
||||||
let ipaddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
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))
|
.map(|port| SocketAddr::new(ipaddr, port))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -60,43 +86,60 @@ fn send_raw_buffers(n_streams: u16) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_rtp_buffers(n_streams: u16) {
|
fn send_test_buffers(n_streams: u16, num_buffers: Option<i32>) {
|
||||||
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);
|
|
||||||
let pipeline = gst::Pipeline::default();
|
let pipeline = gst::Pipeline::default();
|
||||||
for i in 0..n_streams {
|
for i in 0..n_streams {
|
||||||
let src = gst::ElementFactory::make("audiotestsrc")
|
let src = gst::ElementFactory::make("ts-audiotestsrc")
|
||||||
.name(format!("audiotestsrc-{}", i).as_str())
|
.name(format!("ts-audiotestsrc-{}", i).as_str())
|
||||||
|
.property("context-wait", 20u32)
|
||||||
|
.property("is-live", true)
|
||||||
|
.property("do-timestamp", true)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.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")
|
let enc = gst::ElementFactory::make("alawenc")
|
||||||
.name(format!("alawenc-{}", i).as_str())
|
.name(format!("alawenc-{}", i).as_str())
|
||||||
|
@ -106,11 +149,11 @@ fn send_rtp_buffers(n_streams: u16) {
|
||||||
.name(format!("rtppcmapay-{}", i).as_str())
|
.name(format!("rtppcmapay-{}", i).as_str())
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let sink = gst::ElementFactory::make("ts-udpsink")
|
let sink = gst::ElementFactory::make("ts-udpsink")
|
||||||
.name(format!("udpsink-{}", i).as_str())
|
.name(format!("udpsink-{}", i).as_str())
|
||||||
.property("clients", format!("127.0.0.1:{}", i + 40000))
|
|
||||||
.property("context", "context-udpsink")
|
|
||||||
.property("context-wait", 20u32)
|
.property("context-wait", 20u32)
|
||||||
|
.property("clients", format!("127.0.0.1:{}", i + 5004))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -119,6 +162,42 @@ fn send_rtp_buffers(n_streams: u16) {
|
||||||
gst::Element::link_many(elements).unwrap();
|
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();
|
pipeline.set_state(gst::State::Playing).unwrap();
|
||||||
l.run();
|
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 once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::cmp::{max, min, Ordering};
|
use std::cmp::Ordering;
|
||||||
use std::collections::{BTreeSet, VecDeque};
|
use std::collections::{BTreeSet, VecDeque};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -412,7 +412,7 @@ impl SinkHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(last_in_seqnum) = inner.last_in_seqnum {
|
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 {
|
if gap == 1 {
|
||||||
self.calculate_packet_spacing(inner, &mut state, rtptime, pts);
|
self.calculate_packet_spacing(inner, &mut state, rtptime, pts);
|
||||||
} else {
|
} else {
|
||||||
|
@ -463,7 +463,7 @@ impl SinkHandler {
|
||||||
state.equidistant += 1;
|
state.equidistant += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.equidistant = min(max(state.equidistant, -7), 7);
|
state.equidistant = state.equidistant.clamp(-7, 7);
|
||||||
|
|
||||||
inner.last_rtptime = Some(rtptime);
|
inner.last_rtptime = Some(rtptime);
|
||||||
|
|
||||||
|
@ -679,7 +679,7 @@ impl SrcHandler {
|
||||||
// FIXME reason why we can expect Some for the 2 lines below
|
// FIXME reason why we can expect Some for the 2 lines below
|
||||||
let mut last_popped_pts = state.last_popped_pts.unwrap();
|
let mut last_popped_pts = state.last_popped_pts.unwrap();
|
||||||
let interval = pts.into().unwrap().saturating_sub(last_popped_pts);
|
let interval = pts.into().unwrap().saturating_sub(last_popped_pts);
|
||||||
let spacing = interval / (gap as u64 + 1);
|
let spacing = interval / (gap + 1);
|
||||||
|
|
||||||
*discont = true;
|
*discont = true;
|
||||||
|
|
||||||
|
@ -1259,7 +1259,7 @@ impl JitterBuffer {
|
||||||
|
|
||||||
self.task
|
self.task
|
||||||
.prepare(
|
.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,
|
context,
|
||||||
)
|
)
|
||||||
.block_on()?;
|
.block_on()?;
|
||||||
|
|
|
@ -16,29 +16,30 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
|
|
||||||
pub mod socket;
|
|
||||||
mod tcpclientsrc;
|
|
||||||
mod udpsink;
|
|
||||||
mod udpsrc;
|
|
||||||
|
|
||||||
mod appsrc;
|
mod appsrc;
|
||||||
|
mod audiotestsrc;
|
||||||
pub mod dataqueue;
|
pub mod dataqueue;
|
||||||
mod inputselector;
|
mod inputselector;
|
||||||
mod jitterbuffer;
|
mod jitterbuffer;
|
||||||
mod proxy;
|
mod proxy;
|
||||||
mod queue;
|
mod queue;
|
||||||
|
pub mod socket;
|
||||||
|
mod tcpclientsrc;
|
||||||
|
mod udpsink;
|
||||||
|
mod udpsrc;
|
||||||
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
|
|
||||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
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)?;
|
appsrc::register(plugin)?;
|
||||||
jitterbuffer::register(plugin)?;
|
audiotestsrc::register(plugin)?;
|
||||||
inputselector::register(plugin)?;
|
inputselector::register(plugin)?;
|
||||||
|
jitterbuffer::register(plugin)?;
|
||||||
|
proxy::register(plugin)?;
|
||||||
|
queue::register(plugin)?;
|
||||||
|
tcpclientsrc::register(plugin)?;
|
||||||
|
udpsink::register(plugin)?;
|
||||||
|
udpsrc::register(plugin)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -336,13 +336,13 @@ fn eos() {
|
||||||
.name("src-eos")
|
.name("src-eos")
|
||||||
.property("caps", &caps)
|
.property("caps", &caps)
|
||||||
.property("do-timestamp", true)
|
.property("do-timestamp", true)
|
||||||
.property("context", &CONTEXT)
|
.property("context", CONTEXT)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let queue = gst::ElementFactory::make("ts-queue")
|
let queue = gst::ElementFactory::make("ts-queue")
|
||||||
.name("queue-eos")
|
.name("queue-eos")
|
||||||
.property("context", &CONTEXT)
|
.property("context", CONTEXT)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -636,7 +636,7 @@ fn socket_play_null_play() {
|
||||||
let sink = gst::ElementFactory::make("ts-udpsink")
|
let sink = gst::ElementFactory::make("ts-udpsink")
|
||||||
.name(format!("sink-{}", TEST).as_str())
|
.name(format!("sink-{}", TEST).as_str())
|
||||||
.property("socket", &socket)
|
.property("socket", &socket)
|
||||||
.property("context", &TEST)
|
.property("context", TEST)
|
||||||
.property("context-wait", 20u32)
|
.property("context-wait", 20u32)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -100,6 +100,6 @@ fn test_chain() {
|
||||||
assert!(buf == [42, 43, 44, 45, 0]);
|
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));
|
assert!(h.push(buf) == Ok(gst::FlowSuccess::Ok));
|
||||||
}
|
}
|
||||||
|
|
528
meson.build
528
meson.build
|
@ -1,11 +1,16 @@
|
||||||
project('gst-plugins-rs',
|
project('gst-plugins-rs',
|
||||||
'rust',
|
'rust',
|
||||||
'c',
|
'c',
|
||||||
version: '0.9.0-alpha.1',
|
version: '0.9.5',
|
||||||
meson_version : '>= 0.60')
|
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')
|
fs = import('fs')
|
||||||
|
host_system = host_machine.system()
|
||||||
|
|
||||||
if get_option('debug')
|
if get_option('debug')
|
||||||
target = 'debug'
|
target = 'debug'
|
||||||
|
@ -23,9 +28,9 @@ if not cargo_c.found()
|
||||||
endif
|
endif
|
||||||
|
|
||||||
system = build_machine.system()
|
system = build_machine.system()
|
||||||
ext_exe = ''
|
exe_suffix = ''
|
||||||
if system == 'windows'
|
if system == 'windows'
|
||||||
ext_exe = 'exe'
|
exe_suffix = '.exe'
|
||||||
ext_dynamic = 'dll'
|
ext_dynamic = 'dll'
|
||||||
ext_static = 'lib'
|
ext_static = 'lib'
|
||||||
elif system == 'darwin'
|
elif system == 'darwin'
|
||||||
|
@ -36,135 +41,18 @@ else
|
||||||
ext_static = 'a'
|
ext_static = 'a'
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# workspace name -> lib name
|
# Extra env to pass to cargo
|
||||||
# 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 = {}
|
extra_env = {}
|
||||||
|
|
||||||
if dependency('cairo-gobject', required : get_option('videofx')).found()
|
# Used to not lookup the same dependency multiple times which clutters logs
|
||||||
plugins += {'gst-plugin-videofx': 'libgstrsvideofx',}
|
deps_cache = {}
|
||||||
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
|
|
||||||
|
|
||||||
# Need to depends on all gstreamer-rs deps to ensure they are built
|
# Need to depends on all gstreamer-rs deps to ensure they are built
|
||||||
# before gstreamer-rs when building with gst-build.
|
# before gstreamer-rs when building with gst-build.
|
||||||
# Custom targets can't depend on dependency() objects so we have to depend
|
# Custom targets can't depend on dependency() objects so we have to depend
|
||||||
# on the library variable from the subproject instead.
|
# on the library variable from the subproject instead.
|
||||||
gst_req = '>= 1.18.0'
|
glib_req = '>=2.62'
|
||||||
|
gst_req = '>=1.20.0'
|
||||||
depends = []
|
depends = []
|
||||||
|
|
||||||
deps = [
|
deps = [
|
||||||
|
@ -174,6 +62,7 @@ deps = [
|
||||||
['gstreamer-audio-1.0', 'gst-plugins-base', 'audio_dep', 'gstaudio'],
|
['gstreamer-audio-1.0', 'gst-plugins-base', 'audio_dep', 'gstaudio'],
|
||||||
['gstreamer-base-1.0', 'gstreamer', 'gst_base_dep', 'gst_base'],
|
['gstreamer-base-1.0', 'gstreamer', 'gst_base_dep', 'gst_base'],
|
||||||
['gstreamer-check-1.0', 'gstreamer', 'gst_check_dep', 'gst_check'],
|
['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-net-1.0', 'gstreamer', 'gst_net_dep', 'gst_net'],
|
||||||
['gstreamer-rtp-1.0', 'gst-plugins-base', 'rtp_dep', 'gst_rtp'],
|
['gstreamer-rtp-1.0', 'gst-plugins-base', 'rtp_dep', 'gst_rtp'],
|
||||||
['gstreamer-video-1.0', 'gst-plugins-base', 'video_dep', 'gstvideo'],
|
['gstreamer-video-1.0', 'gst-plugins-base', 'video_dep', 'gstvideo'],
|
||||||
|
@ -181,11 +70,11 @@ deps = [
|
||||||
['gstreamer-webrtc-1.0', 'gst-plugins-bad', 'gstwebrtc_dep', 'gstwebrtc'],
|
['gstreamer-webrtc-1.0', 'gst-plugins-bad', 'gstwebrtc_dep', 'gstwebrtc'],
|
||||||
]
|
]
|
||||||
|
|
||||||
# Used to not lookup the same dependency multiple times which clutters logs
|
glib_dep = dependency('glib-2.0', version: glib_req)
|
||||||
deps_cache = {}
|
deps_cache += {'glib-2.0': glib_dep}
|
||||||
|
|
||||||
foreach d: deps
|
foreach d: deps
|
||||||
dep = dependency(d[0], version : gst_req,
|
dep = dependency(d[0], version: gst_req,
|
||||||
fallback : [d[1], d[2]])
|
fallback : [d[1], d[2]])
|
||||||
set_variable(d[2], dep)
|
set_variable(d[2], dep)
|
||||||
deps_cache += {d[0]: dep}
|
deps_cache += {d[0]: dep}
|
||||||
|
@ -195,22 +84,315 @@ foreach d: deps
|
||||||
endif
|
endif
|
||||||
endforeach
|
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
|
'file': {'library': 'libgstrsfile'},
|
||||||
extra_env_list = []
|
# sodium can have an external dependency, see below
|
||||||
foreach key, value : extra_env
|
'threadshare': {
|
||||||
extra_env_list += key + ':' + value
|
'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
|
||||||
|
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
|
endforeach
|
||||||
extra_env_str = ','.join(extra_env_list)
|
|
||||||
|
|
||||||
plugins_install_dir = get_option('libdir') / 'gstreamer-1.0'
|
plugins_install_dir = get_option('libdir') / 'gstreamer-1.0'
|
||||||
pkgconfig_install_dir = get_option('libdir') / 'pkgconfig'
|
pkgconfig_install_dir = get_option('libdir') / 'pkgconfig'
|
||||||
|
|
||||||
|
extra_args = []
|
||||||
if get_option('doc').disabled()
|
if get_option('doc').disabled()
|
||||||
disable_doc = ['--disable-doc']
|
extra_args = ['--disable-doc']
|
||||||
else
|
endif
|
||||||
disable_doc = []
|
|
||||||
|
# '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
|
endif
|
||||||
|
|
||||||
rs_plugins = custom_target('gst-plugins-rs',
|
rs_plugins = custom_target('gst-plugins-rs',
|
||||||
|
@ -221,19 +403,20 @@ rs_plugins = custom_target('gst-plugins-rs',
|
||||||
install_dir: plugins_install_dir,
|
install_dir: plugins_install_dir,
|
||||||
depends: depends,
|
depends: depends,
|
||||||
depfile: 'gst-plugins-rs.dep',
|
depfile: 'gst-plugins-rs.dep',
|
||||||
|
env: extra_env,
|
||||||
command: [cargo_wrapper,
|
command: [cargo_wrapper,
|
||||||
'build',
|
'build',
|
||||||
meson.current_build_dir(),
|
meson.current_build_dir(),
|
||||||
meson.current_source_dir(),
|
meson.current_source_dir(),
|
||||||
meson.global_build_root(),
|
meson.global_build_root(),
|
||||||
target,
|
target,
|
||||||
include,
|
|
||||||
extra_env_str,
|
|
||||||
get_option('prefix'),
|
get_option('prefix'),
|
||||||
get_option('libdir'),
|
get_option('libdir'),
|
||||||
|
'--packages', packages,
|
||||||
|
'--features', features,
|
||||||
'--depfile', '@DEPFILE@',
|
'--depfile', '@DEPFILE@',
|
||||||
'--exts', extensions,
|
'--lib-suffixes', library_suffixes,
|
||||||
] + disable_doc)
|
] + extra_args)
|
||||||
|
|
||||||
plugins = rs_plugins.to_list()
|
plugins = rs_plugins.to_list()
|
||||||
|
|
||||||
|
@ -245,6 +428,17 @@ foreach plugin : plugins
|
||||||
# skip the 'lib' prefix and extension from plugin path
|
# skip the 'lib' prefix and extension from plugin path
|
||||||
plugin_name = fs.stem(plugin.full_path()).substring(3)
|
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
|
# Extract plugin dependencies from their Cargo.toml file
|
||||||
plugin_deps = []
|
plugin_deps = []
|
||||||
p = run_command('dependencies.py', meson.current_source_dir(), plugin_name,
|
p = run_command('dependencies.py', meson.current_source_dir(), plugin_name,
|
||||||
|
@ -252,9 +446,9 @@ foreach plugin : plugins
|
||||||
check: true)
|
check: true)
|
||||||
foreach dep_name : p.stdout().strip().split(',')
|
foreach dep_name : p.stdout().strip().split(',')
|
||||||
dep_name_version = dep_name.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
|
if dep_name_version.length() > 1
|
||||||
extras = {'version': dep_name_version.get(1)}
|
extras = {'version': dep_name_version.get(1).strip()}
|
||||||
else
|
else
|
||||||
extras = {}
|
extras = {}
|
||||||
endif
|
endif
|
||||||
|
@ -274,11 +468,10 @@ foreach plugin : plugins
|
||||||
)
|
)
|
||||||
meson.override_dependency(plugin_name, dep)
|
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))
|
warning('Static plugin @0@ is known to fail. It will not be included in libgstreamer-full.'.format(plugin_name))
|
||||||
else
|
else
|
||||||
gst_plugins += dep
|
gst_plugins += dep
|
||||||
endif
|
|
||||||
|
|
||||||
pc_files += [plugin_name + '.pc']
|
pc_files += [plugin_name + '.pc']
|
||||||
if plugin_name.startswith('gst')
|
if plugin_name.startswith('gst')
|
||||||
|
@ -286,6 +479,7 @@ foreach plugin : plugins
|
||||||
else
|
else
|
||||||
plugin_names += [plugin_name]
|
plugin_names += [plugin_name]
|
||||||
endif
|
endif
|
||||||
|
endif
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
subdir('docs')
|
subdir('docs')
|
||||||
|
@ -302,40 +496,64 @@ custom_target('gst-plugins-rs-pc-files',
|
||||||
depends: rs_plugins,
|
depends: rs_plugins,
|
||||||
command: [python, '-c', '""'])
|
command: [python, '-c', '""'])
|
||||||
|
|
||||||
|
if get_option('webrtc').allowed()
|
||||||
custom_target('gst-webrtc-signalling-server',
|
custom_target('gst-webrtc-signalling-server',
|
||||||
build_by_default: true,
|
build_by_default: true,
|
||||||
output: 'gst-webrtc-signalling-server',
|
output: 'gst-webrtc-signalling-server',
|
||||||
console: true,
|
console: true,
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: get_option('bindir'),
|
install_dir: get_option('bindir'),
|
||||||
depfile: 'gst-webrtc-signalling-server.dep',
|
depfile: 'gst-webrtc-signalling-server.dep',
|
||||||
|
env: extra_env,
|
||||||
command: [cargo_wrapper,
|
command: [cargo_wrapper,
|
||||||
'build',
|
'build',
|
||||||
meson.current_build_dir(),
|
meson.current_build_dir(),
|
||||||
meson.current_source_dir(),
|
meson.current_source_dir(),
|
||||||
meson.global_build_root(),
|
meson.global_build_root(),
|
||||||
target,
|
target,
|
||||||
'',
|
|
||||||
'',
|
|
||||||
get_option('prefix'),
|
get_option('prefix'),
|
||||||
get_option('libdir'),
|
get_option('libdir'),
|
||||||
'--depfile', '@DEPFILE@',
|
'--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',
|
test('tests',
|
||||||
cargo_wrapper,
|
cargo_wrapper,
|
||||||
|
env: extra_env,
|
||||||
args: ['test',
|
args: ['test',
|
||||||
meson.current_build_dir(),
|
meson.current_build_dir(),
|
||||||
meson.current_source_dir(),
|
meson.current_source_dir(),
|
||||||
meson.global_build_root(),
|
meson.global_build_root(),
|
||||||
target,
|
target,
|
||||||
include,
|
|
||||||
extra_env_str,
|
|
||||||
get_option('prefix'),
|
get_option('prefix'),
|
||||||
get_option('libdir')],
|
get_option('libdir'),
|
||||||
|
'--packages', packages],
|
||||||
timeout: 600)
|
timeout: 600)
|
||||||
|
|
||||||
summary({
|
summary({
|
||||||
|
|
|
@ -1,12 +1,64 @@
|
||||||
option('videofx', type : 'feature', value : 'auto', description : 'Build videofx plugin')
|
# Same order as members in Cargo.toml
|
||||||
option('closedcaption', type : 'feature', value : 'auto', description : 'Build closedcaption plugin')
|
|
||||||
option('dav1d', type : 'feature', value : 'auto', description : 'Build dav1d plugin')
|
# audio
|
||||||
option('sodium', type : 'combo',
|
option('audiofx', type: 'feature', value: 'auto', description: 'Build audiofx plugin')
|
||||||
choices : ['system', 'built-in', 'disabled'], value : 'built-in',
|
option('claxon', type: 'feature', value: 'auto', description: 'Build claxon plugin')
|
||||||
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('csound', type : 'feature', value : 'auto', description : 'Build csound plugin')
|
option('lewton', type: 'feature', value: 'auto', description: 'Build lewton plugin')
|
||||||
option('gtk4', type : 'feature', value : 'auto', description : 'Build GTK4 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('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
|
# Common options
|
||||||
option('doc', type : 'feature', value : 'auto', yield: true,
|
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]
|
[package]
|
||||||
name = "gst-plugin-flavors"
|
name = "gst-plugin-flavors"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
@ -9,9 +9,9 @@ rust-version = "1.63"
|
||||||
description = "GStreamer Rust FLV Plugin"
|
description = "GStreamer Rust FLV Plugin"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
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" }
|
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 = [] }
|
num-rational = { version = "0.4", default-features = false, features = [] }
|
||||||
nom = "7"
|
nom = "7"
|
||||||
flavors = { git = "https://github.com/rust-av/flavors" }
|
flavors = { git = "https://github.com/rust-av/flavors" }
|
||||||
|
@ -26,7 +26,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -463,7 +463,7 @@ impl FlvDemux {
|
||||||
match *state {
|
match *state {
|
||||||
State::Stopped => unreachable!(),
|
State::Stopped => unreachable!(),
|
||||||
State::NeedHeader => {
|
State::NeedHeader => {
|
||||||
let header = match self.find_header(&mut *adapter) {
|
let header = match self.find_header(&mut adapter) {
|
||||||
Ok(header) => header,
|
Ok(header) => header,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
gst::trace!(CAT, imp: self, "Need more data");
|
gst::trace!(CAT, imp: self, "Need more data");
|
||||||
|
@ -503,7 +503,7 @@ impl FlvDemux {
|
||||||
*skip_left -= skip as u32;
|
*skip_left -= skip as u32;
|
||||||
}
|
}
|
||||||
State::Streaming(ref mut sstate) => {
|
State::Streaming(ref mut sstate) => {
|
||||||
let res = sstate.handle_tag(self, &mut *adapter);
|
let res = sstate.handle_tag(self, &mut adapter);
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
|
@ -533,7 +533,7 @@ impl FlvDemux {
|
||||||
while adapter.available() >= 9 {
|
while adapter.available() >= 9 {
|
||||||
let data = adapter.map(9).unwrap();
|
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);
|
gst::debug!(CAT, imp: self, "Found FLV header: {:?}", header);
|
||||||
drop(data);
|
drop(data);
|
||||||
adapter.flush(9);
|
adapter.flush(9);
|
||||||
|
@ -745,7 +745,7 @@ impl StreamingState {
|
||||||
|
|
||||||
let data = adapter.map(tag_header.data_size as usize).unwrap();
|
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" => {
|
Ok((_, ref script_data)) if script_data.name == "onMetaData" => {
|
||||||
gst::trace!(CAT, imp: imp, "Got script tag: {:?}", script_data);
|
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");
|
gst::debug!(CAT, imp: imp, "Have all expected streams now");
|
||||||
self.got_all_streams = true;
|
self.got_all_streams = true;
|
||||||
|
@ -853,7 +855,7 @@ impl StreamingState {
|
||||||
|
|
||||||
let data = adapter.map(1).unwrap();
|
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)) => {
|
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||||
gst::error!(CAT, imp: imp, "Invalid AAC audio packet header: {:?}", err);
|
gst::error!(CAT, imp: imp, "Invalid AAC audio packet header: {:?}", err);
|
||||||
drop(data);
|
drop(data);
|
||||||
|
@ -894,7 +896,7 @@ impl StreamingState {
|
||||||
assert!(adapter.available() >= tag_header.data_size as usize);
|
assert!(adapter.available() >= tag_header.data_size as usize);
|
||||||
|
|
||||||
let data = adapter.map(1).unwrap();
|
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)) => {
|
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||||
gst::error!(CAT, imp: imp, "Invalid audio data header: {:?}", err);
|
gst::error!(CAT, imp: imp, "Invalid audio data header: {:?}", err);
|
||||||
drop(data);
|
drop(data);
|
||||||
|
@ -925,7 +927,7 @@ impl StreamingState {
|
||||||
return Ok(events);
|
return Ok(events);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.audio == None {
|
if self.audio.is_none() {
|
||||||
adapter.flush((tag_header.data_size - offset) as usize);
|
adapter.flush((tag_header.data_size - offset) as usize);
|
||||||
return Ok(events);
|
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");
|
gst::debug!(CAT, imp: imp, "Have all expected streams now");
|
||||||
self.got_all_streams = true;
|
self.got_all_streams = true;
|
||||||
|
@ -1012,7 +1016,7 @@ impl StreamingState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = adapter.map(4).unwrap();
|
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)) => {
|
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||||
gst::error!(CAT, imp: imp, "Invalid AVC video packet header: {:?}", err);
|
gst::error!(CAT, imp: imp, "Invalid AVC video packet header: {:?}", err);
|
||||||
drop(data);
|
drop(data);
|
||||||
|
@ -1065,7 +1069,7 @@ impl StreamingState {
|
||||||
assert!(adapter.available() >= tag_header.data_size as usize);
|
assert!(adapter.available() >= tag_header.data_size as usize);
|
||||||
|
|
||||||
let data = adapter.map(1).unwrap();
|
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)) => {
|
Err(nom::Err::Error(err)) | Err(nom::Err::Failure(err)) => {
|
||||||
gst::error!(CAT, imp: imp, "Invalid video data header: {:?}", err);
|
gst::error!(CAT, imp: imp, "Invalid video data header: {:?}", err);
|
||||||
drop(data);
|
drop(data);
|
||||||
|
@ -1101,7 +1105,7 @@ impl StreamingState {
|
||||||
return Ok(events);
|
return Ok(events);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.video == None {
|
if self.video.is_none() {
|
||||||
adapter.flush((tag_header.data_size - offset) as usize);
|
adapter.flush((tag_header.data_size - offset) as usize);
|
||||||
return Ok(events);
|
return Ok(events);
|
||||||
}
|
}
|
||||||
|
@ -1419,7 +1423,7 @@ impl VideoFormat {
|
||||||
flavors::CodecId::H264 => self.avc_sequence_header.as_ref().map(|header| {
|
flavors::CodecId::H264 => self.avc_sequence_header.as_ref().map(|header| {
|
||||||
gst::Caps::builder("video/x-h264")
|
gst::Caps::builder("video/x-h264")
|
||||||
.field("stream-format", "avc")
|
.field("stream-format", "avc")
|
||||||
.field("codec_data", &header)
|
.field("codec_data", header)
|
||||||
.build()
|
.build()
|
||||||
}),
|
}),
|
||||||
flavors::CodecId::H263 => Some(gst::Caps::builder("video/x-h263").build()),
|
flavors::CodecId::H263 => Some(gst::Caps::builder("video/x-h263").build()),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-fmp4"
|
name = "gst-plugin-fmp4"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
authors = ["Sebastian Dröge <sebastian@centricular.com>"]
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
description = "GStreamer Fragmented MP4 Plugin"
|
description = "GStreamer Fragmented MP4 Plugin"
|
||||||
|
@ -10,12 +10,12 @@ rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
gst = { package = "gstreamer", 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" }
|
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" }
|
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" }
|
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"
|
once_cell = "1.0"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstfmp4"
|
name = "gstfmp4"
|
||||||
|
@ -23,20 +23,20 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gst-app = { package = "gstreamer-app", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
|
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", 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", features = ["v1_20"] }
|
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"
|
m3u8-rs = "5.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["v1_18"]
|
default = []
|
||||||
static = []
|
static = []
|
||||||
capi = []
|
capi = []
|
||||||
v1_18 = ["gst-video/v1_18"]
|
doc = []
|
||||||
|
|
||||||
[package.metadata.capi]
|
[package.metadata.capi]
|
||||||
min_version = "0.8.0"
|
min_version = "0.8.0"
|
||||||
|
|
|
@ -225,7 +225,7 @@ fn main() -> Result<(), Error> {
|
||||||
"###,
|
"###,
|
||||||
duration = duration, segment_timeline = segment_timeline);
|
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(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -82,10 +82,8 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
||||||
&& level <= ("3", "1")
|
&& level <= ("3", "1")
|
||||||
&& fps <= gst::Fraction::new(60, 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!(
|
if matches!(
|
||||||
colorimetry.primaries(),
|
colorimetry.primaries(),
|
||||||
|
@ -107,21 +105,13 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
||||||
// Assume it's OK
|
// Assume it's OK
|
||||||
compatible_brands.push(b"cfsd");
|
compatible_brands.push(b"cfsd");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#[cfg(not(feature = "v1_18"))]
|
|
||||||
{
|
|
||||||
// Assume it's OK
|
|
||||||
compatible_brands.push(b"cfsd");
|
|
||||||
}
|
|
||||||
} else if width <= 1920
|
} else if width <= 1920
|
||||||
&& height <= 1080
|
&& height <= 1080
|
||||||
&& level <= ("4", "0")
|
&& level <= ("4", "0")
|
||||||
&& fps <= gst::Fraction::new(60, 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!(
|
if matches!(
|
||||||
colorimetry.primaries(),
|
colorimetry.primaries(),
|
||||||
|
@ -139,12 +129,6 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
||||||
// Assume it's OK
|
// Assume it's OK
|
||||||
compatible_brands.push(b"cfhd");
|
compatible_brands.push(b"cfhd");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#[cfg(not(feature = "v1_18"))]
|
|
||||||
{
|
|
||||||
// Assume it's OK
|
|
||||||
compatible_brands.push(b"cfhd");
|
|
||||||
}
|
|
||||||
} else if width <= 1920
|
} else if width <= 1920
|
||||||
&& height <= 1080
|
&& height <= 1080
|
||||||
&& level <= ("4", "2")
|
&& level <= ("4", "2")
|
||||||
|
@ -271,7 +255,6 @@ fn cmaf_brands_from_caps(caps: &gst::CapsRef, compatible_brands: &mut Vec<&'stat
|
||||||
&& level <= ("5", "1")
|
&& level <= ("5", "1")
|
||||||
&& fps <= gst::Fraction::new(60, 1)
|
&& fps <= gst::Fraction::new(60, 1)
|
||||||
{
|
{
|
||||||
#[cfg(feature = "v1_18")]
|
|
||||||
if let Some(colorimetry) =
|
if let Some(colorimetry) =
|
||||||
colorimetry.and_then(|c| c.parse::<gst_video::VideoColorimetry>().ok())
|
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");
|
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> {
|
pub(super) fn create_fmp4_header(cfg: super::HeaderConfiguration) -> Result<gst::Buffer, Error> {
|
||||||
let mut v = vec![];
|
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| {
|
write_box(&mut v, b"ftyp", |v| {
|
||||||
// major brand
|
// 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_full_box(v, b"mvhd", FULL_BOX_VERSION_1, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
write_mvhd(v, cfg, creation_time)
|
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| {
|
write_box(v, b"trak", |v| {
|
||||||
let mut references = vec![];
|
let mut references = vec![];
|
||||||
|
|
||||||
// Reference the video track for ONVIF metadata tracks
|
// Reference the video track for ONVIF metadata tracks
|
||||||
if cfg.variant == super::Variant::ONVIF
|
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
|
// Find the first video track
|
||||||
for (idx, caps) in cfg.streams.iter().enumerate() {
|
for (idx, other_stream) in cfg.streams.iter().enumerate() {
|
||||||
let s = caps.structure(0).unwrap();
|
let s = other_stream.caps.structure(0).unwrap();
|
||||||
|
|
||||||
if matches!(s.name(), "video/x-h264" | "video/x-h265" | "image/jpeg") {
|
if matches!(s.name(), "video/x-h264" | "video/x-h265" | "image/jpeg") {
|
||||||
references.push(TrackReference {
|
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))?;
|
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)
|
(fps.numer() as u32)
|
||||||
.mul_div_round(100, fps.denom() as u32)
|
.mul_div_round(100, fps.denom() as u32)
|
||||||
.unwrap_or(10_000)
|
.unwrap_or(10_000)
|
||||||
|
}
|
||||||
} else if let Ok(rate) = s.get::<i32>("rate") {
|
} else if let Ok(rate) = s.get::<i32>("rate") {
|
||||||
rate as u32
|
rate as u32
|
||||||
} else {
|
} 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(
|
fn write_mvhd(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
cfg: &super::HeaderConfiguration,
|
cfg: &super::HeaderConfiguration,
|
||||||
|
@ -489,8 +497,8 @@ fn write_mvhd(
|
||||||
v.extend(creation_time.to_be_bytes());
|
v.extend(creation_time.to_be_bytes());
|
||||||
// Modification time
|
// Modification time
|
||||||
v.extend(creation_time.to_be_bytes());
|
v.extend(creation_time.to_be_bytes());
|
||||||
// Timescale: uses the reference track timescale
|
// Timescale
|
||||||
v.extend(caps_to_timescale(&cfg.streams[0]).to_be_bytes());
|
v.extend(header_configuration_to_timescale(cfg).to_be_bytes());
|
||||||
// Duration
|
// Duration
|
||||||
v.extend(0u64.to_be_bytes());
|
v.extend(0u64.to_be_bytes());
|
||||||
|
|
||||||
|
@ -540,7 +548,7 @@ fn write_trak(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
cfg: &super::HeaderConfiguration,
|
cfg: &super::HeaderConfiguration,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
creation_time: u64,
|
creation_time: u64,
|
||||||
references: &[TrackReference],
|
references: &[TrackReference],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
@ -549,13 +557,13 @@ fn write_trak(
|
||||||
b"tkhd",
|
b"tkhd",
|
||||||
FULL_BOX_VERSION_1,
|
FULL_BOX_VERSION_1,
|
||||||
TKHD_FLAGS_TRACK_ENABLED | TKHD_FLAGS_TRACK_IN_MOVIE | TKHD_FLAGS_TRACK_IN_PREVIEW,
|
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 if necessary: for audio tracks to remove initialization samples
|
||||||
// TODO: write edts optionally for negative DTS instead of offsetting the DTS
|
// 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() {
|
if !references.is_empty() {
|
||||||
write_box(v, b"tref", |v| write_tref(v, cfg, references))?;
|
write_box(v, b"tref", |v| write_tref(v, cfg, references))?;
|
||||||
|
@ -568,7 +576,7 @@ fn write_tkhd(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
_cfg: &super::HeaderConfiguration,
|
_cfg: &super::HeaderConfiguration,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
creation_time: u64,
|
creation_time: u64,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Creation time
|
// Creation time
|
||||||
|
@ -591,9 +599,9 @@ fn write_tkhd(
|
||||||
v.extend(0u16.to_be_bytes());
|
v.extend(0u16.to_be_bytes());
|
||||||
|
|
||||||
// Volume
|
// Volume
|
||||||
let s = caps.structure(0).unwrap();
|
let s = stream.caps.structure(0).unwrap();
|
||||||
match s.name() {
|
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((1u16 << 8).to_be_bytes())
|
||||||
}
|
}
|
||||||
_ => v.extend(0u16.to_be_bytes()),
|
_ => v.extend(0u16.to_be_bytes()),
|
||||||
|
@ -621,7 +629,7 @@ fn write_tkhd(
|
||||||
|
|
||||||
// Width/height
|
// Width/height
|
||||||
match s.name() {
|
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 width = s.get::<i32>("width").context("video caps without width")? as u32;
|
||||||
let height = s
|
let height = s
|
||||||
.get::<i32>("height")
|
.get::<i32>("height")
|
||||||
|
@ -650,19 +658,19 @@ fn write_tkhd(
|
||||||
fn write_mdia(
|
fn write_mdia(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
cfg: &super::HeaderConfiguration,
|
cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
creation_time: u64,
|
creation_time: u64,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
write_full_box(v, b"mdhd", FULL_BOX_VERSION_1, FULL_BOX_FLAGS_NONE, |v| {
|
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_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
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -673,7 +681,7 @@ fn write_tref(
|
||||||
references: &[TrackReference],
|
references: &[TrackReference],
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
for reference in references {
|
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 {
|
for track_id in &reference.track_ids {
|
||||||
v.extend(track_id.to_be_bytes());
|
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(
|
fn write_mdhd(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
_cfg: &super::HeaderConfiguration,
|
_cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
creation_time: u64,
|
creation_time: u64,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Creation time
|
// Creation time
|
||||||
|
@ -707,7 +715,7 @@ fn write_mdhd(
|
||||||
// Modification time
|
// Modification time
|
||||||
v.extend(creation_time.to_be_bytes());
|
v.extend(creation_time.to_be_bytes());
|
||||||
// Timescale
|
// Timescale
|
||||||
v.extend(caps_to_timescale(caps).to_be_bytes());
|
v.extend(header_stream_to_timescale(stream).to_be_bytes());
|
||||||
// Duration
|
// Duration
|
||||||
v.extend(0u64.to_be_bytes());
|
v.extend(0u64.to_be_bytes());
|
||||||
|
|
||||||
|
@ -724,15 +732,17 @@ fn write_mdhd(
|
||||||
fn write_hdlr(
|
fn write_hdlr(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
_cfg: &super::HeaderConfiguration,
|
_cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Pre-defined
|
// Pre-defined
|
||||||
v.extend([0u8; 4]);
|
v.extend([0u8; 4]);
|
||||||
|
|
||||||
let s = caps.structure(0).unwrap();
|
let s = stream.caps.structure(0).unwrap();
|
||||||
let (handler_type, name) = match s.name() {
|
let (handler_type, name) = match s.name() {
|
||||||
"video/x-h264" | "video/x-h265" | "image/jpeg" => (b"vide", b"VideoHandler\0".as_slice()),
|
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
||||||
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
(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())
|
(b"soun", b"SoundHandler\0".as_slice())
|
||||||
}
|
}
|
||||||
"application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()),
|
"application/x-onvif-metadata" => (b"meta", b"MetadataHandler\0".as_slice()),
|
||||||
|
@ -754,16 +764,16 @@ fn write_hdlr(
|
||||||
fn write_minf(
|
fn write_minf(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
cfg: &super::HeaderConfiguration,
|
cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let s = caps.structure(0).unwrap();
|
let s = stream.caps.structure(0).unwrap();
|
||||||
|
|
||||||
match s.name() {
|
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
|
// Flags are always 1 for unspecified reasons
|
||||||
write_full_box(v, b"vmhd", FULL_BOX_VERSION_0, 1, |v| write_vmhd(v, cfg))?
|
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_full_box(v, b"smhd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
write_smhd(v, cfg)
|
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"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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -831,10 +841,10 @@ fn write_dref(v: &mut Vec<u8>, _cfg: &super::HeaderConfiguration) -> Result<(),
|
||||||
fn write_stbl(
|
fn write_stbl(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
cfg: &super::HeaderConfiguration,
|
cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
write_full_box(v, b"stsd", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
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_full_box(v, b"stts", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
write_stts(v, cfg)
|
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
|
// For video write a sync sample box as indication that not all samples are sync samples
|
||||||
let s = caps.structure(0).unwrap();
|
if !stream.delta_frames.intra_only() {
|
||||||
match s.name() {
|
|
||||||
"video/x-h264" | "video/x-h265" => {
|
|
||||||
write_full_box(v, b"stss", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
write_full_box(v, b"stss", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
write_stss(v, cfg)
|
write_stss(v, cfg)
|
||||||
})?
|
})?
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -867,18 +873,20 @@ fn write_stbl(
|
||||||
fn write_stsd(
|
fn write_stsd(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
cfg: &super::HeaderConfiguration,
|
cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Entry count
|
// Entry count
|
||||||
v.extend(1u32.to_be_bytes());
|
v.extend(1u32.to_be_bytes());
|
||||||
|
|
||||||
let s = caps.structure(0).unwrap();
|
let s = stream.caps.structure(0).unwrap();
|
||||||
match s.name() {
|
match s.name() {
|
||||||
"video/x-h264" | "video/x-h265" | "image/jpeg" => write_visual_sample_entry(v, cfg, caps)?,
|
"video/x-h264" | "video/x-h265" | "video/x-vp9" | "image/jpeg" => {
|
||||||
"audio/mpeg" | "audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
write_visual_sample_entry(v, cfg, stream)?
|
||||||
write_audio_sample_entry(v, cfg, caps)?
|
|
||||||
}
|
}
|
||||||
"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!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -904,9 +912,9 @@ fn write_sample_entry_box<T, F: FnOnce(&mut Vec<u8>) -> Result<T, Error>>(
|
||||||
fn write_visual_sample_entry(
|
fn write_visual_sample_entry(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
_cfg: &super::HeaderConfiguration,
|
_cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let s = caps.structure(0).unwrap();
|
let s = stream.caps.structure(0).unwrap();
|
||||||
let fourcc = match s.name() {
|
let fourcc = match s.name() {
|
||||||
"video/x-h264" => {
|
"video/x-h264" => {
|
||||||
let stream_format = s.get::<&str>("stream-format").context("no stream-format")?;
|
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",
|
"image/jpeg" => b"jpeg",
|
||||||
|
"video/x-vp9" => b"vp09",
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -993,6 +1002,69 @@ fn write_visual_sample_entry(
|
||||||
Ok(())
|
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" => {
|
"image/jpeg" => {
|
||||||
// Nothing to do here
|
// Nothing to do here
|
||||||
}
|
}
|
||||||
|
@ -1015,50 +1087,11 @@ fn write_visual_sample_entry(
|
||||||
write_box(v, b"colr", move |v| {
|
write_box(v, b"colr", move |v| {
|
||||||
v.extend(b"nclx");
|
v.extend(b"nclx");
|
||||||
let (primaries, transfer, matrix) = {
|
let (primaries, transfer, matrix) = {
|
||||||
#[cfg(feature = "v1_18")]
|
|
||||||
{
|
|
||||||
(
|
(
|
||||||
(colorimetry.primaries().to_iso() as u16),
|
(colorimetry.primaries().to_iso() as u16),
|
||||||
(colorimetry.transfer().to_iso() as u16),
|
(colorimetry.transfer().to_iso() as u16),
|
||||||
(colorimetry.matrix().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() {
|
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(&stream.caps) {
|
||||||
{
|
|
||||||
if let Ok(cll) = gst_video::VideoContentLightLevel::from_caps(caps) {
|
|
||||||
write_box(v, b"clli", move |v| {
|
write_box(v, b"clli", move |v| {
|
||||||
v.extend((cll.max_content_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() as u16).to_be_bytes());
|
v.extend((cll.max_frame_average_light_level()).to_be_bytes());
|
||||||
Ok(())
|
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| {
|
write_box(v, b"mdcv", move |v| {
|
||||||
for primary in mastering.display_primaries() {
|
for primary in mastering.display_primaries() {
|
||||||
v.extend(primary.x.to_be_bytes());
|
v.extend(primary.x.to_be_bytes());
|
||||||
|
@ -1099,7 +1130,6 @@ fn write_visual_sample_entry(
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Write fiel box for codecs that require it
|
// Write fiel box for codecs that require it
|
||||||
if ["image/jpeg"].contains(&s.name()) {
|
if ["image/jpeg"].contains(&s.name()) {
|
||||||
|
@ -1143,11 +1173,12 @@ fn write_visual_sample_entry(
|
||||||
fn write_audio_sample_entry(
|
fn write_audio_sample_entry(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
_cfg: &super::HeaderConfiguration,
|
_cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let s = caps.structure(0).unwrap();
|
let s = stream.caps.structure(0).unwrap();
|
||||||
let fourcc = match s.name() {
|
let fourcc = match s.name() {
|
||||||
"audio/mpeg" => b"mp4a",
|
"audio/mpeg" => b"mp4a",
|
||||||
|
"audio/x-opus" => b"Opus",
|
||||||
"audio/x-alaw" => b"alaw",
|
"audio/x-alaw" => b"alaw",
|
||||||
"audio/x-mulaw" => b"ulaw",
|
"audio/x-mulaw" => b"ulaw",
|
||||||
"audio/x-adpcm" => {
|
"audio/x-adpcm" => {
|
||||||
|
@ -1205,6 +1236,9 @@ fn write_audio_sample_entry(
|
||||||
}
|
}
|
||||||
write_esds_aac(v, &map)?;
|
write_esds_aac(v, &map)?;
|
||||||
}
|
}
|
||||||
|
"audio/x-opus" => {
|
||||||
|
write_dops(v, &stream.caps)?;
|
||||||
|
}
|
||||||
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
"audio/x-alaw" | "audio/x-mulaw" | "audio/x-adpcm" => {
|
||||||
// Nothing to do here
|
// 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(
|
fn write_xml_meta_data_sample_entry(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
_cfg: &super::HeaderConfiguration,
|
_cfg: &super::HeaderConfiguration,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::HeaderStream,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let s = caps.structure(0).unwrap();
|
let s = stream.caps.structure(0).unwrap();
|
||||||
let namespace = match s.name() {
|
let namespace = match s.name() {
|
||||||
"application/x-onvif-metadata" => b"http://www.onvif.org/ver10/schema",
|
"application/x-onvif-metadata" => b"http://www.onvif.org/ver10/schema",
|
||||||
_ => unreachable!(),
|
_ => 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_full_box(v, b"trex", FULL_BOX_VERSION_0, FULL_BOX_FLAGS_NONE, |v| {
|
||||||
write_trex(v, cfg, idx)
|
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> {
|
fn write_mehd(v: &mut Vec<u8>, cfg: &super::HeaderConfiguration) -> Result<(), Error> {
|
||||||
// Use the reference track timescale
|
// Use the reference track timescale
|
||||||
let timescale = caps_to_timescale(&cfg.streams[0]);
|
let timescale = header_configuration_to_timescale(cfg);
|
||||||
|
|
||||||
let duration = cfg
|
let duration = cfg
|
||||||
.duration
|
.duration
|
||||||
|
@ -1478,7 +1571,7 @@ pub(super) fn create_fmp4_fragment_header(
|
||||||
let mut v = vec![];
|
let mut v = vec![];
|
||||||
|
|
||||||
let (brand, compatible_brands) =
|
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| {
|
write_box(&mut v, b"styp", |v| {
|
||||||
// major brand
|
// major brand
|
||||||
|
@ -1529,15 +1622,14 @@ fn write_moof(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let mut data_offset_offsets = vec![];
|
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.
|
// Skip tracks without any buffers for this fragment.
|
||||||
let timing_info = match timing_info {
|
if stream.start_time.is_none() {
|
||||||
None => continue,
|
continue;
|
||||||
Some(ref timing_info) => timing_info,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
write_box(v, b"traf", |v| {
|
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)]
|
#[allow(clippy::identity_op)]
|
||||||
fn sample_flags_from_buffer(
|
#[allow(clippy::bool_to_int_with_if)]
|
||||||
timing_info: &super::FragmentTimingInfo,
|
fn sample_flags_from_buffer(stream: &super::FragmentHeaderStream, buffer: &gst::BufferRef) -> u32 {
|
||||||
buffer: &gst::BufferRef,
|
if stream.delta_frames.intra_only() {
|
||||||
) -> u32 {
|
|
||||||
if timing_info.intra_only {
|
|
||||||
(0b00u32 << (16 + 10)) | // leading: unknown
|
(0b00u32 << (16 + 10)) | // leading: unknown
|
||||||
(0b10u32 << (16 + 8)) | // depends: no
|
(0b10u32 << (16 + 8)) | // depends: no
|
||||||
(0b10u32 << (16 + 6)) | // depended: no
|
(0b10u32 << (16 + 6)) | // depended: no
|
||||||
|
@ -1606,7 +1696,7 @@ const SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT: u32 = 0x8_00;
|
||||||
fn analyze_buffers(
|
fn analyze_buffers(
|
||||||
cfg: &super::FragmentHeaderConfiguration,
|
cfg: &super::FragmentHeaderConfiguration,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
timing_info: &super::FragmentTimingInfo,
|
stream: &super::FragmentHeaderStream,
|
||||||
timescale: u32,
|
timescale: u32,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
(
|
(
|
||||||
|
@ -1665,7 +1755,7 @@ fn analyze_buffers(
|
||||||
tr_flags |= SAMPLE_DURATION_PRESENT;
|
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() {
|
if first_buffer_flags.is_none() {
|
||||||
first_buffer_flags = Some(f);
|
first_buffer_flags = Some(f);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1681,7 +1771,7 @@ fn analyze_buffers(
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(composition_time_offset) = *composition_time_offset {
|
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 {
|
if composition_time_offset != 0 {
|
||||||
tr_flags |= SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT;
|
tr_flags |= SAMPLE_COMPOSITION_TIME_OFFSET_PRESENT;
|
||||||
}
|
}
|
||||||
|
@ -1732,10 +1822,9 @@ fn write_traf(
|
||||||
cfg: &super::FragmentHeaderConfiguration,
|
cfg: &super::FragmentHeaderConfiguration,
|
||||||
data_offset_offsets: &mut Vec<usize>,
|
data_offset_offsets: &mut Vec<usize>,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
caps: &gst::CapsRef,
|
stream: &super::FragmentHeaderStream,
|
||||||
timing_info: &super::FragmentTimingInfo,
|
|
||||||
) -> Result<(), Error> {
|
) -> 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
|
// 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
|
// has to be stored for every single sample
|
||||||
|
@ -1746,7 +1835,7 @@ fn write_traf(
|
||||||
default_duration,
|
default_duration,
|
||||||
default_flags,
|
default_flags,
|
||||||
negative_composition_time_offsets,
|
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_SIZE_PRESENT == 0) ^ default_size.is_some());
|
||||||
assert!((tf_flags & DEFAULT_SAMPLE_DURATION_PRESENT == 0) ^ default_duration.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_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_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;
|
let mut current_data_offset = 0;
|
||||||
|
@ -1786,7 +1875,7 @@ fn write_traf(
|
||||||
current_data_offset,
|
current_data_offset,
|
||||||
tr_flags,
|
tr_flags,
|
||||||
timescale,
|
timescale,
|
||||||
timing_info,
|
stream,
|
||||||
run,
|
run,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
@ -1836,11 +1925,12 @@ fn write_tfdt(
|
||||||
v: &mut Vec<u8>,
|
v: &mut Vec<u8>,
|
||||||
_cfg: &super::FragmentHeaderConfiguration,
|
_cfg: &super::FragmentHeaderConfiguration,
|
||||||
_idx: usize,
|
_idx: usize,
|
||||||
timing_info: &super::FragmentTimingInfo,
|
stream: &super::FragmentHeaderStream,
|
||||||
timescale: u32,
|
timescale: u32,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let base_time = timing_info
|
let base_time = stream
|
||||||
.start_time
|
.start_time
|
||||||
|
.unwrap()
|
||||||
.mul_div_floor(timescale as u64, gst::ClockTime::SECOND.nseconds())
|
.mul_div_floor(timescale as u64, gst::ClockTime::SECOND.nseconds())
|
||||||
.context("base time overflow")?;
|
.context("base time overflow")?;
|
||||||
|
|
||||||
|
@ -1856,7 +1946,7 @@ fn write_trun(
|
||||||
current_data_offset: u32,
|
current_data_offset: u32,
|
||||||
tr_flags: u32,
|
tr_flags: u32,
|
||||||
timescale: u32,
|
timescale: u32,
|
||||||
timing_info: &super::FragmentTimingInfo,
|
stream: &super::FragmentHeaderStream,
|
||||||
buffers: &[Buffer],
|
buffers: &[Buffer],
|
||||||
) -> Result<usize, Error> {
|
) -> Result<usize, Error> {
|
||||||
// Sample count
|
// Sample count
|
||||||
|
@ -1867,7 +1957,7 @@ fn write_trun(
|
||||||
v.extend(current_data_offset.to_be_bytes());
|
v.extend(current_data_offset.to_be_bytes());
|
||||||
|
|
||||||
if (tr_flags & FIRST_SAMPLE_FLAGS_PRESENT) != 0 {
|
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 {
|
for Buffer {
|
||||||
|
@ -1899,7 +1989,7 @@ fn write_trun(
|
||||||
assert!((tr_flags & FIRST_SAMPLE_FLAGS_PRESENT) == 0);
|
assert!((tr_flags & FIRST_SAMPLE_FLAGS_PRESENT) == 0);
|
||||||
|
|
||||||
// Sample flags
|
// 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 {
|
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
|
// 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.
|
/// 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 boxes;
|
||||||
mod imp;
|
mod imp;
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub(crate) struct FMP4MuxPad(ObjectSubclass<imp::FMP4MuxPad>) @extends gst_base::AggregatorPad, gst::Pad, gst::Object;
|
||||||
|
}
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
pub(crate) struct FMP4Mux(ObjectSubclass<imp::FMP4Mux>) @extends gst_base::Aggregator, gst::Element, gst::Object;
|
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> {
|
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
|
#[cfg(feature = "doc")]
|
||||||
|
{
|
||||||
FMP4Mux::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
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());
|
HeaderUpdateMode::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||||
|
}
|
||||||
gst::Element::register(
|
gst::Element::register(
|
||||||
Some(plugin),
|
Some(plugin),
|
||||||
"isofmp4mux",
|
"isofmp4mux",
|
||||||
|
@ -64,33 +72,84 @@ pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct HeaderConfiguration<'a> {
|
pub(crate) struct HeaderConfiguration {
|
||||||
variant: Variant,
|
variant: Variant,
|
||||||
update: bool,
|
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
|
/// 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.
|
/// be used later for the fragments too.
|
||||||
streams: &'a [gst::Caps],
|
streams: Vec<HeaderStream>,
|
||||||
|
|
||||||
write_mehd: bool,
|
write_mehd: bool,
|
||||||
duration: Option<gst::ClockTime>,
|
duration: Option<gst::ClockTime>,
|
||||||
|
|
||||||
/// Start UTC time in ONVIF mode.
|
/// Start UTC time in ONVIF mode.
|
||||||
/// Since Jan 1 1601 in 100ns units.
|
/// Since Jan 1 1601 in 100ns units.
|
||||||
start_utc_time: Option<u64>,
|
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)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct FragmentHeaderConfiguration<'a> {
|
pub(crate) struct FragmentHeaderConfiguration<'a> {
|
||||||
variant: Variant,
|
variant: Variant,
|
||||||
|
|
||||||
|
/// Sequence number for this fragment.
|
||||||
sequence_number: u32,
|
sequence_number: u32,
|
||||||
streams: &'a [(gst::Caps, Option<FragmentTimingInfo>)],
|
|
||||||
|
streams: &'a [FragmentHeaderStream],
|
||||||
buffers: &'a [Buffer],
|
buffers: &'a [Buffer],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct FragmentTimingInfo {
|
pub(crate) struct FragmentHeaderStream {
|
||||||
/// Start time of this fragment
|
/// Caps of this stream
|
||||||
start_time: gst::ClockTime,
|
caps: gst::Caps,
|
||||||
|
|
||||||
/// Set if this is an intra-only stream
|
/// 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)]
|
#[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 {
|
let mut h = if cmaf {
|
||||||
gst_check::Harness::new("cmafmux")
|
gst_check::Harness::new("cmafmux")
|
||||||
} else {
|
} else {
|
||||||
|
@ -30,16 +30,7 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_property("fragment-duration", 5.seconds());
|
.set_property("fragment-duration", 5.seconds());
|
||||||
|
|
||||||
h.set_src_caps(
|
h.set_src_caps(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.play();
|
h.play();
|
||||||
|
|
||||||
let output_offset = if cmaf {
|
let output_offset = if cmaf {
|
||||||
|
@ -54,7 +45,9 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
||||||
{
|
{
|
||||||
let buffer = buffer.get_mut().unwrap();
|
let buffer = buffer.get_mut().unwrap();
|
||||||
buffer.set_pts(i.seconds());
|
buffer.set_pts(i.seconds());
|
||||||
|
if set_dts {
|
||||||
buffer.set_dts(i.seconds());
|
buffer.set_dts(i.seconds());
|
||||||
|
}
|
||||||
buffer.set_duration(gst::ClockTime::SECOND);
|
buffer.set_duration(gst::ClockTime::SECOND);
|
||||||
if i != 0 && i != 5 {
|
if i != 0 && i != 5 {
|
||||||
buffer.set_flags(gst::BufferFlags::DELTA_UNIT);
|
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();
|
let header = h.pull().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
header.flags(),
|
header.flags(),
|
||||||
gst::BufferFlags::HEADER | gst::BufferFlags::DISCONT
|
gst::BufferFlags::HEADER | gst::BufferFlags::DISCONT
|
||||||
);
|
);
|
||||||
assert_eq!(header.pts(), Some(gst::ClockTime::ZERO + output_offset));
|
assert_eq!(header.pts(), Some(gst::ClockTime::ZERO + output_offset));
|
||||||
|
if set_dts {
|
||||||
assert_eq!(header.dts(), Some(gst::ClockTime::ZERO + output_offset));
|
assert_eq!(header.dts(), Some(gst::ClockTime::ZERO + output_offset));
|
||||||
|
}
|
||||||
|
|
||||||
let fragment_header = h.pull().unwrap();
|
let fragment_header = h.pull().unwrap();
|
||||||
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
||||||
|
@ -98,10 +96,12 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
||||||
fragment_header.pts(),
|
fragment_header.pts(),
|
||||||
Some(gst::ClockTime::ZERO + output_offset)
|
Some(gst::ClockTime::ZERO + output_offset)
|
||||||
);
|
);
|
||||||
|
if set_dts {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fragment_header.dts(),
|
fragment_header.dts(),
|
||||||
Some(gst::ClockTime::ZERO + output_offset)
|
Some(gst::ClockTime::ZERO + output_offset)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
assert_eq!(fragment_header.duration(), Some(5.seconds()));
|
assert_eq!(fragment_header.duration(), Some(5.seconds()));
|
||||||
|
|
||||||
for i in 0..5 {
|
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.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||||
}
|
}
|
||||||
assert_eq!(buffer.pts(), Some(i.seconds() + output_offset));
|
assert_eq!(buffer.pts(), Some(i.seconds() + output_offset));
|
||||||
|
if set_dts {
|
||||||
assert_eq!(buffer.dts(), Some(i.seconds() + output_offset));
|
assert_eq!(buffer.dts(), Some(i.seconds() + output_offset));
|
||||||
|
}
|
||||||
assert_eq!(buffer.duration(), Some(gst::ClockTime::SECOND));
|
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();
|
let fragment_header = h.pull().unwrap();
|
||||||
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
assert_eq!(fragment_header.flags(), gst::BufferFlags::HEADER);
|
||||||
assert_eq!(fragment_header.pts(), Some(5.seconds() + output_offset));
|
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.dts(), Some(5.seconds() + output_offset));
|
||||||
|
}
|
||||||
assert_eq!(fragment_header.duration(), Some(2.seconds()));
|
assert_eq!(fragment_header.duration(), Some(2.seconds()));
|
||||||
|
|
||||||
for i in 5..7 {
|
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.flags(), gst::BufferFlags::DELTA_UNIT);
|
||||||
}
|
}
|
||||||
assert_eq!(buffer.pts(), Some(i.seconds() + output_offset));
|
assert_eq!(buffer.pts(), Some(i.seconds() + output_offset));
|
||||||
|
if set_dts {
|
||||||
assert_eq!(buffer.dts(), Some(i.seconds() + output_offset));
|
assert_eq!(buffer.dts(), Some(i.seconds() + output_offset));
|
||||||
|
}
|
||||||
assert_eq!(buffer.duration(), Some(gst::ClockTime::SECOND));
|
assert_eq!(buffer.duration(), Some(gst::ClockTime::SECOND));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,17 +159,53 @@ fn test_buffer_flags_single_stream(cmaf: bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_buffer_flags_single_stream_cmaf() {
|
fn test_buffer_flags_single_h264_stream_cmaf() {
|
||||||
init();
|
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]
|
#[test]
|
||||||
fn test_buffer_flags_single_stream_iso() {
|
fn test_buffer_flags_single_h264_stream_iso() {
|
||||||
init();
|
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]
|
#[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();
|
let header = h1.pull().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
header.flags(),
|
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
|
// Crank the clock: this should bring us to the end of the first fragment
|
||||||
h1.set_time(5.seconds()).unwrap();
|
|
||||||
h1.crank_single_clock_wait().unwrap();
|
h1.crank_single_clock_wait().unwrap();
|
||||||
|
|
||||||
let header = h1.pull().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
|
// Crank the clock: this should bring us to the end of the first fragment
|
||||||
h1.set_time(5.seconds()).unwrap();
|
|
||||||
h1.crank_single_clock_wait().unwrap();
|
h1.crank_single_clock_wait().unwrap();
|
||||||
|
|
||||||
let header = h1.pull().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();
|
let header = h.pull().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
header.flags(),
|
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]
|
[package]
|
||||||
name = "gst-plugin-aws"
|
name = "gst-plugin-aws"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Arun Raghavan <arun@arunraghavan.net>",
|
authors = ["Arun Raghavan <arun@arunraghavan.net>",
|
||||||
"Jordan Petridis <jordan@centricular.com>",
|
"Jordan Petridis <jordan@centricular.com>",
|
||||||
"Mathieu Duponchelle <mathieu@centricular.com>"]
|
"Mathieu Duponchelle <mathieu@centricular.com>"]
|
||||||
|
@ -13,22 +13,23 @@ rust-version = "1.63"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
gst = { package = "gstreamer", 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" }
|
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", 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"] }
|
||||||
aws-config = "0.49.0"
|
aws-config = "0.53.0"
|
||||||
aws-sdk-s3 = "0.19.0"
|
aws-sdk-s3 = "0.23.0"
|
||||||
aws-sdk-transcribe = "0.19.0"
|
aws-sdk-transcribe = "0.23.0"
|
||||||
aws-types = "0.49.0"
|
aws-types = "0.53.0"
|
||||||
aws-sig-auth = "0.49.0"
|
aws-credential-types = "0.53.0"
|
||||||
aws-smithy-http = { version = "0.49.0", features = [ "rt-tokio" ] }
|
aws-sig-auth = "0.53.0"
|
||||||
aws-smithy-types = "0.49.0"
|
aws-smithy-http = { version = "0.53.0", features = [ "rt-tokio" ] }
|
||||||
|
aws-smithy-types = "0.53.0"
|
||||||
http = "0.2.7"
|
http = "0.2.7"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
url = "2"
|
url = "2"
|
||||||
percent-encoding = "2"
|
percent-encoding = "2"
|
||||||
tokio = { version = "1.0", features = [ "full" ] }
|
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"
|
nom = "7"
|
||||||
crc = "3"
|
crc = "3"
|
||||||
byteorder = "1.3.4"
|
byteorder = "1.3.4"
|
||||||
|
@ -39,14 +40,14 @@ serde_json = "1"
|
||||||
atomic_refcell = "0.1"
|
atomic_refcell = "0.1"
|
||||||
base32 = "0.4"
|
base32 = "0.4"
|
||||||
backoff = { version = "0.4", features = [ "futures", "tokio" ] }
|
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]
|
[dev-dependencies]
|
||||||
chrono = { version = "0.4", features = [ "alloc" ] }
|
chrono = { version = "0.4", features = [ "alloc" ] }
|
||||||
env_logger = "0.9"
|
env_logger = "0.10"
|
||||||
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_18"] }
|
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19", features = ["v1_18"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
test-with = { version = "0.8", default-features = false }
|
test-with = { version = "0.9", default-features = false }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstaws"
|
name = "gstaws"
|
||||||
|
@ -54,7 +55,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# gst-plugin-s3
|
# gst-plugin-aws
|
||||||
|
|
||||||
This is a [GStreamer](https://gstreamer.freedesktop.org/) plugin to interact
|
This is a [GStreamer](https://gstreamer.freedesktop.org/) plugin to interact
|
||||||
with [Amazon Web Services](https://aws.amazon.com/). We currently have elements
|
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 start_pts = ((start_time * 1_000_000_000.0) as u64).nseconds();
|
||||||
let end_pts = ((end_time as f64 * 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);
|
let duration = end_pts.saturating_sub(start_pts);
|
||||||
|
|
||||||
if start_pts > last_pts {
|
if start_pts > last_pts {
|
||||||
|
|
|
@ -14,11 +14,11 @@ use gst::{element_imp_error, error_msg, loggable_error};
|
||||||
use std::default::Default;
|
use std::default::Default;
|
||||||
|
|
||||||
use aws_config::default_provider::credentials::DefaultCredentialsChain;
|
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_sig_auth::signer::{self, HttpSignatureType, OperationSigningConfig, RequestConfig};
|
||||||
use aws_smithy_http::body::SdkBody;
|
use aws_smithy_http::body::SdkBody;
|
||||||
use aws_types::credentials::ProvideCredentials;
|
|
||||||
use aws_types::region::{Region, SigningRegion};
|
use aws_types::region::{Region, SigningRegion};
|
||||||
use aws_types::{Credentials, SigningService};
|
use aws_types::SigningService;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use chrono::prelude::*;
|
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::config;
|
||||||
use aws_sdk_s3::model::ObjectCannedAcl;
|
use aws_sdk_s3::model::ObjectCannedAcl;
|
||||||
use aws_sdk_s3::types::ByteStream;
|
use aws_sdk_s3::types::ByteStream;
|
||||||
use aws_sdk_s3::Endpoint;
|
use aws_sdk_s3::{config::retry::RetryConfig, Client, Credentials, Region};
|
||||||
use aws_sdk_s3::{Client, Credentials, Region, RetryConfig};
|
|
||||||
use aws_types::sdk_config::SdkConfig;
|
use aws_types::sdk_config::SdkConfig;
|
||||||
use http::Uri;
|
|
||||||
|
|
||||||
use crate::s3utils;
|
use crate::s3utils;
|
||||||
|
|
||||||
|
@ -85,6 +83,7 @@ impl Default for Settings {
|
||||||
|
|
||||||
pub struct S3HlsSink {
|
pub struct S3HlsSink {
|
||||||
settings: Mutex<Settings>,
|
settings: Mutex<Settings>,
|
||||||
|
state: Mutex<State>,
|
||||||
hlssink: gst::Element,
|
hlssink: gst::Element,
|
||||||
canceller: Mutex<Option<future::AbortHandle>>,
|
canceller: Mutex<Option<future::AbortHandle>>,
|
||||||
}
|
}
|
||||||
|
@ -132,6 +131,18 @@ enum S3RequestControl {
|
||||||
Pause,
|
Pause,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum State {
|
||||||
|
Stopped,
|
||||||
|
Started(Started),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Started {
|
||||||
|
num_uploads_started: usize,
|
||||||
|
num_uploads_completed: usize,
|
||||||
|
num_bytes_uploaded: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl S3Upload {
|
impl S3Upload {
|
||||||
fn new(
|
fn new(
|
||||||
s3_client: Client,
|
s3_client: Client,
|
||||||
|
@ -258,7 +269,8 @@ impl S3HlsSink {
|
||||||
let put_object_req_future = put_object_req.send();
|
let put_object_req_future = put_object_req.send();
|
||||||
let result = s3utils::wait(&self.canceller, put_object_req_future);
|
let result = s3utils::wait(&self.canceller, put_object_req_future);
|
||||||
|
|
||||||
if let Err(err) = result {
|
match result {
|
||||||
|
Err(err) => {
|
||||||
gst::error!(
|
gst::error!(
|
||||||
CAT,
|
CAT,
|
||||||
imp: self,
|
imp: self,
|
||||||
|
@ -273,6 +285,19 @@ impl S3HlsSink {
|
||||||
["Put object request failed"]
|
["Put object request failed"]
|
||||||
);
|
);
|
||||||
break;
|
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)) => {
|
Ok(S3Request::Delete(data)) => {
|
||||||
|
@ -349,29 +374,13 @@ impl S3HlsSink {
|
||||||
}
|
}
|
||||||
|
|
||||||
let sdk_config = settings.config.as_ref().expect("SDK config must be set");
|
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)
|
let config_builder = config::Builder::from(sdk_config)
|
||||||
.region(settings.s3_region.clone())
|
.region(settings.s3_region.clone())
|
||||||
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
||||||
|
|
||||||
let config = if let Some(uri) = endpoint_uri {
|
let config = if let Some(ref uri) = settings.endpoint_uri {
|
||||||
config_builder
|
config_builder.endpoint_url(uri).build()
|
||||||
.endpoint_resolver(Endpoint::mutable(uri))
|
|
||||||
.build()
|
|
||||||
} else {
|
} else {
|
||||||
config_builder.build()
|
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]
|
#[glib::object_subclass]
|
||||||
|
@ -423,6 +450,7 @@ impl ObjectSubclass for S3HlsSink {
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
settings: Mutex::new(Settings::default()),
|
settings: Mutex::new(Settings::default()),
|
||||||
|
state: Mutex::new(State::Stopped),
|
||||||
hlssink,
|
hlssink,
|
||||||
canceller: Mutex::new(None),
|
canceller: Mutex::new(None),
|
||||||
}
|
}
|
||||||
|
@ -488,6 +516,13 @@ impl ObjectImpl for S3HlsSink {
|
||||||
.minimum(1)
|
.minimum(1)
|
||||||
.default_value(DEFAULT_TIMEOUT_IN_MSECS)
|
.default_value(DEFAULT_TIMEOUT_IN_MSECS)
|
||||||
.build(),
|
.build(),
|
||||||
|
glib::ParamSpecBoxed::new(
|
||||||
|
"stats",
|
||||||
|
"Various statistics",
|
||||||
|
"Various statistics",
|
||||||
|
gst::Structure::static_type(),
|
||||||
|
glib::ParamFlags::READABLE,
|
||||||
|
),
|
||||||
glib::ParamSpecString::builder("endpoint-uri")
|
glib::ParamSpecString::builder("endpoint-uri")
|
||||||
.nick("S3 endpoint URI")
|
.nick("S3 endpoint URI")
|
||||||
.blurb("The S3 endpoint URI to use")
|
.blurb("The S3 endpoint URI to use")
|
||||||
|
@ -568,6 +603,7 @@ impl ObjectImpl for S3HlsSink {
|
||||||
"acl" => settings.s3_acl.as_str().to_value(),
|
"acl" => settings.s3_acl.as_str().to_value(),
|
||||||
"retry-attempts" => settings.retry_attempts.to_value(),
|
"retry-attempts" => settings.retry_attempts.to_value(),
|
||||||
"request-timeout" => (settings.request_timeout.as_millis() as u64).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(),
|
"endpoint-uri" => settings.endpoint_uri.to_value(),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
@ -610,6 +646,11 @@ impl ObjectImpl for S3HlsSink {
|
||||||
|
|
||||||
let s3client = self_.s3client_from_settings();
|
let s3client = self_.s3client_from_settings();
|
||||||
let settings = self_.settings.lock().unwrap();
|
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 s3_location = args[1].get::<&str>().unwrap();
|
||||||
let upload = S3Upload::new(
|
let upload = S3Upload::new(
|
||||||
|
@ -639,6 +680,11 @@ impl ObjectImpl for S3HlsSink {
|
||||||
|
|
||||||
let s3client = self_.s3client_from_settings();
|
let s3client = self_.s3client_from_settings();
|
||||||
let settings = self_.settings.lock().unwrap();
|
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 s3_location = args[1].get::<&str>().unwrap();
|
||||||
let upload = S3Upload::new(
|
let upload = S3Upload::new(
|
||||||
|
@ -764,8 +810,24 @@ impl ElementImpl for S3HlsSink {
|
||||||
* in turn will require the settings lock.
|
* in turn will require the settings lock.
|
||||||
*/
|
*/
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
|
let mut state = self.state.lock().unwrap();
|
||||||
|
|
||||||
match transition {
|
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 => {
|
gst::StateChange::PlayingToPaused => {
|
||||||
let s3_txc = settings.s3_txc.clone();
|
let s3_txc = settings.s3_txc.clone();
|
||||||
if let Some(tx) = s3_txc {
|
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 => {
|
gst::StateChange::ReadyToNull => {
|
||||||
drop(settings);
|
drop(settings);
|
||||||
/*
|
/*
|
||||||
|
@ -801,6 +851,8 @@ impl ElementImpl for S3HlsSink {
|
||||||
* pending requests.
|
* pending requests.
|
||||||
*/
|
*/
|
||||||
self.stop();
|
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::config;
|
||||||
use aws_sdk_s3::model::{CompletedMultipartUpload, CompletedPart};
|
use aws_sdk_s3::model::{CompletedMultipartUpload, CompletedPart};
|
||||||
use aws_sdk_s3::types::ByteStream;
|
use aws_sdk_s3::types::ByteStream;
|
||||||
use aws_sdk_s3::Endpoint;
|
use aws_sdk_s3::{config::retry::RetryConfig, Client, Credentials, Region};
|
||||||
use aws_sdk_s3::{Client, Credentials, Region, RetryConfig};
|
|
||||||
use http::Uri;
|
|
||||||
|
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
@ -88,6 +86,7 @@ impl Started {
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
Stopped,
|
Stopped,
|
||||||
|
Completed,
|
||||||
Started(Started),
|
Started(Started),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,23 +184,7 @@ static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
});
|
});
|
||||||
|
|
||||||
impl S3Sink {
|
impl S3Sink {
|
||||||
fn flush_current_buffer(&self) -> Result<(), Option<gst::ErrorMessage>> {
|
fn flush_multipart_upload(&self, state: &mut Started) {
|
||||||
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) => {
|
|
||||||
let settings = self.settings.lock().unwrap();
|
let settings = self.settings.lock().unwrap();
|
||||||
match settings.multipart_upload_on_error {
|
match settings.multipart_upload_on_error {
|
||||||
OnError::Abort => {
|
OnError::Abort => {
|
||||||
|
@ -252,6 +235,29 @@ impl S3Sink {
|
||||||
}
|
}
|
||||||
OnError::DoNothing => (),
|
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!(
|
Some(gst::error_msg!(
|
||||||
gst::ResourceError::OpenWrite,
|
gst::ResourceError::OpenWrite,
|
||||||
["Failed to upload part: {}", err]
|
["Failed to upload part: {}", err]
|
||||||
|
@ -277,6 +283,9 @@ impl S3Sink {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
let state = match *state {
|
let state = match *state {
|
||||||
State::Started(ref mut started_state) => started_state,
|
State::Started(ref mut started_state) => started_state,
|
||||||
|
State::Completed => {
|
||||||
|
unreachable!("Upload should not be completed yet");
|
||||||
|
}
|
||||||
State::Stopped => {
|
State::Stopped => {
|
||||||
unreachable!("Element should be started");
|
unreachable!("Element should be started");
|
||||||
}
|
}
|
||||||
|
@ -437,12 +446,21 @@ impl S3Sink {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
let started_state = match *state {
|
let started_state = match *state {
|
||||||
State::Started(ref mut started_state) => started_state,
|
State::Started(ref mut started_state) => started_state,
|
||||||
|
State::Completed => {
|
||||||
|
unreachable!("Upload should not be completed yet");
|
||||||
|
}
|
||||||
State::Stopped => {
|
State::Stopped => {
|
||||||
unreachable!("Element should be started");
|
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> {
|
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)
|
let config_builder = config::Builder::from(&sdk_config)
|
||||||
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
||||||
|
|
||||||
let config = if let Some(uri) = endpoint_uri {
|
let config = if let Some(ref uri) = settings.endpoint_uri {
|
||||||
config_builder
|
config_builder.endpoint_url(uri).build()
|
||||||
.endpoint_resolver(Endpoint::mutable(uri))
|
|
||||||
.build()
|
|
||||||
} else {
|
} else {
|
||||||
config_builder.build()
|
config_builder.build()
|
||||||
};
|
};
|
||||||
|
@ -562,6 +565,9 @@ impl S3Sink {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
let started_state = match *state {
|
let started_state = match *state {
|
||||||
State::Started(ref mut started_state) => started_state,
|
State::Started(ref mut started_state) => started_state,
|
||||||
|
State::Completed => {
|
||||||
|
unreachable!("Upload should not be completed yet");
|
||||||
|
}
|
||||||
State::Stopped => {
|
State::Stopped => {
|
||||||
unreachable!("Element should be started already");
|
unreachable!("Element should be started already");
|
||||||
}
|
}
|
||||||
|
@ -953,6 +959,17 @@ impl BaseSinkImpl for S3Sink {
|
||||||
|
|
||||||
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
fn stop(&self) -> Result<(), gst::ErrorMessage> {
|
||||||
let mut state = self.state.lock().unwrap();
|
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;
|
*state = State::Stopped;
|
||||||
gst::info!(CAT, imp: self, "Stopped");
|
gst::info!(CAT, imp: self, "Stopped");
|
||||||
|
|
||||||
|
@ -965,6 +982,15 @@ impl BaseSinkImpl for S3Sink {
|
||||||
return Err(gst::FlowError::Error);
|
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);
|
gst::trace!(CAT, imp: self, "Rendering {:?}", buffer);
|
||||||
let map = buffer.map_readable().map_err(|_| {
|
let map = buffer.map_readable().map_err(|_| {
|
||||||
gst::element_imp_error!(self, gst::CoreError::Failed, ["Failed to map buffer"]);
|
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 std::time::Duration;
|
||||||
|
|
||||||
use aws_sdk_s3::config;
|
use aws_sdk_s3::config;
|
||||||
use aws_sdk_s3::Endpoint;
|
use aws_sdk_s3::{config::retry::RetryConfig, Client, Credentials};
|
||||||
use aws_sdk_s3::{Client, Credentials, RetryConfig};
|
|
||||||
use http::Uri;
|
|
||||||
|
|
||||||
use gst::glib;
|
use gst::glib;
|
||||||
use gst::prelude::*;
|
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)
|
let config_builder = config::Builder::from(&sdk_config)
|
||||||
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
.retry_config(RetryConfig::standard().with_max_attempts(settings.retry_attempts));
|
||||||
|
|
||||||
let config = if let Some(uri) = endpoint_uri {
|
let config = if let Some(ref uri) = settings.endpoint_uri {
|
||||||
config_builder
|
config_builder.endpoint_url(uri).build()
|
||||||
.endpoint_resolver(Endpoint::mutable(uri))
|
|
||||||
.build()
|
|
||||||
} else {
|
} else {
|
||||||
config_builder.build()
|
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()),
|
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());
|
return Err("Extra query terms, only 'version' is supported".to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,10 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use aws_config::meta::region::RegionProviderChain;
|
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_types::sdk_config::SdkConfig;
|
||||||
|
|
||||||
use aws_smithy_http::byte_stream::{ByteStream, Error};
|
use aws_smithy_http::byte_stream::{error::Error, ByteStream};
|
||||||
use aws_smithy_types::{timeout, tristate::TriState};
|
|
||||||
|
|
||||||
use bytes::{buf::BufMut, Bytes, BytesMut};
|
use bytes::{buf::BufMut, Bytes, BytesMut};
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
|
@ -96,19 +95,16 @@ pub fn wait_stream(
|
||||||
}
|
}
|
||||||
|
|
||||||
// See setting-timeouts example in aws-sdk-rust.
|
// See setting-timeouts example in aws-sdk-rust.
|
||||||
pub fn timeout_config(request_timeout: Duration) -> timeout::Config {
|
pub fn timeout_config(request_timeout: Duration) -> TimeoutConfig {
|
||||||
timeout::Config::new().with_api_timeouts(
|
TimeoutConfig::builder()
|
||||||
timeout::Api::new()
|
.operation_attempt_timeout(request_timeout)
|
||||||
// This timeout acts at the "HTTP request" level and sets a separate timeout for each
|
.build()
|
||||||
// HTTP request made as part of a "service request."
|
|
||||||
.with_call_attempt_timeout(TriState::Set(request_timeout)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn wait_config(
|
pub fn wait_config(
|
||||||
canceller: &Mutex<Option<future::AbortHandle>>,
|
canceller: &Mutex<Option<future::AbortHandle>>,
|
||||||
region: Region,
|
region: Region,
|
||||||
timeout_config: timeout::Config,
|
timeout_config: TimeoutConfig,
|
||||||
credentials: Option<Credentials>,
|
credentials: Option<Credentials>,
|
||||||
) -> Result<SdkConfig, WaitError<Error>> {
|
) -> Result<SdkConfig, WaitError<Error>> {
|
||||||
let region_provider = RegionProviderChain::first_try(region)
|
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_ACCESS_KEY_ID)]
|
||||||
#[test_with::env(AWS_SECRET_ACCESS_KEY)]
|
#[test_with::env(AWS_SECRET_ACCESS_KEY)]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -2,27 +2,27 @@
|
||||||
name = "gst-plugin-hlssink3"
|
name = "gst-plugin-hlssink3"
|
||||||
description = "GStreamer HLS (HTTP Live Streaming) Plugin"
|
description = "GStreamer HLS (HTTP Live Streaming) Plugin"
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
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>"]
|
authors = ["Rafael Caricio <rafael@caricio.com>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
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" }
|
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" }
|
gio = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16" }
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.7.2"
|
||||||
m3u8-rs = "5.0"
|
m3u8-rs = "5.0"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
gst-audio = { package = "gstreamer-audio", 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" }
|
gst-check = { package = "gstreamer-check", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path = "../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path = "../../version-helper" }
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gsthlssink3"
|
name = "gsthlssink3"
|
||||||
|
|
|
@ -93,7 +93,7 @@ impl Playlist {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.playlist_index += 1;
|
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.
|
/// Sets the playlist to started state.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-ndi"
|
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>"]
|
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"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
@ -9,11 +9,11 @@ edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glib = { git = "https://github.com/gtk-rs/gtk-rs-core"}
|
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" }
|
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" }
|
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" }
|
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" }
|
gst-video = { package = "gstreamer-video", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
byte-slice-cast = "1"
|
byte-slice-cast = "1"
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
|
@ -21,7 +21,7 @@ atomic_refcell = "0.1"
|
||||||
libloading = "0.7"
|
libloading = "0.7"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path = "../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path = "../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["interlaced-fields", "sink"]
|
default = ["interlaced-fields", "sink"]
|
||||||
|
|
|
@ -215,16 +215,12 @@ impl GstObjectImpl for Device {}
|
||||||
impl DeviceImpl for Device {
|
impl DeviceImpl for Device {
|
||||||
fn create_element(&self, name: Option<&str>) -> Result<gst::Element, gst::LoggableError> {
|
fn create_element(&self, name: Option<&str>) -> Result<gst::Element, gst::LoggableError> {
|
||||||
let source_info = self.source.get().unwrap();
|
let source_info = self.source.get().unwrap();
|
||||||
let element = glib::Object::with_type(
|
let element = glib::Object::builder::<crate::ndisrc::NdiSrc>()
|
||||||
crate::ndisrc::NdiSrc::static_type(),
|
.property("name", name)
|
||||||
&[
|
.property("ndi-name", source_info.ndi_name())
|
||||||
("name", &name),
|
.property("url-address", source_info.url_address())
|
||||||
("ndi-name", &source_info.ndi_name()),
|
.build()
|
||||||
("url-address", &source_info.url_address()),
|
.upcast::<gst::Element>();
|
||||||
],
|
|
||||||
)
|
|
||||||
.dynamic_cast::<gst::Element>()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(element)
|
Ok(element)
|
||||||
}
|
}
|
||||||
|
@ -242,16 +238,16 @@ impl super::Device {
|
||||||
|
|
||||||
// Put the url-address into the extra properties
|
// Put the url-address into the extra properties
|
||||||
let extra_properties = gst::Structure::builder("properties")
|
let extra_properties = gst::Structure::builder("properties")
|
||||||
.field("ndi-name", &source.ndi_name())
|
.field("ndi-name", source.ndi_name())
|
||||||
.field("url-address", &source.url_address())
|
.field("url-address", source.url_address())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let device = glib::Object::new::<super::Device>(&[
|
let device = glib::Object::builder::<super::Device>()
|
||||||
("caps", &caps),
|
.property("caps", caps)
|
||||||
("display-name", &display_name),
|
.property("display-name", display_name)
|
||||||
("device-class", &device_class),
|
.property("device-class", device_class)
|
||||||
("properties", &extra_properties),
|
.property("properties", extra_properties)
|
||||||
]);
|
.build();
|
||||||
|
|
||||||
let imp = device.imp();
|
let imp = device.imp();
|
||||||
imp.source.set(source.to_owned()).unwrap();
|
imp.source.set(source.to_owned()).unwrap();
|
||||||
|
|
|
@ -121,23 +121,23 @@ impl ElementImpl for NdiSink {
|
||||||
gst::Structure::builder("video/x-raw")
|
gst::Structure::builder("video/x-raw")
|
||||||
.field(
|
.field(
|
||||||
"format",
|
"format",
|
||||||
&gst::List::new(&[
|
gst::List::new([
|
||||||
&gst_video::VideoFormat::Uyvy.to_str(),
|
gst_video::VideoFormat::Uyvy.to_str(),
|
||||||
&gst_video::VideoFormat::I420.to_str(),
|
gst_video::VideoFormat::I420.to_str(),
|
||||||
&gst_video::VideoFormat::Nv12.to_str(),
|
gst_video::VideoFormat::Nv12.to_str(),
|
||||||
&gst_video::VideoFormat::Nv21.to_str(),
|
gst_video::VideoFormat::Nv21.to_str(),
|
||||||
&gst_video::VideoFormat::Yv12.to_str(),
|
gst_video::VideoFormat::Yv12.to_str(),
|
||||||
&gst_video::VideoFormat::Bgra.to_str(),
|
gst_video::VideoFormat::Bgra.to_str(),
|
||||||
&gst_video::VideoFormat::Bgrx.to_str(),
|
gst_video::VideoFormat::Bgrx.to_str(),
|
||||||
&gst_video::VideoFormat::Rgba.to_str(),
|
gst_video::VideoFormat::Rgba.to_str(),
|
||||||
&gst_video::VideoFormat::Rgbx.to_str(),
|
gst_video::VideoFormat::Rgbx.to_str(),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.field("width", &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("height", gst::IntRange::<i32>::new(1, std::i32::MAX))
|
||||||
.field(
|
.field(
|
||||||
"framerate",
|
"framerate",
|
||||||
&gst::FractionRange::new(
|
gst::FractionRange::new(
|
||||||
gst::Fraction::new(0, 1),
|
gst::Fraction::new(0, 1),
|
||||||
gst::Fraction::new(std::i32::MAX, 1),
|
gst::Fraction::new(std::i32::MAX, 1),
|
||||||
),
|
),
|
||||||
|
@ -146,10 +146,10 @@ impl ElementImpl for NdiSink {
|
||||||
)
|
)
|
||||||
.structure(
|
.structure(
|
||||||
gst::Structure::builder("audio/x-raw")
|
gst::Structure::builder("audio/x-raw")
|
||||||
.field("format", &gst_audio::AUDIO_FORMAT_F32.to_str())
|
.field("format", gst_audio::AUDIO_FORMAT_F32.to_str())
|
||||||
.field("rate", &gst::IntRange::<i32>::new(1, i32::MAX))
|
.field("rate", gst::IntRange::<i32>::new(1, i32::MAX))
|
||||||
.field("channels", &gst::IntRange::<i32>::new(1, i32::MAX))
|
.field("channels", gst::IntRange::<i32>::new(1, i32::MAX))
|
||||||
.field("layout", &"interleaved")
|
.field("layout", "interleaved")
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -109,6 +109,12 @@ impl ObjectSubclass for NdiSrc {
|
||||||
impl ObjectImpl for NdiSrc {
|
impl ObjectImpl for NdiSrc {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
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![
|
vec![
|
||||||
glib::ParamSpecString::builder("ndi-name")
|
glib::ParamSpecString::builder("ndi-name")
|
||||||
.nick("NDI Name")
|
.nick("NDI Name")
|
||||||
|
@ -118,11 +124,7 @@ impl ObjectImpl for NdiSrc {
|
||||||
.nick("URL/Address")
|
.nick("URL/Address")
|
||||||
.blurb("URL/address and port of the sender, e.g. 127.0.0.1:5961")
|
.blurb("URL/address and port of the sender, e.g. 127.0.0.1:5961")
|
||||||
.build(),
|
.build(),
|
||||||
glib::ParamSpecString::builder("receiver-ndi-name")
|
receiver.build(),
|
||||||
.nick("Receiver NDI Name")
|
|
||||||
.blurb("NDI stream name of this receiver")
|
|
||||||
.doc_show_default()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecUInt::builder("connect-timeout")
|
glib::ParamSpecUInt::builder("connect-timeout")
|
||||||
.nick("Connect Timeout")
|
.nick("Connect Timeout")
|
||||||
.blurb("Connection timeout in ms")
|
.blurb("Connection timeout in ms")
|
||||||
|
|
|
@ -915,14 +915,14 @@ impl Receiver {
|
||||||
real_time_now,
|
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,
|
element,
|
||||||
timestamp,
|
timestamp,
|
||||||
receive_time,
|
receive_time,
|
||||||
duration,
|
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,
|
element,
|
||||||
Some(timecode),
|
Some(timecode),
|
||||||
receive_time,
|
receive_time,
|
||||||
|
@ -1299,14 +1299,14 @@ impl Receiver {
|
||||||
|
|
||||||
gst::ReferenceTimestampMeta::add(
|
gst::ReferenceTimestampMeta::add(
|
||||||
buffer,
|
buffer,
|
||||||
&*crate::TIMECODE_CAPS,
|
&crate::TIMECODE_CAPS,
|
||||||
(video_frame.timecode() as u64 * 100).nseconds(),
|
(video_frame.timecode() as u64 * 100).nseconds(),
|
||||||
gst::ClockTime::NONE,
|
gst::ClockTime::NONE,
|
||||||
);
|
);
|
||||||
if video_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined {
|
if video_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined {
|
||||||
gst::ReferenceTimestampMeta::add(
|
gst::ReferenceTimestampMeta::add(
|
||||||
buffer,
|
buffer,
|
||||||
&*crate::TIMESTAMP_CAPS,
|
&crate::TIMESTAMP_CAPS,
|
||||||
(video_frame.timestamp() as u64 * 100).nseconds(),
|
(video_frame.timestamp() as u64 * 100).nseconds(),
|
||||||
gst::ClockTime::NONE,
|
gst::ClockTime::NONE,
|
||||||
);
|
);
|
||||||
|
@ -1587,11 +1587,19 @@ impl Receiver {
|
||||||
let fourcc = audio_frame.fourcc();
|
let fourcc = audio_frame.fourcc();
|
||||||
|
|
||||||
if [NDIlib_FourCC_audio_type_FLTp].contains(&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(
|
let builder = gst_audio::AudioInfo::builder(
|
||||||
gst_audio::AUDIO_FORMAT_F32,
|
gst_audio::AUDIO_FORMAT_F32,
|
||||||
audio_frame.sample_rate() as u32,
|
audio_frame.sample_rate() as u32,
|
||||||
audio_frame.no_channels() as u32,
|
channels,
|
||||||
);
|
)
|
||||||
|
.positions(&positions[..channels as usize]);
|
||||||
|
|
||||||
let info = builder.build().map_err(|_| {
|
let info = builder.build().map_err(|_| {
|
||||||
gst::element_error!(
|
gst::element_error!(
|
||||||
|
@ -1670,14 +1678,14 @@ impl Receiver {
|
||||||
|
|
||||||
gst::ReferenceTimestampMeta::add(
|
gst::ReferenceTimestampMeta::add(
|
||||||
buffer,
|
buffer,
|
||||||
&*crate::TIMECODE_CAPS,
|
&crate::TIMECODE_CAPS,
|
||||||
(audio_frame.timecode() as u64 * 100).nseconds(),
|
(audio_frame.timecode() as u64 * 100).nseconds(),
|
||||||
gst::ClockTime::NONE,
|
gst::ClockTime::NONE,
|
||||||
);
|
);
|
||||||
if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined {
|
if audio_frame.timestamp() != ndisys::NDIlib_recv_timestamp_undefined {
|
||||||
gst::ReferenceTimestampMeta::add(
|
gst::ReferenceTimestampMeta::add(
|
||||||
buffer,
|
buffer,
|
||||||
&*crate::TIMESTAMP_CAPS,
|
&crate::TIMESTAMP_CAPS,
|
||||||
(audio_frame.timestamp() as u64 * 100).nseconds(),
|
(audio_frame.timestamp() as u64 * 100).nseconds(),
|
||||||
gst::ClockTime::NONE,
|
gst::ClockTime::NONE,
|
||||||
);
|
);
|
||||||
|
|
|
@ -21,6 +21,8 @@ const LIBRARY_NAME: &str = "Processing.NDI.Lib.x86.dll";
|
||||||
const LIBRARY_NAME: &str = "libndi.so.5";
|
const LIBRARY_NAME: &str = "libndi.so.5";
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
const LIBRARY_NAME: &str = "libndi.dylib";
|
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)]
|
#[allow(clippy::type_complexity)]
|
||||||
struct FFI {
|
struct FFI {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-onvif"
|
name = "gst-plugin-onvif"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
|
authors = ["Mathieu Duponchelle <mathieu@centricular.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
|
@ -9,16 +9,16 @@ edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", 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", 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", 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", 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"
|
once_cell = "1.0"
|
||||||
xmlparser = "0.13"
|
xmlparser = "0.13"
|
||||||
chrono = { version = "0.4", default-features = false }
|
chrono = { version = "0.4", default-features = false }
|
||||||
cairo-rs = { git = "https://github.com/gtk-rs/gtk-rs-core", features=["use_glib"] }
|
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" }
|
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" }
|
pangocairo = { git = "https://github.com/gtk-rs/gtk-rs-core", branch = "0.16", version = "0.16" }
|
||||||
xmltree = "0.10"
|
xmltree = "0.10"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -27,7 +27,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
|
@ -187,8 +187,8 @@ impl OnvifMetadataOverlay {
|
||||||
gst_video::VideoFormat::Bgra,
|
gst_video::VideoFormat::Bgra,
|
||||||
#[cfg(target_endian = "big")]
|
#[cfg(target_endian = "big")]
|
||||||
gst_video::VideoFormat::Argb,
|
gst_video::VideoFormat::Argb,
|
||||||
total_width as u32,
|
total_width,
|
||||||
total_height as u32,
|
total_height,
|
||||||
)
|
)
|
||||||
.ok()?;
|
.ok()?;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "gst-plugin-raptorq"
|
name = "gst-plugin-raptorq"
|
||||||
version = "0.9.0-alpha.1"
|
version = "0.9.8"
|
||||||
authors = ["Tomasz Andrzejak <andreiltd@gmail.com>"]
|
authors = ["Tomasz Andrzejak <andreiltd@gmail.com>"]
|
||||||
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
repository = "https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs"
|
||||||
description = "GStreamer RaptorQ FEC Plugin"
|
description = "GStreamer RaptorQ FEC Plugin"
|
||||||
|
@ -9,14 +9,14 @@ edition = "2021"
|
||||||
rust-version = "1.63"
|
rust-version = "1.63"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gst = { package = "gstreamer", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19.1" }
|
||||||
gst-base = { package = "gstreamer-base", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
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" }
|
gst-rtp = { package = "gstreamer-rtp", git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", branch = "0.19", version = "0.19" }
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
raptorq = "1.7"
|
raptorq = "1.7"
|
||||||
|
|
||||||
[dev-dependencies]
|
[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"
|
rand = "0.8"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -25,7 +25,7 @@ crate-type = ["cdylib", "rlib"]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
gst-plugin-version-helper = { path="../../version-helper" }
|
gst-plugin-version-helper = { version = "0.7", path="../../version-helper" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
static = []
|
static = []
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue